summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.OpenId
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenAuth.OpenId')
-rw-r--r--src/DotNetOpenAuth.OpenId/Configuration/AssociationTypeCollection.cs60
-rw-r--r--src/DotNetOpenAuth.OpenId/Configuration/AssociationTypeElement.cs57
-rw-r--r--src/DotNetOpenAuth.OpenId/Configuration/HostMetaDiscoveryElement.cs37
-rw-r--r--src/DotNetOpenAuth.OpenId/Configuration/OpenIdElement.cs150
-rw-r--r--src/DotNetOpenAuth.OpenId/Configuration/OpenIdProviderElement.cs72
-rw-r--r--src/DotNetOpenAuth.OpenId/Configuration/OpenIdProviderSecuritySettingsElement.cs154
-rw-r--r--src/DotNetOpenAuth.OpenId/Configuration/OpenIdRelyingPartyElement.cs135
-rw-r--r--src/DotNetOpenAuth.OpenId/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs266
-rw-r--r--src/DotNetOpenAuth.OpenId/Configuration/XriResolverElement.cs66
-rw-r--r--src/DotNetOpenAuth.OpenId/DotNetOpenAuth.OpenId.csproj192
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Association.cs307
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/AssociationContract.cs65
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/AuthenticationRequestMode.cs26
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Behaviors/AXFetchAsSregTransformBase.cs37
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Behaviors/BehaviorStrings.Designer.cs126
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Behaviors/BehaviorStrings.resx141
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Behaviors/BehaviorStrings.sr.resx123
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Behaviors/GsaIcamProfileBase.cs58
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/BackwardCompatibilityBindingElement.cs129
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/ExtensionsBindingElement.cs251
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/IOpenIdExtensionFactory.cs48
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/ITamperResistantOpenIdMessage.cs46
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/KeyValueFormEncoding.cs169
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/OpenIdChannel.cs229
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/OriginalStringUriEncoder.cs47
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs210
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/SigningBindingElement.cs199
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/SigningBindingElementContract.cs64
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/SkipSecurityBindingElement.cs87
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/DiffieHellmanUtilities.cs161
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/AliasManager.cs187
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AXAttributeFormats.cs45
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AXUtilities.cs144
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AttributeRequest.cs156
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AttributeValues.cs114
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/Constants.cs18
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/FetchRequest.cs285
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/FetchResponse.cs207
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/StoreRequest.cs127
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/StoreResponse.cs164
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/WellKnownAttributes.cs325
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/ExtensionArgumentsManager.cs223
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/ExtensionBase.cs190
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/IClientScriptExtensionResponse.cs32
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/OAuth/AuthorizationApprovedResponse.cs48
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/OAuth/AuthorizationDeclinedResponse.cs34
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/OAuth/AuthorizationRequest.cs57
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/OAuth/Constants.cs22
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/OpenIdExtensionFactoryAggregator.cs81
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/OpenIdExtensionsInteropHelper.cs124
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs70
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/Constants.cs77
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/DateTimeEncoder.cs69
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/NistAssuranceLevel.cs60
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PapeUtilities.cs65
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyRequest.cs222
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs282
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs316
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs360
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/Constants.cs67
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/DemandLevel.cs32
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/Gender.cs22
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/GenderEncoder.cs54
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/StandardOpenIdExtensionFactory.cs97
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/UI/UIConstants.cs34
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/UI/UIModes.cs25
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/UI/UIRequest.cs197
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Extensions/UI/UIUtilities.cs28
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/HmacShaAssociation.cs285
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/IIdentifierDiscoveryService.cs67
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/IOpenIdApplicationStore.cs16
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/IOpenIdHost.cs28
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/IProviderEndpoint.cs143
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Identifier.cs336
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/IdentifierContract.cs56
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/IdentifierDiscoveryResult.cs497
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/IdentifierDiscoveryServices.cs82
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateDiffieHellmanRequest.cs100
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateDiffieHellmanResponse.cs45
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateRequest.cs67
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateSuccessfulResponse.cs92
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateSuccessfulResponseContract.cs17
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateUnencryptedRequest.cs50
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateUnencryptedResponse.cs35
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateUnsuccessfulResponse.cs54
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Messages/CheckAuthenticationRequest.cs79
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Messages/CheckAuthenticationResponse.cs55
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Messages/CheckIdRequest.cs93
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Messages/DirectErrorResponse.cs68
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Messages/DirectResponseBase.cs157
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Messages/IErrorMessage.cs32
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Messages/IOpenIdMessageExtension.cs160
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Messages/IndirectErrorResponse.cs57
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Messages/IndirectResponseBase.cs113
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Messages/IndirectSignedResponse.cs411
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Messages/NegativeAssertionResponse.cs141
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Messages/PositiveAssertionResponse.cs83
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Messages/RequestBase.cs185
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Messages/SignedResponseRequest.cs184
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/NoDiscoveryIdentifier.cs100
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/OpenIdStrings.Designer.cs823
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/OpenIdStrings.resx376
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/OpenIdStrings.sr.resx340
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/OpenIdUtilities.cs218
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/OpenIdXrdsHelper.cs68
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Protocol.cs478
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Provider/IAuthenticationRequest.cs367
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Provider/IHostProcessedRequest.cs202
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Provider/IProviderBehavior.cs114
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Provider/IRequest.cs149
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Provider/ProviderSecuritySettings.cs167
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Provider/RelyingPartyDiscoveryResult.cs36
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/ProviderEndpointDescription.cs134
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/Realm.cs534
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/AuthenticationStatus.cs43
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/IAuthenticationRequest.cs186
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/IAuthenticationRequestContract.cs111
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/IAuthenticationResponse.cs530
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/IRelyingPartyBehavior.cs92
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/RelyingPartySecuritySettings.cs172
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/RelyingPartyEndpointDescription.cs63
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/SecuritySettings.cs110
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/UriDiscoveryService.cs139
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/UriIdentifier.cs729
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/XriDiscoveryProxyService.cs108
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/XriIdentifier.cs207
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenIdXrdsHelperRelyingParty.cs163
-rw-r--r--src/DotNetOpenAuth.OpenId/Properties/AssemblyInfo.cs66
-rw-r--r--src/DotNetOpenAuth.OpenId/Xrds/ServiceElement.cs133
-rw-r--r--src/DotNetOpenAuth.OpenId/Xrds/TypeElement.cs34
-rw-r--r--src/DotNetOpenAuth.OpenId/Xrds/UriElement.cs98
-rw-r--r--src/DotNetOpenAuth.OpenId/Xrds/XrdElement.cs160
-rw-r--r--src/DotNetOpenAuth.OpenId/Xrds/XrdsDocument.cs87
-rw-r--r--src/DotNetOpenAuth.OpenId/Xrds/XrdsNode.cs69
-rw-r--r--src/DotNetOpenAuth.OpenId/Xrds/XrdsStrings.Designer.cs99
-rw-r--r--src/DotNetOpenAuth.OpenId/Xrds/XrdsStrings.resx132
-rw-r--r--src/DotNetOpenAuth.OpenId/Xrds/XrdsStrings.sr.resx132
-rw-r--r--src/DotNetOpenAuth.OpenId/Yadis/ContentTypes.cs32
-rw-r--r--src/DotNetOpenAuth.OpenId/Yadis/DiscoveryResult.cs106
-rw-r--r--src/DotNetOpenAuth.OpenId/Yadis/HtmlParser.cs142
-rw-r--r--src/DotNetOpenAuth.OpenId/Yadis/Yadis.cs205
141 files changed, 20414 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.OpenId/Configuration/AssociationTypeCollection.cs b/src/DotNetOpenAuth.OpenId/Configuration/AssociationTypeCollection.cs
new file mode 100644
index 0000000..419a76a
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/Configuration/AssociationTypeCollection.cs
@@ -0,0 +1,60 @@
+//-----------------------------------------------------------------------
+// <copyright file="AssociationTypeCollection.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Configuration {
+ using System.Collections.Generic;
+ using System.Configuration;
+ using System.Diagnostics.Contracts;
+
+ /// <summary>
+ /// Describes a collection of association type sub-elements in a .config file.
+ /// </summary>
+ [ContractVerification(true)]
+ internal class AssociationTypeCollection : ConfigurationElementCollection, IEnumerable<AssociationTypeElement> {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AssociationTypeCollection"/> class.
+ /// </summary>
+ public AssociationTypeCollection() {
+ }
+
+ #region IEnumerable<AssociationTypeElement> Members
+
+ /// <summary>
+ /// Returns an enumerator that iterates through the collection.
+ /// </summary>
+ /// <returns>
+ /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
+ /// </returns>
+ public new IEnumerator<AssociationTypeElement> GetEnumerator() {
+ for (int i = 0; i < Count; i++) {
+ yield return (AssociationTypeElement)BaseGet(i);
+ }
+ }
+
+ #endregion
+
+ /// <summary>
+ /// When overridden in a derived class, creates a new <see cref="T:System.Configuration.ConfigurationElement"/>.
+ /// </summary>
+ /// <returns>
+ /// A new <see cref="T:System.Configuration.ConfigurationElement"/>.
+ /// </returns>
+ protected override ConfigurationElement CreateNewElement() {
+ return new AssociationTypeElement();
+ }
+
+ /// <summary>
+ /// Gets the element key for a specified configuration element when overridden in a derived class.
+ /// </summary>
+ /// <param name="element">The <see cref="T:System.Configuration.ConfigurationElement"/> to return the key for.</param>
+ /// <returns>
+ /// An <see cref="T:System.Object"/> that acts as the key for the specified <see cref="T:System.Configuration.ConfigurationElement"/>.
+ /// </returns>
+ protected override object GetElementKey(ConfigurationElement element) {
+ return ((AssociationTypeElement)element).AssociationType ?? string.Empty;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/Configuration/AssociationTypeElement.cs b/src/DotNetOpenAuth.OpenId/Configuration/AssociationTypeElement.cs
new file mode 100644
index 0000000..32c1cc9
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/Configuration/AssociationTypeElement.cs
@@ -0,0 +1,57 @@
+//-----------------------------------------------------------------------
+// <copyright file="AssociationTypeElement.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Configuration {
+ using System;
+ using System.Collections.Generic;
+ using System.Configuration;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Text;
+
+ /// <summary>
+ /// Describes an association type and its maximum lifetime as an element
+ /// in a .config file.
+ /// </summary>
+ [ContractVerification(true)]
+ internal class AssociationTypeElement : ConfigurationElement {
+ /// <summary>
+ /// The name of the attribute that stores the association type.
+ /// </summary>
+ private const string AssociationTypeConfigName = "type";
+
+ /// <summary>
+ /// The name of the attribute that stores the association's maximum lifetime.
+ /// </summary>
+ private const string MaximumLifetimeConfigName = "lifetime";
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AssociationTypeElement"/> class.
+ /// </summary>
+ internal AssociationTypeElement() {
+ }
+
+ /// <summary>
+ /// Gets or sets the protocol name of the association.
+ /// </summary>
+ [ConfigurationProperty(AssociationTypeConfigName, IsRequired = true, IsKey = true)]
+ ////[StringValidator(MinLength = 1)]
+ public string AssociationType {
+ get { return (string)this[AssociationTypeConfigName]; }
+ set { this[AssociationTypeConfigName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the maximum time a shared association should live.
+ /// </summary>
+ /// <value>The default value is 14 days.</value>
+ [ConfigurationProperty(MaximumLifetimeConfigName, IsRequired = true)]
+ public TimeSpan MaximumLifetime {
+ get { return (TimeSpan)this[MaximumLifetimeConfigName]; }
+ set { this[MaximumLifetimeConfigName] = value; }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/Configuration/HostMetaDiscoveryElement.cs b/src/DotNetOpenAuth.OpenId/Configuration/HostMetaDiscoveryElement.cs
new file mode 100644
index 0000000..437b12f
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/Configuration/HostMetaDiscoveryElement.cs
@@ -0,0 +1,37 @@
+//-----------------------------------------------------------------------
+// <copyright file="HostMetaDiscoveryElement.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Configuration {
+ using System.Configuration;
+
+ /// <summary>
+ /// The configuration element that can adjust how hostmeta discovery works.
+ /// </summary>
+ internal class HostMetaDiscoveryElement : ConfigurationElement {
+ /// <summary>
+ /// The property name for enableCertificateValidationCache.
+ /// </summary>
+ private const string EnableCertificateValidationCacheConfigName = "enableCertificateValidationCache";
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="HostMetaDiscoveryElement"/> class.
+ /// </summary>
+ public HostMetaDiscoveryElement() {
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether validated certificates should be cached and not validated again.
+ /// </summary>
+ /// <remarks>
+ /// This helps to avoid unexplained 5-10 second delays in certificate validation for Google Apps for Domains that impact some servers.
+ /// </remarks>
+ [ConfigurationProperty(EnableCertificateValidationCacheConfigName, DefaultValue = false)]
+ public bool EnableCertificateValidationCache {
+ get { return (bool)this[EnableCertificateValidationCacheConfigName]; }
+ set { this[EnableCertificateValidationCacheConfigName] = value; }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/Configuration/OpenIdElement.cs b/src/DotNetOpenAuth.OpenId/Configuration/OpenIdElement.cs
new file mode 100644
index 0000000..c43a3ad
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/Configuration/OpenIdElement.cs
@@ -0,0 +1,150 @@
+//-----------------------------------------------------------------------
+// <copyright file="OpenIdElement.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Configuration {
+ using System;
+ using System.Collections.Generic;
+ using System.Configuration;
+ using System.Diagnostics.Contracts;
+ using DotNetOpenAuth.OpenId.ChannelElements;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// Represents the &lt;openid&gt; element in the host's .config file.
+ /// </summary>
+ [ContractVerification(true)]
+ internal class OpenIdElement : ConfigurationSection {
+ /// <summary>
+ /// The name of the section under which this library's settings must be found.
+ /// </summary>
+ private const string SectionName = DotNetOpenAuthSection.SectionName + "/openid";
+
+ /// <summary>
+ /// The name of the &lt;relyingParty&gt; sub-element.
+ /// </summary>
+ private const string RelyingPartyElementName = "relyingParty";
+
+ /// <summary>
+ /// The name of the &lt;provider&gt; sub-element.
+ /// </summary>
+ private const string ProviderElementName = "provider";
+
+ /// <summary>
+ /// The name of the &lt;extensions&gt; sub-element.
+ /// </summary>
+ private const string ExtensionFactoriesElementName = "extensionFactories";
+
+ /// <summary>
+ /// The name of the &lt;xriResolver&gt; sub-element.
+ /// </summary>
+ private const string XriResolverElementName = "xriResolver";
+
+ /// <summary>
+ /// The name of the @maxAuthenticationTime attribute.
+ /// </summary>
+ private const string MaxAuthenticationTimePropertyName = "maxAuthenticationTime";
+
+ /// <summary>
+ /// The name of the @cacheDiscovery attribute.
+ /// </summary>
+ private const string CacheDiscoveryPropertyName = "cacheDiscovery";
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OpenIdElement"/> class.
+ /// </summary>
+ internal OpenIdElement() {
+ }
+
+ /// <summary>
+ /// Gets the configuration section from the .config file.
+ /// </summary>
+ public static OpenIdElement Configuration {
+ get {
+ Contract.Ensures(Contract.Result<OpenIdElement>() != null);
+ return (OpenIdElement)ConfigurationManager.GetSection(SectionName) ?? new OpenIdElement();
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the maximum time a user can take to complete authentication.
+ /// </summary>
+ /// <remarks>
+ /// This time limit allows the library to decide how long to cache certain values
+ /// necessary to complete authentication. The lower the time, the less demand on
+ /// the server. But too short a time can frustrate the user.
+ /// </remarks>
+ [ConfigurationProperty(MaxAuthenticationTimePropertyName, DefaultValue = "0:05")] // 5 minutes
+ [PositiveTimeSpanValidator]
+ internal TimeSpan MaxAuthenticationTime {
+ get {
+ Contract.Ensures(Contract.Result<TimeSpan>() > TimeSpan.Zero);
+ TimeSpan result = (TimeSpan)this[MaxAuthenticationTimePropertyName];
+ Contract.Assume(result > TimeSpan.Zero); // our PositiveTimeSpanValidator should take care of this
+ return result;
+ }
+
+ set {
+ Requires.InRange(value > TimeSpan.Zero, "value");
+ this[MaxAuthenticationTimePropertyName] = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the results of Identifier discovery
+ /// should be cached.
+ /// </summary>
+ /// <value>
+ /// Use <c>true</c> to allow identifier discovery to immediately return cached results when available;
+ /// otherwise, use <c>false</c>.to force fresh results every time at the cost of slightly slower logins.
+ /// The default value is <c>true</c>.
+ /// </value>
+ /// <remarks>
+ /// When enabled, caching is done according to HTTP standards.
+ /// </remarks>
+ [ConfigurationProperty(CacheDiscoveryPropertyName, DefaultValue = true)]
+ internal bool CacheDiscovery {
+ get { return (bool)this[CacheDiscoveryPropertyName]; }
+ set { this[CacheDiscoveryPropertyName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the configuration specific for Relying Parties.
+ /// </summary>
+ [ConfigurationProperty(RelyingPartyElementName)]
+ internal OpenIdRelyingPartyElement RelyingParty {
+ get { return (OpenIdRelyingPartyElement)this[RelyingPartyElementName] ?? new OpenIdRelyingPartyElement(); }
+ set { this[RelyingPartyElementName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the configuration specific for Providers.
+ /// </summary>
+ [ConfigurationProperty(ProviderElementName)]
+ internal OpenIdProviderElement Provider {
+ get { return (OpenIdProviderElement)this[ProviderElementName] ?? new OpenIdProviderElement(); }
+ set { this[ProviderElementName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the registered OpenID extension factories.
+ /// </summary>
+ [ConfigurationProperty(ExtensionFactoriesElementName, IsDefaultCollection = false)]
+ [ConfigurationCollection(typeof(TypeConfigurationCollection<IOpenIdExtensionFactory>))]
+ internal TypeConfigurationCollection<IOpenIdExtensionFactory> ExtensionFactories {
+ get { return (TypeConfigurationCollection<IOpenIdExtensionFactory>)this[ExtensionFactoriesElementName] ?? new TypeConfigurationCollection<IOpenIdExtensionFactory>(); }
+ set { this[ExtensionFactoriesElementName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the configuration for the XRI resolver.
+ /// </summary>
+ [ConfigurationProperty(XriResolverElementName)]
+ internal XriResolverElement XriResolver {
+ get { return (XriResolverElement)this[XriResolverElementName] ?? new XriResolverElement(); }
+ set { this[XriResolverElementName] = value; }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/Configuration/OpenIdProviderElement.cs b/src/DotNetOpenAuth.OpenId/Configuration/OpenIdProviderElement.cs
new file mode 100644
index 0000000..df93d3b
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/Configuration/OpenIdProviderElement.cs
@@ -0,0 +1,72 @@
+//-----------------------------------------------------------------------
+// <copyright file="OpenIdProviderElement.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Configuration {
+ using System.Configuration;
+ using System.Diagnostics.Contracts;
+ using DotNetOpenAuth.OpenId;
+ using DotNetOpenAuth.OpenId.Provider;
+
+ /// <summary>
+ /// The section in the .config file that allows customization of OpenID Provider behaviors.
+ /// </summary>
+ [ContractVerification(true)]
+ internal class OpenIdProviderElement : ConfigurationElement {
+ /// <summary>
+ /// The name of the &lt;provider&gt; sub-element.
+ /// </summary>
+ private const string ProviderElementName = "provider";
+
+ /// <summary>
+ /// The name of the security sub-element.
+ /// </summary>
+ private const string SecuritySettingsConfigName = "security";
+
+ /// <summary>
+ /// Gets the name of the &lt;behaviors&gt; sub-element.
+ /// </summary>
+ private const string BehaviorsElementName = "behaviors";
+
+ /// <summary>
+ /// The name of the custom store sub-element.
+ /// </summary>
+ private const string StoreConfigName = "store";
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OpenIdProviderElement"/> class.
+ /// </summary>
+ public OpenIdProviderElement() {
+ }
+
+ /// <summary>
+ /// Gets or sets the security settings.
+ /// </summary>
+ [ConfigurationProperty(SecuritySettingsConfigName)]
+ public OpenIdProviderSecuritySettingsElement SecuritySettings {
+ get { return (OpenIdProviderSecuritySettingsElement)this[SecuritySettingsConfigName] ?? new OpenIdProviderSecuritySettingsElement(); }
+ set { this[SecuritySettingsConfigName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the special behaviors to apply.
+ /// </summary>
+ [ConfigurationProperty(BehaviorsElementName, IsDefaultCollection = false)]
+ [ConfigurationCollection(typeof(TypeConfigurationCollection<IProviderBehavior>))]
+ public TypeConfigurationCollection<IProviderBehavior> Behaviors {
+ get { return (TypeConfigurationCollection<IProviderBehavior>)this[BehaviorsElementName] ?? new TypeConfigurationCollection<IProviderBehavior>(); }
+ set { this[BehaviorsElementName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the type to use for storing application state.
+ /// </summary>
+ [ConfigurationProperty(StoreConfigName)]
+ public TypeConfigurationElement<IOpenIdApplicationStore> ApplicationStore {
+ get { return (TypeConfigurationElement<IOpenIdApplicationStore>)this[StoreConfigName] ?? new TypeConfigurationElement<IOpenIdApplicationStore>(); }
+ set { this[StoreConfigName] = value; }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/Configuration/OpenIdProviderSecuritySettingsElement.cs b/src/DotNetOpenAuth.OpenId/Configuration/OpenIdProviderSecuritySettingsElement.cs
new file mode 100644
index 0000000..f003900
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/Configuration/OpenIdProviderSecuritySettingsElement.cs
@@ -0,0 +1,154 @@
+//-----------------------------------------------------------------------
+// <copyright file="OpenIdProviderSecuritySettingsElement.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Configuration {
+ using System.Configuration;
+ using System.Diagnostics.Contracts;
+ using DotNetOpenAuth.OpenId;
+ using DotNetOpenAuth.OpenId.Provider;
+
+ /// <summary>
+ /// Represents the .config file element that allows for setting the security policies of the Provider.
+ /// </summary>
+ [ContractVerification(true)]
+ internal class OpenIdProviderSecuritySettingsElement : ConfigurationElement {
+ /// <summary>
+ /// Gets the name of the @protectDownlevelReplayAttacks attribute.
+ /// </summary>
+ private const string ProtectDownlevelReplayAttacksConfigName = "protectDownlevelReplayAttacks";
+
+ /// <summary>
+ /// Gets the name of the @minimumHashBitLength attribute.
+ /// </summary>
+ private const string MinimumHashBitLengthConfigName = "minimumHashBitLength";
+
+ /// <summary>
+ /// Gets the name of the @maximumHashBitLength attribute.
+ /// </summary>
+ private const string MaximumHashBitLengthConfigName = "maximumHashBitLength";
+
+ /// <summary>
+ /// The name of the associations collection sub-element.
+ /// </summary>
+ private const string AssociationsConfigName = "associations";
+
+ /// <summary>
+ /// The name of the @encodeAssociationSecretsInHandles attribute.
+ /// </summary>
+ private const string EncodeAssociationSecretsInHandlesConfigName = "encodeAssociationSecretsInHandles";
+
+ /// <summary>
+ /// Gets the name of the @requireSsl attribute.
+ /// </summary>
+ private const string RequireSslConfigName = "requireSsl";
+
+ /// <summary>
+ /// Gets the name of the @unsolicitedAssertionVerification attribute.
+ /// </summary>
+ private const string UnsolicitedAssertionVerificationConfigName = "unsolicitedAssertionVerification";
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OpenIdProviderSecuritySettingsElement"/> class.
+ /// </summary>
+ public OpenIdProviderSecuritySettingsElement() {
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether all discovery and authentication should require SSL security.
+ /// </summary>
+ [ConfigurationProperty(RequireSslConfigName, DefaultValue = false)]
+ public bool RequireSsl {
+ get { return (bool)this[RequireSslConfigName]; }
+ set { this[RequireSslConfigName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the minimum length of the hash that protects the protocol from hijackers.
+ /// </summary>
+ [ConfigurationProperty(MinimumHashBitLengthConfigName, DefaultValue = SecuritySettings.MinimumHashBitLengthDefault)]
+ public int MinimumHashBitLength {
+ get { return (int)this[MinimumHashBitLengthConfigName]; }
+ set { this[MinimumHashBitLengthConfigName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the maximum length of the hash that protects the protocol from hijackers.
+ /// </summary>
+ [ConfigurationProperty(MaximumHashBitLengthConfigName, DefaultValue = SecuritySettings.MaximumHashBitLengthRPDefault)]
+ public int MaximumHashBitLength {
+ get { return (int)this[MaximumHashBitLengthConfigName]; }
+ set { this[MaximumHashBitLengthConfigName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the Provider should take special care
+ /// to protect OpenID 1.x relying parties against replay attacks.
+ /// </summary>
+ [ConfigurationProperty(ProtectDownlevelReplayAttacksConfigName, DefaultValue = ProviderSecuritySettings.ProtectDownlevelReplayAttacksDefault)]
+ public bool ProtectDownlevelReplayAttacks {
+ get { return (bool)this[ProtectDownlevelReplayAttacksConfigName]; }
+ set { this[ProtectDownlevelReplayAttacksConfigName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the level of verification a Provider performs on an identifier before
+ /// sending an unsolicited assertion for it.
+ /// </summary>
+ /// <value>The default value is <see cref="ProviderSecuritySettings.UnsolicitedAssertionVerificationLevel.RequireSuccess"/>.</value>
+ [ConfigurationProperty(UnsolicitedAssertionVerificationConfigName, DefaultValue = ProviderSecuritySettings.UnsolicitedAssertionVerificationDefault)]
+ public ProviderSecuritySettings.UnsolicitedAssertionVerificationLevel UnsolicitedAssertionVerification {
+ get { return (ProviderSecuritySettings.UnsolicitedAssertionVerificationLevel)this[UnsolicitedAssertionVerificationConfigName]; }
+ set { this[UnsolicitedAssertionVerificationConfigName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the configured lifetimes of the various association types.
+ /// </summary>
+ [ConfigurationProperty(AssociationsConfigName, IsDefaultCollection = false)]
+ [ConfigurationCollection(typeof(AssociationTypeCollection))]
+ public AssociationTypeCollection AssociationLifetimes {
+ get {
+ Contract.Ensures(Contract.Result<AssociationTypeCollection>() != null);
+ return (AssociationTypeCollection)this[AssociationsConfigName] ?? new AssociationTypeCollection();
+ }
+
+ set {
+ this[AssociationsConfigName] = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the Provider should ease the burden of storing associations
+ /// by encoding their secrets (in signed, encrypted form) into the association handles themselves, storing only
+ /// a few rotating, private symmetric keys in the Provider's store instead.
+ /// </summary>
+ [ConfigurationProperty(EncodeAssociationSecretsInHandlesConfigName, DefaultValue = ProviderSecuritySettings.EncodeAssociationSecretsInHandlesDefault)]
+ public bool EncodeAssociationSecretsInHandles {
+ get { return (bool)this[EncodeAssociationSecretsInHandlesConfigName]; }
+ set { this[EncodeAssociationSecretsInHandlesConfigName] = value; }
+ }
+
+ /// <summary>
+ /// Initializes a programmatically manipulatable bag of these security settings with the settings from the config file.
+ /// </summary>
+ /// <returns>The newly created security settings object.</returns>
+ public ProviderSecuritySettings CreateSecuritySettings() {
+ ProviderSecuritySettings settings = new ProviderSecuritySettings();
+ settings.RequireSsl = this.RequireSsl;
+ settings.MinimumHashBitLength = this.MinimumHashBitLength;
+ settings.MaximumHashBitLength = this.MaximumHashBitLength;
+ settings.ProtectDownlevelReplayAttacks = this.ProtectDownlevelReplayAttacks;
+ settings.UnsolicitedAssertionVerification = this.UnsolicitedAssertionVerification;
+ settings.EncodeAssociationSecretsInHandles = this.EncodeAssociationSecretsInHandles;
+ foreach (AssociationTypeElement element in this.AssociationLifetimes) {
+ Contract.Assume(element != null);
+ settings.AssociationLifetimes.Add(element.AssociationType, element.MaximumLifetime);
+ }
+
+ return settings;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/Configuration/OpenIdRelyingPartyElement.cs b/src/DotNetOpenAuth.OpenId/Configuration/OpenIdRelyingPartyElement.cs
new file mode 100644
index 0000000..8af1129
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/Configuration/OpenIdRelyingPartyElement.cs
@@ -0,0 +1,135 @@
+//-----------------------------------------------------------------------
+// <copyright file="OpenIdRelyingPartyElement.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Configuration {
+ using System;
+ using System.Configuration;
+ using System.Diagnostics.Contracts;
+ using DotNetOpenAuth.OpenId;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+
+ /// <summary>
+ /// The section in the .config file that allows customization of OpenID Relying Party behaviors.
+ /// </summary>
+ [ContractVerification(true)]
+ internal class OpenIdRelyingPartyElement : ConfigurationElement {
+ /// <summary>
+ /// The name of the custom store sub-element.
+ /// </summary>
+ private const string StoreConfigName = "store";
+
+ /// <summary>
+ /// The name of the &lt;relyingParty&gt; sub-element.
+ /// </summary>
+ private const string RelyingPartyElementName = "relyingParty";
+
+ /// <summary>
+ /// The name of the attribute that specifies whether dnoa.userSuppliedIdentifier is tacked onto the openid.return_to URL.
+ /// </summary>
+ private const string PreserveUserSuppliedIdentifierConfigName = "preserveUserSuppliedIdentifier";
+
+ /// <summary>
+ /// Gets the name of the security sub-element.
+ /// </summary>
+ private const string SecuritySettingsConfigName = "security";
+
+ /// <summary>
+ /// The name of the &lt;behaviors&gt; sub-element.
+ /// </summary>
+ private const string BehaviorsElementName = "behaviors";
+
+ /// <summary>
+ /// The name of the &lt;discoveryServices&gt; sub-element.
+ /// </summary>
+ private const string DiscoveryServicesElementName = "discoveryServices";
+
+ /// <summary>
+ /// The name of the &lt;hostMetaDiscovery&gt; sub-element.
+ /// </summary>
+ private const string HostMetaDiscoveryElementName = "hostMetaDiscovery";
+
+ /// <summary>
+ /// The built-in set of identifier discovery services.
+ /// </summary>
+ private static readonly TypeConfigurationCollection<IIdentifierDiscoveryService> defaultDiscoveryServices =
+ new TypeConfigurationCollection<IIdentifierDiscoveryService>(new Type[] { typeof(UriDiscoveryService), typeof(XriDiscoveryProxyService) });
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OpenIdRelyingPartyElement"/> class.
+ /// </summary>
+ public OpenIdRelyingPartyElement() {
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether "dnoa.userSuppliedIdentifier" is tacked onto the openid.return_to URL in order to preserve what the user typed into the OpenID box.
+ /// </summary>
+ /// <value>
+ /// The default value is <c>true</c>.
+ /// </value>
+ [ConfigurationProperty(PreserveUserSuppliedIdentifierConfigName, DefaultValue = true)]
+ public bool PreserveUserSuppliedIdentifier {
+ get { return (bool)this[PreserveUserSuppliedIdentifierConfigName]; }
+ set { this[PreserveUserSuppliedIdentifierConfigName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the security settings.
+ /// </summary>
+ [ConfigurationProperty(SecuritySettingsConfigName)]
+ public OpenIdRelyingPartySecuritySettingsElement SecuritySettings {
+ get { return (OpenIdRelyingPartySecuritySettingsElement)this[SecuritySettingsConfigName] ?? new OpenIdRelyingPartySecuritySettingsElement(); }
+ set { this[SecuritySettingsConfigName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the special behaviors to apply.
+ /// </summary>
+ [ConfigurationProperty(BehaviorsElementName, IsDefaultCollection = false)]
+ [ConfigurationCollection(typeof(TypeConfigurationCollection<IRelyingPartyBehavior>))]
+ public TypeConfigurationCollection<IRelyingPartyBehavior> Behaviors {
+ get { return (TypeConfigurationCollection<IRelyingPartyBehavior>)this[BehaviorsElementName] ?? new TypeConfigurationCollection<IRelyingPartyBehavior>(); }
+ set { this[BehaviorsElementName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the type to use for storing application state.
+ /// </summary>
+ [ConfigurationProperty(StoreConfigName)]
+ public TypeConfigurationElement<IOpenIdApplicationStore> ApplicationStore {
+ get { return (TypeConfigurationElement<IOpenIdApplicationStore>)this[StoreConfigName] ?? new TypeConfigurationElement<IOpenIdApplicationStore>(); }
+ set { this[StoreConfigName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the host meta discovery configuration element.
+ /// </summary>
+ [ConfigurationProperty(HostMetaDiscoveryElementName)]
+ internal HostMetaDiscoveryElement HostMetaDiscovery {
+ get { return (HostMetaDiscoveryElement)this[HostMetaDiscoveryElementName] ?? new HostMetaDiscoveryElement(); }
+ set { this[HostMetaDiscoveryElementName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the services to use for discovering service endpoints for identifiers.
+ /// </summary>
+ /// <remarks>
+ /// If no discovery services are defined in the (web) application's .config file,
+ /// the default set of discovery services built into the library are used.
+ /// </remarks>
+ [ConfigurationProperty(DiscoveryServicesElementName, IsDefaultCollection = false)]
+ [ConfigurationCollection(typeof(TypeConfigurationCollection<IIdentifierDiscoveryService>))]
+ internal TypeConfigurationCollection<IIdentifierDiscoveryService> DiscoveryServices {
+ get {
+ var configResult = (TypeConfigurationCollection<IIdentifierDiscoveryService>)this[DiscoveryServicesElementName];
+ return configResult != null && configResult.Count > 0 ? configResult : defaultDiscoveryServices;
+ }
+
+ set {
+ this[DiscoveryServicesElementName] = value;
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs b/src/DotNetOpenAuth.OpenId/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs
new file mode 100644
index 0000000..f0d8942
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs
@@ -0,0 +1,266 @@
+//-----------------------------------------------------------------------
+// <copyright file="OpenIdRelyingPartySecuritySettingsElement.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Configuration {
+ using System;
+ using System.Configuration;
+ using System.Diagnostics.Contracts;
+ using DotNetOpenAuth.OpenId;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+
+ /// <summary>
+ /// Represents the .config file element that allows for setting the security policies of the Relying Party.
+ /// </summary>
+ internal class OpenIdRelyingPartySecuritySettingsElement : ConfigurationElement {
+ /// <summary>
+ /// Gets the name of the @minimumRequiredOpenIdVersion attribute.
+ /// </summary>
+ private const string MinimumRequiredOpenIdVersionConfigName = "minimumRequiredOpenIdVersion";
+
+ /// <summary>
+ /// Gets the name of the @minimumHashBitLength attribute.
+ /// </summary>
+ private const string MinimumHashBitLengthConfigName = "minimumHashBitLength";
+
+ /// <summary>
+ /// Gets the name of the @maximumHashBitLength attribute.
+ /// </summary>
+ private const string MaximumHashBitLengthConfigName = "maximumHashBitLength";
+
+ /// <summary>
+ /// Gets the name of the @requireSsl attribute.
+ /// </summary>
+ private const string RequireSslConfigName = "requireSsl";
+
+ /// <summary>
+ /// Gets the name of the @requireDirectedIdentity attribute.
+ /// </summary>
+ private const string RequireDirectedIdentityConfigName = "requireDirectedIdentity";
+
+ /// <summary>
+ /// Gets the name of the @requireAssociation attribute.
+ /// </summary>
+ private const string RequireAssociationConfigName = "requireAssociation";
+
+ /// <summary>
+ /// Gets the name of the @rejectUnsolicitedAssertions attribute.
+ /// </summary>
+ private const string RejectUnsolicitedAssertionsConfigName = "rejectUnsolicitedAssertions";
+
+ /// <summary>
+ /// Gets the name of the @rejectDelegatedIdentifiers attribute.
+ /// </summary>
+ private const string RejectDelegatingIdentifiersConfigName = "rejectDelegatingIdentifiers";
+
+ /// <summary>
+ /// Gets the name of the @ignoreUnsignedExtensions attribute.
+ /// </summary>
+ private const string IgnoreUnsignedExtensionsConfigName = "ignoreUnsignedExtensions";
+
+ /// <summary>
+ /// Gets the name of the @allowDualPurposeIdentifiers attribute.
+ /// </summary>
+ private const string AllowDualPurposeIdentifiersConfigName = "allowDualPurposeIdentifiers";
+
+ /// <summary>
+ /// Gets the name of the @allowApproximateIdentifierDiscovery attribute.
+ /// </summary>
+ private const string AllowApproximateIdentifierDiscoveryConfigName = "allowApproximateIdentifierDiscovery";
+
+ /// <summary>
+ /// Gets the name of the @protectDownlevelReplayAttacks attribute.
+ /// </summary>
+ private const string ProtectDownlevelReplayAttacksConfigName = "protectDownlevelReplayAttacks";
+
+ /// <summary>
+ /// The name of the &lt;trustedProviders&gt; sub-element.
+ /// </summary>
+ private const string TrustedProvidersElementName = "trustedProviders";
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OpenIdRelyingPartySecuritySettingsElement"/> class.
+ /// </summary>
+ public OpenIdRelyingPartySecuritySettingsElement() {
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether all discovery and authentication should require SSL security.
+ /// </summary>
+ [ConfigurationProperty(RequireSslConfigName, DefaultValue = false)]
+ public bool RequireSsl {
+ get { return (bool)this[RequireSslConfigName]; }
+ set { this[RequireSslConfigName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether only OP Identifiers will be discoverable
+ /// when creating authentication requests.
+ /// </summary>
+ [ConfigurationProperty(RequireDirectedIdentityConfigName, DefaultValue = false)]
+ public bool RequireDirectedIdentity {
+ get { return (bool)this[RequireDirectedIdentityConfigName]; }
+ set { this[RequireDirectedIdentityConfigName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether authentication requests
+ /// will only be created where an association with the Provider can be established.
+ /// </summary>
+ [ConfigurationProperty(RequireAssociationConfigName, DefaultValue = false)]
+ public bool RequireAssociation {
+ get { return (bool)this[RequireAssociationConfigName]; }
+ set { this[RequireAssociationConfigName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the minimum OpenID version a Provider is required to support in order for this library to interoperate with it.
+ /// </summary>
+ /// <remarks>
+ /// Although the earliest versions of OpenID are supported, for security reasons it may be desirable to require the
+ /// remote party to support a later version of OpenID.
+ /// </remarks>
+ [ConfigurationProperty(MinimumRequiredOpenIdVersionConfigName, DefaultValue = "V10")]
+ public ProtocolVersion MinimumRequiredOpenIdVersion {
+ get { return (ProtocolVersion)this[MinimumRequiredOpenIdVersionConfigName]; }
+ set { this[MinimumRequiredOpenIdVersionConfigName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the minimum length of the hash that protects the protocol from hijackers.
+ /// </summary>
+ [ConfigurationProperty(MinimumHashBitLengthConfigName, DefaultValue = SecuritySettings.MinimumHashBitLengthDefault)]
+ public int MinimumHashBitLength {
+ get { return (int)this[MinimumHashBitLengthConfigName]; }
+ set { this[MinimumHashBitLengthConfigName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the maximum length of the hash that protects the protocol from hijackers.
+ /// </summary>
+ [ConfigurationProperty(MaximumHashBitLengthConfigName, DefaultValue = SecuritySettings.MaximumHashBitLengthRPDefault)]
+ public int MaximumHashBitLength {
+ get { return (int)this[MaximumHashBitLengthConfigName]; }
+ set { this[MaximumHashBitLengthConfigName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether all unsolicited assertions should be ignored.
+ /// </summary>
+ /// <value>The default value is <c>false</c>.</value>
+ [ConfigurationProperty(RejectUnsolicitedAssertionsConfigName, DefaultValue = false)]
+ public bool RejectUnsolicitedAssertions {
+ get { return (bool)this[RejectUnsolicitedAssertionsConfigName]; }
+ set { this[RejectUnsolicitedAssertionsConfigName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether delegating identifiers are refused for authentication.
+ /// </summary>
+ /// <value>The default value is <c>false</c>.</value>
+ /// <remarks>
+ /// When set to <c>true</c>, login attempts that start at the RP or arrive via unsolicited
+ /// assertions will be rejected if discovery on the identifier shows that OpenID delegation
+ /// is used for the identifier. This is useful for an RP that should only accept identifiers
+ /// directly issued by the Provider that is sending the assertion.
+ /// </remarks>
+ [ConfigurationProperty(RejectDelegatingIdentifiersConfigName, DefaultValue = false)]
+ public bool RejectDelegatingIdentifiers {
+ get { return (bool)this[RejectDelegatingIdentifiersConfigName]; }
+ set { this[RejectDelegatingIdentifiersConfigName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether unsigned extensions in authentication responses should be ignored.
+ /// </summary>
+ /// <value>The default value is <c>false</c>.</value>
+ /// <remarks>
+ /// When set to true, the <see cref="IAuthenticationResponse.GetUntrustedExtension"/> methods
+ /// will not return any extension that was not signed by the Provider.
+ /// </remarks>
+ [ConfigurationProperty(IgnoreUnsignedExtensionsConfigName, DefaultValue = false)]
+ public bool IgnoreUnsignedExtensions {
+ get { return (bool)this[IgnoreUnsignedExtensionsConfigName]; }
+ set { this[IgnoreUnsignedExtensionsConfigName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether identifiers that are both OP Identifiers and Claimed Identifiers
+ /// should ever be recognized as claimed identifiers.
+ /// </summary>
+ /// <value>
+ /// The default value is <c>false</c>, per the OpenID 2.0 spec.
+ /// </value>
+ [ConfigurationProperty(AllowDualPurposeIdentifiersConfigName, DefaultValue = false)]
+ public bool AllowDualPurposeIdentifiers {
+ get { return (bool)this[AllowDualPurposeIdentifiersConfigName]; }
+ set { this[AllowDualPurposeIdentifiersConfigName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether certain Claimed Identifiers that exploit
+ /// features that .NET does not have the ability to send exact HTTP requests for will
+ /// still be allowed by using an approximate HTTP request.
+ /// </summary>
+ /// <value>
+ /// The default value is <c>true</c>.
+ /// </value>
+ [ConfigurationProperty(AllowApproximateIdentifierDiscoveryConfigName, DefaultValue = true)]
+ public bool AllowApproximateIdentifierDiscovery {
+ get { return (bool)this[AllowApproximateIdentifierDiscoveryConfigName]; }
+ set { this[AllowApproximateIdentifierDiscoveryConfigName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the Relying Party should take special care
+ /// to protect users against replay attacks when interoperating with OpenID 1.1 Providers.
+ /// </summary>
+ [ConfigurationProperty(ProtectDownlevelReplayAttacksConfigName, DefaultValue = RelyingPartySecuritySettings.ProtectDownlevelReplayAttacksDefault)]
+ public bool ProtectDownlevelReplayAttacks {
+ get { return (bool)this[ProtectDownlevelReplayAttacksConfigName]; }
+ set { this[ProtectDownlevelReplayAttacksConfigName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the set of trusted OpenID Provider Endpoints.
+ /// </summary>
+ [ConfigurationProperty(TrustedProvidersElementName, IsDefaultCollection = false)]
+ [ConfigurationCollection(typeof(TrustedProviderConfigurationCollection))]
+ public TrustedProviderConfigurationCollection TrustedProviders {
+ get { return (TrustedProviderConfigurationCollection)this[TrustedProvidersElementName] ?? new TrustedProviderConfigurationCollection(); }
+ set { this[TrustedProvidersElementName] = value; }
+ }
+
+ /// <summary>
+ /// Initializes a programmatically manipulatable bag of these security settings with the settings from the config file.
+ /// </summary>
+ /// <returns>The newly created security settings object.</returns>
+ public RelyingPartySecuritySettings CreateSecuritySettings() {
+ Contract.Ensures(Contract.Result<RelyingPartySecuritySettings>() != null);
+
+ RelyingPartySecuritySettings settings = new RelyingPartySecuritySettings();
+ settings.RequireSsl = this.RequireSsl;
+ settings.RequireDirectedIdentity = this.RequireDirectedIdentity;
+ settings.RequireAssociation = this.RequireAssociation;
+ settings.MinimumRequiredOpenIdVersion = this.MinimumRequiredOpenIdVersion;
+ settings.MinimumHashBitLength = this.MinimumHashBitLength;
+ settings.MaximumHashBitLength = this.MaximumHashBitLength;
+ settings.PrivateSecretMaximumAge = DotNetOpenAuthSection.Messaging.PrivateSecretMaximumAge;
+ settings.RejectUnsolicitedAssertions = this.RejectUnsolicitedAssertions;
+ settings.RejectDelegatingIdentifiers = this.RejectDelegatingIdentifiers;
+ settings.IgnoreUnsignedExtensions = this.IgnoreUnsignedExtensions;
+ settings.AllowDualPurposeIdentifiers = this.AllowDualPurposeIdentifiers;
+ settings.AllowApproximateIdentifierDiscovery = this.AllowApproximateIdentifierDiscovery;
+ settings.ProtectDownlevelReplayAttacks = this.ProtectDownlevelReplayAttacks;
+
+ settings.RejectAssertionsFromUntrustedProviders = this.TrustedProviders.RejectAssertionsFromUntrustedProviders;
+ foreach (TrustedProviderEndpointConfigurationElement opEndpoint in this.TrustedProviders) {
+ settings.TrustedProviderEndpoints.Add(opEndpoint.ProviderEndpoint);
+ }
+
+ return settings;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/Configuration/XriResolverElement.cs b/src/DotNetOpenAuth.OpenId/Configuration/XriResolverElement.cs
new file mode 100644
index 0000000..e5dcefb
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/Configuration/XriResolverElement.cs
@@ -0,0 +1,66 @@
+//-----------------------------------------------------------------------
+// <copyright file="XriResolverElement.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Configuration {
+ using System.Configuration;
+
+ /// <summary>
+ /// Represents the &lt;xriResolver&gt; element in the host's .config file.
+ /// </summary>
+ internal class XriResolverElement : ConfigurationElement {
+ /// <summary>
+ /// Gets the name of the @enabled attribute.
+ /// </summary>
+ private const string EnabledAttributeName = "enabled";
+
+ /// <summary>
+ /// The default value for <see cref="Enabled"/>.
+ /// </summary>
+ private const bool EnabledDefaultValue = true;
+
+ /// <summary>
+ /// The name of the &lt;proxy&gt; sub-element.
+ /// </summary>
+ private const string ProxyElementName = "proxy";
+
+ /// <summary>
+ /// The default XRI proxy resolver to use.
+ /// </summary>
+ private static readonly HostNameElement ProxyDefault = new HostNameElement("xri.net");
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="XriResolverElement"/> class.
+ /// </summary>
+ internal XriResolverElement() {
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this XRI resolution is enabled.
+ /// </summary>
+ /// <value>The default value is <c>true</c>.</value>
+ [ConfigurationProperty(EnabledAttributeName, DefaultValue = EnabledDefaultValue)]
+ internal bool Enabled {
+ get { return (bool)this[EnabledAttributeName]; }
+ set { this[EnabledAttributeName] = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the proxy to use for resolving XRIs.
+ /// </summary>
+ /// <value>The default value is "xri.net".</value>
+ [ConfigurationProperty(ProxyElementName)]
+ internal HostNameElement Proxy {
+ get {
+ var host = (HostNameElement)this[ProxyElementName] ?? ProxyDefault;
+ return string.IsNullOrEmpty(host.Name.Trim()) ? ProxyDefault : host;
+ }
+
+ set {
+ this[ProxyElementName] = value;
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/DotNetOpenAuth.OpenId.csproj b/src/DotNetOpenAuth.OpenId/DotNetOpenAuth.OpenId.csproj
new file mode 100644
index 0000000..75bd113
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/DotNetOpenAuth.OpenId.csproj
@@ -0,0 +1,192 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))\EnlistmentInfo.props" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.props))' != '' " />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ </PropertyGroup>
+ <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" />
+ <PropertyGroup>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{3896A32A-E876-4C23-B9B8-78E17D134CD3}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>DotNetOpenAuth</RootNamespace>
+ <AssemblyName>DotNetOpenAuth.OpenId</AssemblyName>
+ </PropertyGroup>
+ <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.Product.props" />
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ </PropertyGroup>
+ <ItemGroup>
+ <Compile Include="Configuration\AssociationTypeCollection.cs" />
+ <Compile Include="Configuration\AssociationTypeElement.cs" />
+ <Compile Include="Configuration\HostMetaDiscoveryElement.cs" />
+ <Compile Include="Configuration\OpenIdElement.cs" />
+ <Compile Include="Configuration\OpenIdProviderElement.cs" />
+ <Compile Include="Configuration\OpenIdProviderSecuritySettingsElement.cs" />
+ <Compile Include="Configuration\OpenIdRelyingPartyElement.cs" />
+ <Compile Include="Configuration\OpenIdRelyingPartySecuritySettingsElement.cs" />
+ <Compile Include="Configuration\XriResolverElement.cs" />
+ <Compile Include="OpenIdXrdsHelperRelyingParty.cs" />
+ <Compile Include="OpenId\Association.cs" />
+ <Compile Include="OpenId\AuthenticationRequestMode.cs" />
+ <Compile Include="OpenId\Behaviors\AXFetchAsSregTransformBase.cs" />
+ <Compile Include="OpenId\Behaviors\BehaviorStrings.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DesignTime>True</DesignTime>
+ <DependentUpon>BehaviorStrings.resx</DependentUpon>
+ </Compile>
+ <Compile Include="OpenId\Behaviors\GsaIcamProfileBase.cs" />
+ <Compile Include="OpenId\ChannelElements\BackwardCompatibilityBindingElement.cs" />
+ <Compile Include="OpenId\ChannelElements\SigningBindingElementContract.cs" />
+ <Compile Include="OpenId\ChannelElements\ExtensionsBindingElement.cs" />
+ <Compile Include="OpenId\ChannelElements\IOpenIdExtensionFactory.cs" />
+ <Compile Include="OpenId\ChannelElements\ITamperResistantOpenIdMessage.cs" />
+ <Compile Include="OpenId\ChannelElements\OriginalStringUriEncoder.cs" />
+ <Compile Include="OpenId\ChannelElements\SigningBindingElement.cs" />
+ <Compile Include="OpenId\ChannelElements\KeyValueFormEncoding.cs" />
+ <Compile Include="OpenId\ChannelElements\OpenIdChannel.cs" />
+ <Compile Include="OpenId\ChannelElements\ReturnToSignatureBindingElement.cs" />
+ <Compile Include="OpenId\ChannelElements\SkipSecurityBindingElement.cs" />
+ <Compile Include="OpenId\AssociationContract.cs" />
+ <Compile Include="OpenId\Extensions\AliasManager.cs" />
+ <Compile Include="OpenId\Extensions\AttributeExchange\AttributeRequest.cs" />
+ <Compile Include="OpenId\Extensions\AttributeExchange\AttributeValues.cs" />
+ <Compile Include="OpenId\Extensions\AttributeExchange\AXAttributeFormats.cs" />
+ <Compile Include="OpenId\Extensions\AttributeExchange\AXUtilities.cs" />
+ <Compile Include="OpenId\Extensions\AttributeExchange\Constants.cs" />
+ <Compile Include="OpenId\Extensions\AttributeExchange\FetchRequest.cs" />
+ <Compile Include="OpenId\Extensions\AttributeExchange\FetchResponse.cs" />
+ <Compile Include="OpenId\Extensions\AttributeExchange\StoreRequest.cs" />
+ <Compile Include="OpenId\Extensions\AttributeExchange\StoreResponse.cs" />
+ <Compile Include="OpenId\Extensions\AttributeExchange\WellKnownAttributes.cs" />
+ <Compile Include="OpenId\Extensions\ExtensionBase.cs" />
+ <Compile Include="OpenId\Extensions\ExtensionArgumentsManager.cs" />
+ <Compile Include="OpenId\Extensions\IClientScriptExtensionResponse.cs" />
+ <Compile Include="OpenId\Extensions\OAuth\AuthorizationRequest.cs" />
+ <Compile Include="OpenId\Extensions\OAuth\AuthorizationApprovedResponse.cs" />
+ <Compile Include="OpenId\Extensions\OAuth\Constants.cs" />
+ <Compile Include="OpenId\Extensions\OAuth\AuthorizationDeclinedResponse.cs" />
+ <Compile Include="OpenId\Extensions\OpenIdExtensionFactoryAggregator.cs" />
+ <Compile Include="OpenId\Extensions\SimpleRegistration\GenderEncoder.cs" />
+ <Compile Include="OpenId\Extensions\StandardOpenIdExtensionFactory.cs" />
+ <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\AuthenticationPolicies.cs" />
+ <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\Constants.cs" />
+ <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\DateTimeEncoder.cs" />
+ <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\NistAssuranceLevel.cs" />
+ <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\PapeUtilities.cs" />
+ <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\PolicyRequest.cs" />
+ <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\PolicyResponse.cs" />
+ <Compile Include="OpenId\Extensions\SimpleRegistration\ClaimsRequest.cs" />
+ <Compile Include="OpenId\Extensions\SimpleRegistration\ClaimsResponse.cs" />
+ <Compile Include="OpenId\Extensions\SimpleRegistration\Constants.cs" />
+ <Compile Include="OpenId\Extensions\SimpleRegistration\DemandLevel.cs" />
+ <Compile Include="OpenId\Extensions\SimpleRegistration\Gender.cs" />
+ <Compile Include="OpenId\Extensions\UI\UIConstants.cs" />
+ <Compile Include="OpenId\Extensions\UI\UIModes.cs" />
+ <Compile Include="OpenId\Extensions\UI\UIRequest.cs" />
+ <Compile Include="OpenId\Extensions\UI\UIUtilities.cs" />
+ <Compile Include="OpenId\Identifier.cs" />
+ <Compile Include="OpenId\IdentifierContract.cs" />
+ <Compile Include="OpenId\Extensions\OpenIdExtensionsInteropHelper.cs" />
+ <Compile Include="OpenId\IdentifierDiscoveryResult.cs" />
+ <Compile Include="OpenId\IIdentifierDiscoveryService.cs" />
+ <Compile Include="OpenId\IdentifierDiscoveryServices.cs" />
+ <Compile Include="OpenId\IOpenIdHost.cs" />
+ <Compile Include="OpenId\IProviderEndpoint.cs" />
+ <Compile Include="OpenId\Provider\IAuthenticationRequest.cs" />
+ <Compile Include="OpenId\Provider\IHostProcessedRequest.cs" />
+ <Compile Include="OpenId\Provider\IProviderBehavior.cs" />
+ <Compile Include="OpenId\Provider\IRequest.cs" />
+ <Compile Include="OpenId\Provider\ProviderSecuritySettings.cs" />
+ <Compile Include="OpenId\Provider\RelyingPartyDiscoveryResult.cs" />
+ <Compile Include="OpenId\RelyingParty\AuthenticationStatus.cs" />
+ <Compile Include="OpenId\RelyingParty\IAuthenticationRequest.cs" />
+ <Compile Include="OpenId\RelyingParty\IAuthenticationRequestContract.cs" />
+ <Compile Include="OpenId\RelyingParty\IAuthenticationResponse.cs" />
+ <Compile Include="OpenId\RelyingParty\IRelyingPartyBehavior.cs" />
+ <Compile Include="OpenId\Messages\CheckAuthenticationRequest.cs" />
+ <Compile Include="OpenId\Messages\CheckAuthenticationResponse.cs" />
+ <Compile Include="OpenId\Messages\CheckIdRequest.cs" />
+ <Compile Include="OpenId\Messages\AssociateSuccessfulResponseContract.cs" />
+ <Compile Include="OpenId\Messages\IErrorMessage.cs" />
+ <Compile Include="OpenId\Messages\IndirectResponseBase.cs" />
+ <Compile Include="OpenId\Messages\IndirectSignedResponse.cs" />
+ <Compile Include="OpenId\Messages\IOpenIdMessageExtension.cs" />
+ <Compile Include="OpenId\Messages\NegativeAssertionResponse.cs" />
+ <Compile Include="OpenId\Messages\PositiveAssertionResponse.cs" />
+ <Compile Include="OpenId\Messages\SignedResponseRequest.cs" />
+ <Compile Include="OpenId\NoDiscoveryIdentifier.cs" />
+ <Compile Include="OpenId\OpenIdUtilities.cs" />
+ <Compile Include="OpenId\OpenIdXrdsHelper.cs" />
+ <Compile Include="OpenId\ProviderEndpointDescription.cs" />
+ <Compile Include="OpenId\Realm.cs" />
+ <Compile Include="OpenId\RelyingPartyEndpointDescription.cs" />
+ <Compile Include="OpenId\DiffieHellmanUtilities.cs" />
+ <Compile Include="OpenId\HmacShaAssociation.cs" />
+ <Compile Include="OpenId\Messages\AssociateUnencryptedRequest.cs" />
+ <Compile Include="OpenId\Messages\AssociateDiffieHellmanRequest.cs" />
+ <Compile Include="OpenId\Messages\AssociateDiffieHellmanResponse.cs" />
+ <Compile Include="OpenId\Messages\AssociateRequest.cs" />
+ <Compile Include="OpenId\Messages\AssociateSuccessfulResponse.cs" />
+ <Compile Include="OpenId\Messages\AssociateUnencryptedResponse.cs" />
+ <Compile Include="OpenId\Messages\AssociateUnsuccessfulResponse.cs" />
+ <Compile Include="OpenId\Messages\IndirectErrorResponse.cs" />
+ <Compile Include="OpenId\Messages\DirectErrorResponse.cs" />
+ <Compile Include="OpenId\Messages\RequestBase.cs" />
+ <Compile Include="OpenId\Messages\DirectResponseBase.cs" />
+ <Compile Include="OpenId\OpenIdStrings.Designer.cs">
+ <DependentUpon>OpenIdStrings.resx</DependentUpon>
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ </Compile>
+ <Compile Include="OpenId\Protocol.cs" />
+ <Compile Include="OpenId\IOpenIdApplicationStore.cs" />
+ <Compile Include="OpenId\RelyingParty\RelyingPartySecuritySettings.cs" />
+ <Compile Include="OpenId\UriDiscoveryService.cs" />
+ <Compile Include="OpenId\XriDiscoveryProxyService.cs" />
+ <Compile Include="OpenId\SecuritySettings.cs" />
+ <Compile Include="OpenId\UriIdentifier.cs" />
+ <Compile Include="OpenId\XriIdentifier.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Xrds\XrdsStrings.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DesignTime>True</DesignTime>
+ <DependentUpon>XrdsStrings.resx</DependentUpon>
+ </Compile>
+ <Compile Include="Yadis\ContentTypes.cs" />
+ <Compile Include="Yadis\DiscoveryResult.cs" />
+ <Compile Include="Yadis\HtmlParser.cs" />
+ <Compile Include="Xrds\ServiceElement.cs" />
+ <Compile Include="Xrds\TypeElement.cs" />
+ <Compile Include="Xrds\UriElement.cs" />
+ <Compile Include="Xrds\XrdElement.cs" />
+ <Compile Include="Xrds\XrdsDocument.cs" />
+ <Compile Include="Xrds\XrdsNode.cs" />
+ <Compile Include="Yadis\Yadis.cs" />
+ <EmbeddedResource Include="OpenId\Behaviors\BehaviorStrings.resx" />
+ <EmbeddedResource Include="OpenId\Behaviors\BehaviorStrings.sr.resx" />
+ <EmbeddedResource Include="OpenId\OpenIdStrings.resx" />
+ <EmbeddedResource Include="OpenId\OpenIdStrings.sr.resx" />
+ <EmbeddedResource Include="Xrds\XrdsStrings.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>XrdsStrings.Designer.cs</LastGenOutput>
+ </EmbeddedResource>
+ <EmbeddedResource Include="Xrds\XrdsStrings.sr.resx" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\DotNetOpenAuth.Core\DotNetOpenAuth.Core.csproj">
+ <Project>{60426312-6AE5-4835-8667-37EDEA670222}</Project>
+ <Name>DotNetOpenAuth.Core</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\Org.Mentalis.Security.Cryptography\Org.Mentalis.Security.Cryptography.csproj">
+ <Project>{26DC877F-5987-48DD-9DDB-E62F2DE0E150}</Project>
+ <Name>Org.Mentalis.Security.Cryptography</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.targets" />
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))\EnlistmentInfo.targets" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))' != '' " />
+</Project> \ No newline at end of file
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Association.cs b/src/DotNetOpenAuth.OpenId/OpenId/Association.cs
new file mode 100644
index 0000000..a0f5bae
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Association.cs
@@ -0,0 +1,307 @@
+//-----------------------------------------------------------------------
+// <copyright file="Association.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId {
+ using System;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.IO;
+ using System.Security.Cryptography;
+ using System.Text;
+ using DotNetOpenAuth.Configuration;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// Stores a secret used in signing and verifying messages.
+ /// </summary>
+ /// <remarks>
+ /// OpenID associations may be shared between Provider and Relying Party (smart
+ /// associations), or be a way for a Provider to recall its own secret for later
+ /// (dumb associations).
+ /// </remarks>
+ [DebuggerDisplay("Handle = {Handle}, Expires = {Expires}")]
+ [ContractVerification(true)]
+ [ContractClass(typeof(AssociationContract))]
+ public abstract class Association {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Association"/> class.
+ /// </summary>
+ /// <param name="handle">The handle.</param>
+ /// <param name="secret">The secret.</param>
+ /// <param name="totalLifeLength">How long the association will be useful.</param>
+ /// <param name="issued">The UTC time of when this association was originally issued by the Provider.</param>
+ protected Association(string handle, byte[] secret, TimeSpan totalLifeLength, DateTime issued) {
+ Requires.NotNullOrEmpty(handle, "handle");
+ Requires.NotNull(secret, "secret");
+ Requires.InRange(totalLifeLength > TimeSpan.Zero, "totalLifeLength");
+ Requires.True(issued.Kind == DateTimeKind.Utc, "issued");
+ Requires.InRange(issued <= DateTime.UtcNow, "issued");
+ Contract.Ensures(this.TotalLifeLength == totalLifeLength);
+
+ this.Handle = handle;
+ this.SecretKey = secret;
+ this.TotalLifeLength = totalLifeLength;
+ this.Issued = OpenIdUtilities.CutToSecond(issued);
+ }
+
+ /// <summary>
+ /// Gets a unique handle by which this <see cref="Association"/> may be stored or retrieved.
+ /// </summary>
+ public string Handle { get; internal set; }
+
+ /// <summary>
+ /// Gets the UTC time when this <see cref="Association"/> will expire.
+ /// </summary>
+ public DateTime Expires {
+ get { return this.Issued + this.TotalLifeLength; }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this <see cref="Association"/> has already expired.
+ /// </summary>
+ public bool IsExpired {
+ get { return this.Expires < DateTime.UtcNow; }
+ }
+
+ /// <summary>
+ /// Gets the length (in bits) of the hash this association creates when signing.
+ /// </summary>
+ public abstract int HashBitLength { get; }
+
+ /// <summary>
+ /// Gets a value indicating whether this instance has useful life remaining.
+ /// </summary>
+ /// <value>
+ /// <c>true</c> if this instance has useful life remaining; otherwise, <c>false</c>.
+ /// </value>
+ internal bool HasUsefulLifeRemaining {
+ get { return this.TimeTillExpiration >= MinimumUsefulAssociationLifetime; }
+ }
+
+ /// <summary>
+ /// Gets or sets the UTC time that this <see cref="Association"/> was first created.
+ /// </summary>
+ [MessagePart]
+ internal DateTime Issued { get; set; }
+
+ /// <summary>
+ /// Gets the duration a secret key used for signing dumb client requests will be good for.
+ /// </summary>
+ protected internal static TimeSpan DumbSecretLifetime {
+ get {
+ Contract.Ensures(Contract.Result<TimeSpan>() > TimeSpan.Zero);
+ return OpenIdElement.Configuration.MaxAuthenticationTime;
+ }
+ }
+
+ /// <summary>
+ /// Gets the number of seconds until this <see cref="Association"/> expires.
+ /// Never negative (counter runs to zero).
+ /// </summary>
+ protected internal long SecondsTillExpiration {
+ get {
+ Contract.Ensures(Contract.Result<long>() >= 0);
+ return Math.Max(0, (long)this.TimeTillExpiration.TotalSeconds);
+ }
+ }
+
+ /// <summary>
+ /// Gets the shared secret key between the consumer and provider.
+ /// </summary>
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "It is a buffer.")]
+ [MessagePart("key")]
+ protected internal byte[] SecretKey { get; private set; }
+
+ /// <summary>
+ /// Gets the lifetime the OpenID provider permits this <see cref="Association"/>.
+ /// </summary>
+ [MessagePart("ttl")]
+ protected TimeSpan TotalLifeLength { get; private set; }
+
+ /// <summary>
+ /// Gets the minimum lifetime an association must still be good for in order for it to be used for a future authentication.
+ /// </summary>
+ /// <remarks>
+ /// Associations that are not likely to last the duration of a user login are not worth using at all.
+ /// </remarks>
+ private static TimeSpan MinimumUsefulAssociationLifetime {
+ get {
+ Contract.Ensures(Contract.Result<TimeSpan>() > TimeSpan.Zero);
+ return OpenIdElement.Configuration.MaxAuthenticationTime;
+ }
+ }
+
+ /// <summary>
+ /// Gets the TimeSpan till this association expires.
+ /// </summary>
+ private TimeSpan TimeTillExpiration {
+ get { return this.Expires - DateTime.UtcNow; }
+ }
+
+ /// <summary>
+ /// Re-instantiates an <see cref="Association"/> previously persisted in a database or some
+ /// other shared store.
+ /// </summary>
+ /// <param name="handle">
+ /// The <see cref="Handle"/> property of the previous <see cref="Association"/> instance.
+ /// </param>
+ /// <param name="expiresUtc">
+ /// The UTC value of the <see cref="Expires"/> property of the previous <see cref="Association"/> instance.
+ /// </param>
+ /// <param name="privateData">
+ /// The byte array returned by a call to <see cref="SerializePrivateData"/> on the previous
+ /// <see cref="Association"/> instance.
+ /// </param>
+ /// <returns>
+ /// The newly dehydrated <see cref="Association"/>, which can be returned
+ /// from a custom association store's
+ /// IRelyingPartyAssociationStore.GetAssociation method.
+ /// </returns>
+ public static Association Deserialize(string handle, DateTime expiresUtc, byte[] privateData) {
+ Requires.NotNullOrEmpty(handle, "handle");
+ Requires.NotNull(privateData, "privateData");
+ Contract.Ensures(Contract.Result<Association>() != null);
+
+ expiresUtc = expiresUtc.ToUniversalTimeSafe();
+ TimeSpan remainingLifeLength = expiresUtc - DateTime.UtcNow;
+ byte[] secret = privateData; // the whole of privateData is the secret key for now.
+ // We figure out what derived type to instantiate based on the length of the secret.
+ try {
+ return HmacShaAssociation.Create(handle, secret, remainingLifeLength);
+ } catch (ArgumentException ex) {
+ throw new ArgumentException(OpenIdStrings.BadAssociationPrivateData, "privateData", ex);
+ }
+ }
+
+ /// <summary>
+ /// Returns private data required to persist this <see cref="Association"/> in
+ /// permanent storage (a shared database for example) for deserialization later.
+ /// </summary>
+ /// <returns>
+ /// An opaque byte array that must be stored and returned exactly as it is provided here.
+ /// The byte array may vary in length depending on the specific type of <see cref="Association"/>,
+ /// but in current versions are no larger than 256 bytes.
+ /// </returns>
+ /// <remarks>
+ /// Values of public properties on the base class <see cref="Association"/> are not included
+ /// in this byte array, as they are useful for fast database lookup and are persisted separately.
+ /// </remarks>
+ public byte[] SerializePrivateData() {
+ Contract.Ensures(Contract.Result<byte[]>() != null);
+
+ // We may want to encrypt this secret using the machine.config private key,
+ // and add data regarding which Association derivative will need to be
+ // re-instantiated on deserialization.
+ // For now, we just send out the secret key. We can derive the type from the length later.
+ byte[] secretKeyCopy = new byte[this.SecretKey.Length];
+ if (this.SecretKey.Length > 0) {
+ this.SecretKey.CopyTo(secretKeyCopy, 0);
+ }
+ return secretKeyCopy;
+ }
+
+ /// <summary>
+ /// Tests equality of two <see cref="Association"/> objects.
+ /// </summary>
+ /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
+ /// <returns>
+ /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
+ /// </returns>
+ public override bool Equals(object obj) {
+ Association a = obj as Association;
+ if (a == null) {
+ return false;
+ }
+ if (a.GetType() != GetType()) {
+ return false;
+ }
+
+ if (a.Handle != this.Handle ||
+ a.Issued != this.Issued ||
+ !MessagingUtilities.Equals(a.TotalLifeLength, this.TotalLifeLength, TimeSpan.FromSeconds(2))) {
+ return false;
+ }
+
+ if (!MessagingUtilities.AreEquivalent(a.SecretKey, this.SecretKey)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Returns the hash code.
+ /// </summary>
+ /// <returns>
+ /// A hash code for the current <see cref="T:System.Object"/>.
+ /// </returns>
+ public override int GetHashCode() {
+ var hmac = HmacAlgorithms.Create(HmacAlgorithms.HmacSha1, this.SecretKey);
+ try {
+ CryptoStream cs = new CryptoStream(Stream.Null, hmac, CryptoStreamMode.Write);
+
+ byte[] hbytes = ASCIIEncoding.ASCII.GetBytes(this.Handle);
+
+ cs.Write(hbytes, 0, hbytes.Length);
+ cs.Close();
+
+ byte[] hash = hmac.Hash;
+ hmac.Clear();
+
+ long val = 0;
+ for (int i = 0; i < hash.Length; i++) {
+ val = val ^ (long)hash[i];
+ }
+
+ val = val ^ this.Expires.ToFileTimeUtc();
+
+ return (int)val;
+ } finally {
+ ((IDisposable)hmac).Dispose();
+ }
+ }
+
+ /// <summary>
+ /// The string to pass as the assoc_type value in the OpenID protocol.
+ /// </summary>
+ /// <param name="protocol">The protocol version of the message that the assoc_type value will be included in.</param>
+ /// <returns>The value that should be used for the openid.assoc_type parameter.</returns>
+ internal abstract string GetAssociationType(Protocol protocol);
+
+ /// <summary>
+ /// Generates a signature from a given blob of data.
+ /// </summary>
+ /// <param name="data">The data to sign. This data will not be changed (the signature is the return value).</param>
+ /// <returns>The calculated signature of the data.</returns>
+ protected internal byte[] Sign(byte[] data) {
+ Requires.NotNull(data, "data");
+ using (HashAlgorithm hasher = this.CreateHasher()) {
+ return hasher.ComputeHash(data);
+ }
+ }
+
+ /// <summary>
+ /// Returns the specific hash algorithm used for message signing.
+ /// </summary>
+ /// <returns>The hash algorithm used for message signing.</returns>
+ protected abstract HashAlgorithm CreateHasher();
+
+#if CONTRACTS_FULL
+ /// <summary>
+ /// Verifies conditions that should be true for any valid state of this object.
+ /// </summary>
+ [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")]
+ [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")]
+ [ContractInvariantMethod]
+ private void ObjectInvariant() {
+ Contract.Invariant(!string.IsNullOrEmpty(this.Handle));
+ Contract.Invariant(this.TotalLifeLength > TimeSpan.Zero);
+ Contract.Invariant(this.SecretKey != null);
+ }
+#endif
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/AssociationContract.cs b/src/DotNetOpenAuth.OpenId/OpenId/AssociationContract.cs
new file mode 100644
index 0000000..e04a332
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/AssociationContract.cs
@@ -0,0 +1,65 @@
+//-----------------------------------------------------------------------
+// <copyright file="AssociationContract.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId {
+ using System;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.IO;
+ using System.Security.Cryptography;
+ using System.Text;
+ using DotNetOpenAuth.Configuration;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// Code contract for the <see cref="Association"/> class.
+ /// </summary>
+ [ContractClassFor(typeof(Association))]
+ internal abstract class AssociationContract : Association {
+ /// <summary>
+ /// Prevents a default instance of the <see cref="AssociationContract"/> class from being created.
+ /// </summary>
+ private AssociationContract()
+ : base(null, null, TimeSpan.Zero, DateTime.Now) {
+ }
+
+ /// <summary>
+ /// Gets the length (in bits) of the hash this association creates when signing.
+ /// </summary>
+ public override int HashBitLength {
+ get {
+ Contract.Ensures(Contract.Result<int>() > 0);
+ throw new NotImplementedException();
+ }
+ }
+
+ /// <summary>
+ /// The string to pass as the assoc_type value in the OpenID protocol.
+ /// </summary>
+ /// <param name="protocol">The protocol version of the message that the assoc_type value will be included in.</param>
+ /// <returns>
+ /// The value that should be used for the openid.assoc_type parameter.
+ /// </returns>
+ [Pure]
+ internal override string GetAssociationType(Protocol protocol) {
+ Requires.NotNull(protocol, "protocol");
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Returns the specific hash algorithm used for message signing.
+ /// </summary>
+ /// <returns>
+ /// The hash algorithm used for message signing.
+ /// </returns>
+ [Pure]
+ protected override HashAlgorithm CreateHasher() {
+ Contract.Ensures(Contract.Result<HashAlgorithm>() != null);
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/AuthenticationRequestMode.cs b/src/DotNetOpenAuth.OpenId/OpenId/AuthenticationRequestMode.cs
new file mode 100644
index 0000000..eb2a4bd
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/AuthenticationRequestMode.cs
@@ -0,0 +1,26 @@
+//-----------------------------------------------------------------------
+// <copyright file="AuthenticationRequestMode.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId {
+ /// <summary>
+ /// Indicates the mode the Provider should use while authenticating the end user.
+ /// </summary>
+ public enum AuthenticationRequestMode {
+ /// <summary>
+ /// The Provider should use whatever credentials are immediately available
+ /// to determine whether the end user owns the Identifier. If sufficient
+ /// credentials (i.e. cookies) are not immediately available, the Provider
+ /// should fail rather than prompt the user.
+ /// </summary>
+ Immediate,
+
+ /// <summary>
+ /// The Provider should determine whether the end user owns the Identifier,
+ /// displaying a web page to the user to login etc., if necessary.
+ /// </summary>
+ Setup,
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Behaviors/AXFetchAsSregTransformBase.cs b/src/DotNetOpenAuth.OpenId/OpenId/Behaviors/AXFetchAsSregTransformBase.cs
new file mode 100644
index 0000000..b5a29b0
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Behaviors/AXFetchAsSregTransformBase.cs
@@ -0,0 +1,37 @@
+//-----------------------------------------------------------------------
+// <copyright file="AXFetchAsSregTransformBase.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Behaviors {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Extensions;
+ using DotNetOpenAuth.OpenId.Extensions.AttributeExchange;
+ using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration;
+
+ /// <summary>
+ /// An Attribute Exchange and Simple Registration filter to make all incoming attribute
+ /// requests look like Simple Registration requests, and to convert the response
+ /// to the originally requested extension and format.
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sreg", Justification = "Abbreviation")]
+ public abstract class AXFetchAsSregTransformBase {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AXFetchAsSregTransformBase"/> class.
+ /// </summary>
+ protected AXFetchAsSregTransformBase() {
+ this.AXFormats = AXAttributeFormats.Common;
+ }
+
+ /// <summary>
+ /// Gets or sets the AX attribute type URI formats this transform is willing to work with.
+ /// </summary>
+ public AXAttributeFormats AXFormats { get; set; }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Behaviors/BehaviorStrings.Designer.cs b/src/DotNetOpenAuth.OpenId/OpenId/Behaviors/BehaviorStrings.Designer.cs
new file mode 100644
index 0000000..8c952ab
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Behaviors/BehaviorStrings.Designer.cs
@@ -0,0 +1,126 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:4.0.30104.0
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Behaviors {
+ using System;
+
+
+ /// <summary>
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ /// </summary>
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class BehaviorStrings {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal BehaviorStrings() {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DotNetOpenAuth.OpenId.Behaviors.BehaviorStrings", typeof(BehaviorStrings).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The PAPE request has an incomplete set of authentication policies..
+ /// </summary>
+ internal static string PapeRequestMissingRequiredPolicies {
+ get {
+ return ResourceManager.GetString("PapeRequestMissingRequiredPolicies", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A PAPE response is missing or is missing required policies..
+ /// </summary>
+ internal static string PapeResponseOrRequiredPoliciesMissing {
+ get {
+ return ResourceManager.GetString("PapeResponseOrRequiredPoliciesMissing", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to No personally identifiable information should be included in authentication responses when the PAPE authentication policy http://www.idmanagement.gov/schema/2009/05/icam/no-pii.pdf is present..
+ /// </summary>
+ internal static string PiiIncludedWithNoPiiPolicy {
+ get {
+ return ResourceManager.GetString("PiiIncludedWithNoPiiPolicy", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to No personally identifiable information should be requested when the http://www.idmanagement.gov/schema/2009/05/icam/no-pii.pdf PAPE policy is present..
+ /// </summary>
+ internal static string PiiRequestedWithNoPiiPolicy {
+ get {
+ return ResourceManager.GetString("PiiRequestedWithNoPiiPolicy", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to No PPID provider has been configured..
+ /// </summary>
+ internal static string PpidProviderNotGiven {
+ get {
+ return ResourceManager.GetString("PpidProviderNotGiven", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Discovery on the Realm URL MUST be performed before sending a positive assertion..
+ /// </summary>
+ internal static string RealmDiscoveryNotPerformed {
+ get {
+ return ResourceManager.GetString("RealmDiscoveryNotPerformed", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The Realm in an authentication request must be an HTTPS URL..
+ /// </summary>
+ internal static string RealmMustBeHttps {
+ get {
+ return ResourceManager.GetString("RealmMustBeHttps", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Behaviors/BehaviorStrings.resx b/src/DotNetOpenAuth.OpenId/OpenId/Behaviors/BehaviorStrings.resx
new file mode 100644
index 0000000..a8bf2d6
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Behaviors/BehaviorStrings.resx
@@ -0,0 +1,141 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="PapeRequestMissingRequiredPolicies" xml:space="preserve">
+ <value>The PAPE request has an incomplete set of authentication policies.</value>
+ </data>
+ <data name="PapeResponseOrRequiredPoliciesMissing" xml:space="preserve">
+ <value>A PAPE response is missing or is missing required policies.</value>
+ </data>
+ <data name="PiiIncludedWithNoPiiPolicy" xml:space="preserve">
+ <value>No personally identifiable information should be included in authentication responses when the PAPE authentication policy http://www.idmanagement.gov/schema/2009/05/icam/no-pii.pdf is present.</value>
+ </data>
+ <data name="PiiRequestedWithNoPiiPolicy" xml:space="preserve">
+ <value>No personally identifiable information should be requested when the http://www.idmanagement.gov/schema/2009/05/icam/no-pii.pdf PAPE policy is present.</value>
+ </data>
+ <data name="PpidProviderNotGiven" xml:space="preserve">
+ <value>No PPID provider has been configured.</value>
+ </data>
+ <data name="RealmDiscoveryNotPerformed" xml:space="preserve">
+ <value>Discovery on the Realm URL MUST be performed before sending a positive assertion.</value>
+ </data>
+ <data name="RealmMustBeHttps" xml:space="preserve">
+ <value>The Realm in an authentication request must be an HTTPS URL.</value>
+ </data>
+</root> \ No newline at end of file
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Behaviors/BehaviorStrings.sr.resx b/src/DotNetOpenAuth.OpenId/OpenId/Behaviors/BehaviorStrings.sr.resx
new file mode 100644
index 0000000..2b1b911
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Behaviors/BehaviorStrings.sr.resx
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="PpidProviderNotGiven" xml:space="preserve">
+ <value>Nijedan PPID provajder nije konfigurisan.</value>
+ </data>
+</root> \ No newline at end of file
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Behaviors/GsaIcamProfileBase.cs b/src/DotNetOpenAuth.OpenId/OpenId/Behaviors/GsaIcamProfileBase.cs
new file mode 100644
index 0000000..c7352c8
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Behaviors/GsaIcamProfileBase.cs
@@ -0,0 +1,58 @@
+//-----------------------------------------------------------------------
+// <copyright file="GsaIcamProfileBase.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Behaviors {
+ using System;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using DotNetOpenAuth.Configuration;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Extensions.AttributeExchange;
+ using DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy;
+ using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration;
+
+ /// <summary>
+ /// Implements the Identity, Credential, &amp; Access Management (ICAM) OpenID 2.0 Profile
+ /// for the General Services Administration (GSA).
+ /// </summary>
+ /// <remarks>
+ /// <para>Relying parties that include this profile are always held to the terms required by the profile,
+ /// but Providers are only affected by the special behaviors of the profile when the RP specifically
+ /// indicates that they want to use this profile. </para>
+ /// </remarks>
+ [Serializable]
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Icam", Justification = "Acronym")]
+ public abstract class GsaIcamProfileBase {
+ /// <summary>
+ /// Backing field for the <see cref="DisableSslRequirement"/> static property.
+ /// </summary>
+ private static bool disableSslRequirement = DotNetOpenAuthSection.Messaging.RelaxSslRequirements;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="GsaIcamProfileBase"/> class.
+ /// </summary>
+ public GsaIcamProfileBase() {
+ if (DisableSslRequirement) {
+ Logger.OpenId.Warn("GSA level 1 behavior has its RequireSsl requirement disabled.");
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether PII is allowed to be requested or received via OpenID.
+ /// </summary>
+ /// <value>The default value is <c>false</c>.</value>
+ public static bool AllowPersonallyIdentifiableInformation { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether to ignore the SSL requirement (for testing purposes only).
+ /// </summary>
+ public static bool DisableSslRequirement { // not an auto-property because it has a default value, and FxCop doesn't want us using static constructors.
+ get { return disableSslRequirement; }
+ set { disableSslRequirement = value; }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/BackwardCompatibilityBindingElement.cs b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/BackwardCompatibilityBindingElement.cs
new file mode 100644
index 0000000..ff8a766
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/BackwardCompatibilityBindingElement.cs
@@ -0,0 +1,129 @@
+//-----------------------------------------------------------------------
+// <copyright file="BackwardCompatibilityBindingElement.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.ChannelElements {
+ using System;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Reflection;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// Provides a mechanism for Relying Parties to work with OpenID 1.0 Providers
+ /// without losing claimed_id and op_endpoint data, which OpenID 2.0 Providers
+ /// are required to send back with positive assertions.
+ /// </summary>
+ internal class BackwardCompatibilityBindingElement : IChannelBindingElement {
+ /// <summary>
+ /// The "dnoa.op_endpoint" callback parameter that stores the Provider Endpoint URL
+ /// to tack onto the return_to URI.
+ /// </summary>
+ private const string ProviderEndpointParameterName = OpenIdUtilities.CustomParameterPrefix + "op_endpoint";
+
+ /// <summary>
+ /// The "dnoa.claimed_id" callback parameter that stores the Claimed Identifier
+ /// to tack onto the return_to URI.
+ /// </summary>
+ private const string ClaimedIdentifierParameterName = OpenIdUtilities.CustomParameterPrefix + "claimed_id";
+
+ #region IChannelBindingElement Members
+
+ /// <summary>
+ /// Gets or sets the channel that this binding element belongs to.
+ /// </summary>
+ /// <value></value>
+ /// <remarks>
+ /// This property is set by the channel when it is first constructed.
+ /// </remarks>
+ public Channel Channel { get; set; }
+
+ /// <summary>
+ /// Gets the protection offered (if any) by this binding element.
+ /// </summary>
+ /// <value><see cref="MessageProtections.None"/></value>
+ public MessageProtections Protection {
+ get { return MessageProtections.None; }
+ }
+
+ /// <summary>
+ /// Prepares a message for sending based on the rules of this channel binding element.
+ /// </summary>
+ /// <param name="message">The message to prepare for sending.</param>
+ /// <returns>
+ /// The protections (if any) that this binding element applied to the message.
+ /// Null if this binding element did not even apply to this binding element.
+ /// </returns>
+ /// <remarks>
+ /// Implementations that provide message protection must honor the
+ /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable.
+ /// </remarks>
+ public MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) {
+ SignedResponseRequest request = message as SignedResponseRequest;
+ if (request != null && request.Version.Major < 2) {
+ request.AddReturnToArguments(ProviderEndpointParameterName, request.Recipient.AbsoluteUri);
+
+ CheckIdRequest authRequest = request as CheckIdRequest;
+ if (authRequest != null) {
+ request.AddReturnToArguments(ClaimedIdentifierParameterName, authRequest.ClaimedIdentifier);
+ }
+
+ return MessageProtections.None;
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Performs any transformation on an incoming message that may be necessary and/or
+ /// validates an incoming message based on the rules of this channel binding element.
+ /// </summary>
+ /// <param name="message">The incoming message to process.</param>
+ /// <returns>
+ /// The protections (if any) that this binding element applied to the message.
+ /// Null if this binding element did not even apply to this binding element.
+ /// </returns>
+ /// <exception cref="ProtocolException">
+ /// Thrown when the binding element rules indicate that this message is invalid and should
+ /// NOT be processed.
+ /// </exception>
+ /// <remarks>
+ /// Implementations that provide message protection must honor the
+ /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable.
+ /// </remarks>
+ public MessageProtections? ProcessIncomingMessage(IProtocolMessage message) {
+ IndirectSignedResponse response = message as IndirectSignedResponse;
+ if (response != null && response.Version.Major < 2) {
+ // GetReturnToArgument may return parameters that are not signed,
+ // but we must allow for that since in OpenID 1.x, a stateless RP has
+ // no way to preserve the provider endpoint and claimed identifier otherwise.
+ // We'll verify the positive assertion later in the
+ // RelyingParty.PositiveAuthenticationResponse constructor anyway.
+ // If this is a 1.0 OP signed response without these parameters then we didn't initiate
+ // the request ,and since 1.0 OPs are not supposed to be able to send unsolicited
+ // assertions it's an invalid case that we throw an exception for.
+ if (response.ProviderEndpoint == null) {
+ string op_endpoint = response.GetReturnToArgument(ProviderEndpointParameterName);
+ ErrorUtilities.VerifyProtocol(op_endpoint != null, MessagingStrings.RequiredParametersMissing, message.GetType().Name, ProviderEndpointParameterName);
+ response.ProviderEndpoint = new Uri(op_endpoint);
+ }
+
+ PositiveAssertionResponse authResponse = response as PositiveAssertionResponse;
+ if (authResponse != null) {
+ if (authResponse.ClaimedIdentifier == null) {
+ string claimedId = response.GetReturnToArgument(ClaimedIdentifierParameterName);
+ ErrorUtilities.VerifyProtocol(claimedId != null, MessagingStrings.RequiredParametersMissing, message.GetType().Name, ClaimedIdentifierParameterName);
+ authResponse.ClaimedIdentifier = claimedId;
+ }
+ }
+
+ return MessageProtections.None;
+ }
+
+ return null;
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/ExtensionsBindingElement.cs b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/ExtensionsBindingElement.cs
new file mode 100644
index 0000000..46d08a3
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/ExtensionsBindingElement.cs
@@ -0,0 +1,251 @@
+//-----------------------------------------------------------------------
+// <copyright file="ExtensionsBindingElement.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Reflection;
+ using DotNetOpenAuth.OpenId.Extensions;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// The binding element that serializes/deserializes OpenID extensions to/from
+ /// their carrying OpenID messages.
+ /// </summary>
+ internal class ExtensionsBindingElement : IChannelBindingElement {
+ /// <summary>
+ /// False if unsigned extensions should be dropped. Must always be true on Providers, since RPs never sign extensions.
+ /// </summary>
+ private readonly bool receiveUnsignedExtensions;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ExtensionsBindingElement"/> class.
+ /// </summary>
+ /// <param name="extensionFactory">The extension factory.</param>
+ /// <param name="securitySettings">The security settings.</param>
+ /// <param name="receiveUnsignedExtensions">Security setting for relying parties. Should be true for Providers.</param>
+ internal ExtensionsBindingElement(IOpenIdExtensionFactory extensionFactory, SecuritySettings securitySettings, bool receiveUnsignedExtensions) {
+ Requires.NotNull(extensionFactory, "extensionFactory");
+ Requires.NotNull(securitySettings, "securitySettings");
+
+ this.ExtensionFactory = extensionFactory;
+ this.receiveUnsignedExtensions = receiveUnsignedExtensions;
+ }
+
+ #region IChannelBindingElement Members
+
+ /// <summary>
+ /// Gets or sets the channel that this binding element belongs to.
+ /// </summary>
+ /// <value></value>
+ /// <remarks>
+ /// This property is set by the channel when it is first constructed.
+ /// </remarks>
+ public Channel Channel { get; set; }
+
+ /// <summary>
+ /// Gets the extension factory.
+ /// </summary>
+ public IOpenIdExtensionFactory ExtensionFactory { get; private set; }
+
+ /// <summary>
+ /// Gets the protection offered (if any) by this binding element.
+ /// </summary>
+ /// <value><see cref="MessageProtections.None"/></value>
+ public MessageProtections Protection {
+ get { return MessageProtections.None; }
+ }
+
+ /// <summary>
+ /// Prepares a message for sending based on the rules of this channel binding element.
+ /// </summary>
+ /// <param name="message">The message to prepare for sending.</param>
+ /// <returns>
+ /// The protections (if any) that this binding element applied to the message.
+ /// Null if this binding element did not even apply to this binding element.
+ /// </returns>
+ /// <remarks>
+ /// Implementations that provide message protection must honor the
+ /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable.
+ /// </remarks>
+ [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "It doesn't look too bad to me. :)")]
+ public MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) {
+ var extendableMessage = message as IProtocolMessageWithExtensions;
+ if (extendableMessage != null) {
+ Protocol protocol = Protocol.Lookup(message.Version);
+ MessageDictionary baseMessageDictionary = this.Channel.MessageDescriptions.GetAccessor(message);
+
+ // We have a helper class that will do all the heavy-lifting of organizing
+ // all the extensions, their aliases, and their parameters.
+ var extensionManager = ExtensionArgumentsManager.CreateOutgoingExtensions(protocol);
+ foreach (IExtensionMessage protocolExtension in extendableMessage.Extensions) {
+ var extension = protocolExtension as IOpenIdMessageExtension;
+ if (extension != null) {
+ Reporting.RecordFeatureUse(protocolExtension);
+
+ // Give extensions that require custom serialization a chance to do their work.
+ var customSerializingExtension = extension as IMessageWithEvents;
+ if (customSerializingExtension != null) {
+ customSerializingExtension.OnSending();
+ }
+
+ // OpenID 2.0 Section 12 forbids two extensions with the same TypeURI in the same message.
+ ErrorUtilities.VerifyProtocol(!extensionManager.ContainsExtension(extension.TypeUri), OpenIdStrings.ExtensionAlreadyAddedWithSameTypeURI, extension.TypeUri);
+
+ // Ensure that we're sending out a valid extension.
+ var extensionDescription = this.Channel.MessageDescriptions.Get(extension);
+ var extensionDictionary = extensionDescription.GetDictionary(extension).Serialize();
+ extensionDescription.EnsureMessagePartsPassBasicValidation(extensionDictionary);
+
+ // Add the extension to the outgoing message payload.
+ extensionManager.AddExtensionArguments(extension.TypeUri, extensionDictionary);
+ } else {
+ Logger.OpenId.WarnFormat("Unexpected extension type {0} did not implement {1}.", protocolExtension.GetType(), typeof(IOpenIdMessageExtension).Name);
+ }
+ }
+
+ // We use a cheap trick (for now at least) to determine whether the 'openid.' prefix
+ // belongs on the parameters by just looking at what other parameters do.
+ // Technically, direct message responses from Provider to Relying Party are the only
+ // messages that leave off the 'openid.' prefix.
+ bool includeOpenIdPrefix = baseMessageDictionary.Keys.Any(key => key.StartsWith(protocol.openid.Prefix, StringComparison.Ordinal));
+
+ // Add the extension parameters to the base message for transmission.
+ baseMessageDictionary.AddExtraParameters(extensionManager.GetArgumentsToSend(includeOpenIdPrefix));
+ return MessageProtections.None;
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Performs any transformation on an incoming message that may be necessary and/or
+ /// validates an incoming message based on the rules of this channel binding element.
+ /// </summary>
+ /// <param name="message">The incoming message to process.</param>
+ /// <returns>
+ /// The protections (if any) that this binding element applied to the message.
+ /// Null if this binding element did not even apply to this binding element.
+ /// </returns>
+ /// <exception cref="ProtocolException">
+ /// Thrown when the binding element rules indicate that this message is invalid and should
+ /// NOT be processed.
+ /// </exception>
+ /// <remarks>
+ /// Implementations that provide message protection must honor the
+ /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable.
+ /// </remarks>
+ public MessageProtections? ProcessIncomingMessage(IProtocolMessage message) {
+ var extendableMessage = message as IProtocolMessageWithExtensions;
+ if (extendableMessage != null) {
+ // First add the extensions that are signed by the Provider.
+ foreach (IOpenIdMessageExtension signedExtension in this.GetExtensions(extendableMessage, true, null)) {
+ Reporting.RecordFeatureUse(signedExtension);
+ signedExtension.IsSignedByRemoteParty = true;
+ extendableMessage.Extensions.Add(signedExtension);
+ }
+
+ // Now search again, considering ALL extensions whether they are signed or not,
+ // skipping the signed ones and adding the new ones as unsigned extensions.
+ if (this.receiveUnsignedExtensions) {
+ Func<string, bool> isNotSigned = typeUri => !extendableMessage.Extensions.Cast<IOpenIdMessageExtension>().Any(ext => ext.TypeUri == typeUri);
+ foreach (IOpenIdMessageExtension unsignedExtension in this.GetExtensions(extendableMessage, false, isNotSigned)) {
+ Reporting.RecordFeatureUse(unsignedExtension);
+ unsignedExtension.IsSignedByRemoteParty = false;
+ extendableMessage.Extensions.Add(unsignedExtension);
+ }
+ }
+
+ return MessageProtections.None;
+ }
+
+ return null;
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Gets the extensions on a message.
+ /// </summary>
+ /// <param name="message">The carrier of the extensions.</param>
+ /// <param name="ignoreUnsigned">If set to <c>true</c> only signed extensions will be available.</param>
+ /// <param name="extensionFilter">A optional filter that takes an extension type URI and
+ /// returns a value indicating whether that extension should be deserialized and
+ /// returned in the sequence. May be null.</param>
+ /// <returns>A sequence of extensions in the message.</returns>
+ private IEnumerable<IOpenIdMessageExtension> GetExtensions(IProtocolMessageWithExtensions message, bool ignoreUnsigned, Func<string, bool> extensionFilter) {
+ bool isAtProvider = message is SignedResponseRequest;
+
+ // We have a helper class that will do all the heavy-lifting of organizing
+ // all the extensions, their aliases, and their parameters.
+ var extensionManager = ExtensionArgumentsManager.CreateIncomingExtensions(this.GetExtensionsDictionary(message, ignoreUnsigned));
+ foreach (string typeUri in extensionManager.GetExtensionTypeUris()) {
+ // Our caller may have already obtained a signed version of this extension,
+ // so skip it if they don't want this one.
+ if (extensionFilter != null && !extensionFilter(typeUri)) {
+ continue;
+ }
+
+ var extensionData = extensionManager.GetExtensionArguments(typeUri);
+
+ // Initialize this particular extension.
+ IOpenIdMessageExtension extension = this.ExtensionFactory.Create(typeUri, extensionData, message, isAtProvider);
+ if (extension != null) {
+ try {
+ // Make sure the extension fulfills spec requirements before deserializing it.
+ MessageDescription messageDescription = this.Channel.MessageDescriptions.Get(extension);
+ messageDescription.EnsureMessagePartsPassBasicValidation(extensionData);
+
+ // Deserialize the extension.
+ MessageDictionary extensionDictionary = messageDescription.GetDictionary(extension);
+ foreach (var pair in extensionData) {
+ extensionDictionary[pair.Key] = pair.Value;
+ }
+
+ // Give extensions that require custom serialization a chance to do their work.
+ var customSerializingExtension = extension as IMessageWithEvents;
+ if (customSerializingExtension != null) {
+ customSerializingExtension.OnReceiving();
+ }
+ } catch (ProtocolException ex) {
+ Logger.OpenId.ErrorFormat(OpenIdStrings.BadExtension, extension.GetType(), ex);
+ extension = null;
+ }
+
+ if (extension != null) {
+ yield return extension;
+ }
+ } else {
+ Logger.OpenId.DebugFormat("Extension with type URI '{0}' ignored because it is not a recognized extension.", typeUri);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets the dictionary of message parts that should be deserialized into extensions.
+ /// </summary>
+ /// <param name="message">The message.</param>
+ /// <param name="ignoreUnsigned">If set to <c>true</c> only signed extensions will be available.</param>
+ /// <returns>
+ /// A dictionary of message parts, including only signed parts when appropriate.
+ /// </returns>
+ private IDictionary<string, string> GetExtensionsDictionary(IProtocolMessage message, bool ignoreUnsigned) {
+ Requires.ValidState(this.Channel != null);
+
+ IndirectSignedResponse signedResponse = message as IndirectSignedResponse;
+ if (signedResponse != null && ignoreUnsigned) {
+ return signedResponse.GetSignedMessageParts(this.Channel);
+ } else {
+ return this.Channel.MessageDescriptions.GetAccessor(message);
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/IOpenIdExtensionFactory.cs b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/IOpenIdExtensionFactory.cs
new file mode 100644
index 0000000..6c47ab5
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/IOpenIdExtensionFactory.cs
@@ -0,0 +1,48 @@
+//-----------------------------------------------------------------------
+// <copyright file="IOpenIdExtensionFactory.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.ChannelElements {
+ using System.Collections.Generic;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// OpenID extension factory class for creating extensions based on received Type URIs.
+ /// </summary>
+ /// <remarks>
+ /// OpenID extension factories must be registered with the library. This can be
+ /// done by adding a factory to OpenIdRelyingParty.ExtensionFactories
+ /// or OpenIdProvider.ExtensionFactories, or by adding a snippet
+ /// such as the following to your web.config file:
+ /// <example>
+ /// &lt;dotNetOpenAuth&gt;
+ /// &lt;openid&gt;
+ /// &lt;extensionFactories&gt;
+ /// &lt;add type="DotNetOpenAuth.ApplicationBlock.CustomExtensions.Acme, DotNetOpenAuth.ApplicationBlock" /&gt;
+ /// &lt;/extensionFactories&gt;
+ /// &lt;/openid&gt;
+ /// &lt;/dotNetOpenAuth&gt;
+ /// </example>
+ /// </remarks>
+ public interface IOpenIdExtensionFactory {
+ /// <summary>
+ /// Creates a new instance of some extension based on the received extension parameters.
+ /// </summary>
+ /// <param name="typeUri">The type URI of the extension.</param>
+ /// <param name="data">The parameters associated specifically with this extension.</param>
+ /// <param name="baseMessage">The OpenID message carrying this extension.</param>
+ /// <param name="isProviderRole">A value indicating whether this extension is being received at the OpenID Provider.</param>
+ /// <returns>
+ /// An instance of <see cref="IOpenIdMessageExtension"/> if the factory recognizes
+ /// the extension described in the input parameters; <c>null</c> otherwise.
+ /// </returns>
+ /// <remarks>
+ /// This factory method need only initialize properties in the instantiated extension object
+ /// that are not bound using <see cref="MessagePartAttribute"/>.
+ /// </remarks>
+ IOpenIdMessageExtension Create(string typeUri, IDictionary<string, string> data, IProtocolMessageWithExtensions baseMessage, bool isProviderRole);
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/ITamperResistantOpenIdMessage.cs b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/ITamperResistantOpenIdMessage.cs
new file mode 100644
index 0000000..fb8c445
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/ITamperResistantOpenIdMessage.cs
@@ -0,0 +1,46 @@
+//-----------------------------------------------------------------------
+// <copyright file="ITamperResistantOpenIdMessage.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
+
+ /// <summary>
+ /// An interface that OAuth messages implement to support signing.
+ /// </summary>
+ internal interface ITamperResistantOpenIdMessage : ITamperResistantProtocolMessage, IReplayProtectedProtocolMessage {
+ /// <summary>
+ /// Gets or sets the association handle used to sign the message.
+ /// </summary>
+ /// <value>The handle for the association that was used to sign this assertion. </value>
+ string AssociationHandle { get; set; }
+
+ /// <summary>
+ /// Gets or sets the association handle that the Provider wants the Relying Party to not use any more.
+ /// </summary>
+ /// <value>If the Relying Party sent an invalid association handle with the request, it SHOULD be included here.</value>
+ string InvalidateHandle { get; set; }
+
+ /// <summary>
+ /// Gets or sets the signed parameter order.
+ /// </summary>
+ /// <value>Comma-separated list of signed fields.</value>
+ /// <example>"op_endpoint,identity,claimed_id,return_to,assoc_handle,response_nonce"</example>
+ /// <remarks>
+ /// This entry consists of the fields without the "openid." prefix that the signature covers.
+ /// This list MUST contain at least "op_endpoint", "return_to" "response_nonce" and "assoc_handle",
+ /// and if present in the response, "claimed_id" and "identity".
+ /// Additional keys MAY be signed as part of the message. See Generating Signatures.
+ /// </remarks>
+ [SuppressMessage("Microsoft.StyleCop.CSharp.DocumentationRules", "SA1630:DocumentationTextMustContainWhitespace", Justification = "The samples are string literals.")]
+ string SignedParameterOrder { get; set; } // TODO: make sure we have a unit test to verify that an incoming message with fewer signed fields than required will be rejected.
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/KeyValueFormEncoding.cs b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/KeyValueFormEncoding.cs
new file mode 100644
index 0000000..cdd1085
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/KeyValueFormEncoding.cs
@@ -0,0 +1,169 @@
+//-----------------------------------------------------------------------
+// <copyright file="KeyValueFormEncoding.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.IO;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// Indicates the level of strictness to require when decoding a
+ /// Key-Value Form encoded dictionary.
+ /// </summary>
+ public enum KeyValueFormConformanceLevel {
+ /// <summary>
+ /// Be as forgiving as possible to errors made while encoding.
+ /// </summary>
+ Loose,
+
+ /// <summary>
+ /// Allow for certain errors in encoding attributable to ambiguities
+ /// in the OpenID 1.1 spec's description of the encoding.
+ /// </summary>
+ OpenId11,
+
+ /// <summary>
+ /// The strictest mode. The decoder requires the encoded dictionary
+ /// to be in strict compliance with OpenID 2.0's description of
+ /// the encoding.
+ /// </summary>
+ OpenId20,
+ }
+
+ /// <summary>
+ /// Performs conversion to and from the Key-Value Form Encoding defined by
+ /// OpenID Authentication 2.0 section 4.1.1.
+ /// http://openid.net/specs/openid-authentication-2_0.html#anchor4
+ /// </summary>
+ /// <remarks>
+ /// This class is thread safe and immutable.
+ /// </remarks>
+ internal class KeyValueFormEncoding {
+ /// <summary>
+ /// Characters that must not appear in parameter names.
+ /// </summary>
+ private static readonly char[] IllegalKeyCharacters = { '\n', ':' };
+
+ /// <summary>
+ /// Characters that must not appaer in parameter values.
+ /// </summary>
+ private static readonly char[] IllegalValueCharacters = { '\n' };
+
+ /// <summary>
+ /// The newline character sequence to use.
+ /// </summary>
+ private const string NewLineCharacters = "\n";
+
+ /// <summary>
+ /// The character encoding to use.
+ /// </summary>
+ private static readonly Encoding textEncoding = new UTF8Encoding(false);
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="KeyValueFormEncoding"/> class.
+ /// </summary>
+ public KeyValueFormEncoding() {
+ this.ConformanceLevel = KeyValueFormConformanceLevel.Loose;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="KeyValueFormEncoding"/> class.
+ /// </summary>
+ /// <param name="conformanceLevel">How strictly an incoming Key-Value Form message will be held to the spec.</param>
+ public KeyValueFormEncoding(KeyValueFormConformanceLevel conformanceLevel) {
+ this.ConformanceLevel = conformanceLevel;
+ }
+
+ /// <summary>
+ /// Gets a value controlling how strictly an incoming Key-Value Form message will be held to the spec.
+ /// </summary>
+ public KeyValueFormConformanceLevel ConformanceLevel { get; private set; }
+
+ /// <summary>
+ /// Encodes key/value pairs to Key-Value Form.
+ /// </summary>
+ /// <param name="keysAndValues">
+ /// The dictionary of key/value pairs to convert to a byte stream.
+ /// </param>
+ /// <returns>The UTF8 byte array.</returns>
+ /// <remarks>
+ /// Enumerating a Dictionary&lt;TKey, TValue&gt; has undeterministic ordering.
+ /// If ordering of the key=value pairs is important, a deterministic enumerator must
+ /// be used.
+ /// </remarks>
+ [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Not a problem for this type.")]
+ public static byte[] GetBytes(IEnumerable<KeyValuePair<string, string>> keysAndValues) {
+ Requires.NotNull(keysAndValues, "keysAndValues");
+
+ using (MemoryStream ms = new MemoryStream()) {
+ using (StreamWriter sw = new StreamWriter(ms, textEncoding)) {
+ sw.NewLine = NewLineCharacters;
+ foreach (var pair in keysAndValues) {
+ if (pair.Key.IndexOfAny(IllegalKeyCharacters) >= 0) {
+ throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, OpenIdStrings.InvalidCharacterInKeyValueFormInput, pair.Key));
+ }
+ if (pair.Value.IndexOfAny(IllegalValueCharacters) >= 0) {
+ throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, OpenIdStrings.InvalidCharacterInKeyValueFormInput, pair.Value));
+ }
+
+ sw.Write(pair.Key);
+ sw.Write(':');
+ sw.Write(pair.Value);
+ sw.WriteLine();
+ }
+ }
+
+ return ms.ToArray();
+ }
+ }
+
+ /// <summary>
+ /// Decodes bytes in Key-Value Form to key/value pairs.
+ /// </summary>
+ /// <param name="data">The stream of Key-Value Form encoded bytes.</param>
+ /// <returns>The deserialized dictionary.</returns>
+ /// <exception cref="FormatException">Thrown when the data is not in the expected format.</exception>
+ public IDictionary<string, string> GetDictionary(Stream data) {
+ using (StreamReader reader = new StreamReader(data, textEncoding)) {
+ var dict = new Dictionary<string, string>();
+ int line_num = 0;
+ string line;
+ while ((line = reader.ReadLine()) != null) {
+ line_num++;
+ if (this.ConformanceLevel == KeyValueFormConformanceLevel.Loose) {
+ line = line.Trim();
+ if (line.Length == 0) {
+ continue;
+ }
+ }
+ string[] parts = line.Split(new[] { ':' }, 2);
+ ErrorUtilities.VerifyFormat(parts.Length == 2, OpenIdStrings.InvalidKeyValueFormCharacterMissing, ':', line_num, line);
+ if (this.ConformanceLevel > KeyValueFormConformanceLevel.Loose) {
+ ErrorUtilities.VerifyFormat(!(char.IsWhiteSpace(parts[0], parts[0].Length - 1) || char.IsWhiteSpace(parts[1], 0)), OpenIdStrings.InvalidCharacterInKeyValueFormInput, ' ', line_num, line);
+ }
+ if (this.ConformanceLevel < KeyValueFormConformanceLevel.OpenId20) {
+ parts[0] = parts[0].Trim();
+ parts[1] = parts[1].Trim();
+ }
+
+ // calling Add method will throw if a key is encountered twice,
+ // which we should do.
+ dict.Add(parts[0], parts[1]);
+ }
+ if (this.ConformanceLevel > KeyValueFormConformanceLevel.Loose) {
+ reader.BaseStream.Seek(-1, SeekOrigin.End);
+ ErrorUtilities.VerifyFormat(reader.BaseStream.ReadByte() == '\n', OpenIdStrings.InvalidKeyValueFormCharacterMissing, "\\n", line_num, line);
+ }
+ return dict;
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/OpenIdChannel.cs b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/OpenIdChannel.cs
new file mode 100644
index 0000000..6b88b3f
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/OpenIdChannel.cs
@@ -0,0 +1,229 @@
+//-----------------------------------------------------------------------
+// <copyright file="OpenIdChannel.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.IO;
+ using System.Linq;
+ using System.Net;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
+ using DotNetOpenAuth.OpenId.Extensions;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// A channel that knows how to send and receive OpenID messages.
+ /// </summary>
+ [ContractVerification(true)]
+ internal class OpenIdChannel : Channel {
+ /// <summary>
+ /// The HTTP Content-Type to use in Key-Value Form responses.
+ /// </summary>
+ /// <remarks>
+ /// OpenID 2.0 section 5.1.2 says this SHOULD be text/plain. But this value
+ /// does not prevent free hosters like GoDaddy from tacking on their ads
+ /// to the end of the direct response, corrupting the data. So we deviate
+ /// from the spec a bit here to improve the story for free Providers.
+ /// </remarks>
+ internal const string KeyValueFormContentType = "application/x-openid-kvf";
+
+ /// <summary>
+ /// The encoder that understands how to read and write Key-Value Form.
+ /// </summary>
+ private KeyValueFormEncoding keyValueForm = new KeyValueFormEncoding();
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OpenIdChannel"/> class.
+ /// </summary>
+ /// <param name="messageTypeProvider">A class prepared to analyze incoming messages and indicate what concrete
+ /// message types can deserialize from it.</param>
+ /// <param name="bindingElements">The binding elements to use in sending and receiving messages.</param>
+ protected OpenIdChannel(IMessageFactory messageTypeProvider, IChannelBindingElement[] bindingElements)
+ : base(messageTypeProvider, bindingElements) {
+ Requires.NotNull(messageTypeProvider, "messageTypeProvider");
+
+ // Customize the binding element order, since we play some tricks for higher
+ // security and backward compatibility with older OpenID versions.
+ var outgoingBindingElements = new List<IChannelBindingElement>(bindingElements);
+ var incomingBindingElements = new List<IChannelBindingElement>(bindingElements);
+ incomingBindingElements.Reverse();
+
+ // Customize the order of the incoming elements by moving the return_to elements in front.
+ var backwardCompatibility = incomingBindingElements.OfType<BackwardCompatibilityBindingElement>().SingleOrDefault();
+ var returnToSign = incomingBindingElements.OfType<ReturnToSignatureBindingElement>().SingleOrDefault();
+ if (backwardCompatibility != null) {
+ incomingBindingElements.MoveTo(0, backwardCompatibility);
+ }
+ if (returnToSign != null) {
+ // Yes, this is intentionally, shifting the backward compatibility
+ // binding element to second position.
+ incomingBindingElements.MoveTo(0, returnToSign);
+ }
+
+ this.CustomizeBindingElementOrder(outgoingBindingElements, incomingBindingElements);
+
+ // Change out the standard web request handler to reflect the standard
+ // OpenID pattern that outgoing web requests are to unknown and untrusted
+ // servers on the Internet.
+ this.WebRequestHandler = new UntrustedWebRequestHandler();
+ }
+
+ /// <summary>
+ /// Verifies the integrity and applicability of an incoming message.
+ /// </summary>
+ /// <param name="message">The message just received.</param>
+ /// <exception cref="ProtocolException">
+ /// Thrown when the message is somehow invalid, except for check_authentication messages.
+ /// This can be due to tampering, replay attack or expiration, among other things.
+ /// </exception>
+ protected override void ProcessIncomingMessage(IProtocolMessage message) {
+ var checkAuthRequest = message as CheckAuthenticationRequest;
+ if (checkAuthRequest != null) {
+ IndirectSignedResponse originalResponse = new IndirectSignedResponse(checkAuthRequest, this);
+ try {
+ base.ProcessIncomingMessage(originalResponse);
+ checkAuthRequest.IsValid = true;
+ } catch (ProtocolException) {
+ checkAuthRequest.IsValid = false;
+ }
+ } else {
+ base.ProcessIncomingMessage(message);
+ }
+
+ // Convert an OpenID indirect error message, which we never expect
+ // between two good OpenID implementations, into an exception.
+ // We don't process DirectErrorResponse because associate negotiations
+ // commonly get a derivative of that message type and handle it.
+ var errorMessage = message as IndirectErrorResponse;
+ if (errorMessage != null) {
+ string exceptionMessage = string.Format(
+ CultureInfo.CurrentCulture,
+ OpenIdStrings.IndirectErrorFormattedMessage,
+ errorMessage.ErrorMessage,
+ errorMessage.Contact,
+ errorMessage.Reference);
+ throw new ProtocolException(exceptionMessage, message);
+ }
+ }
+
+ /// <summary>
+ /// Prepares an HTTP request that carries a given message.
+ /// </summary>
+ /// <param name="request">The message to send.</param>
+ /// <returns>
+ /// The <see cref="HttpWebRequest"/> prepared to send the request.
+ /// </returns>
+ protected override HttpWebRequest CreateHttpRequest(IDirectedProtocolMessage request) {
+ return this.InitializeRequestAsPost(request);
+ }
+
+ /// <summary>
+ /// Gets the protocol message that may be in the given HTTP response.
+ /// </summary>
+ /// <param name="response">The response that is anticipated to contain an protocol message.</param>
+ /// <returns>
+ /// The deserialized message parts, if found. Null otherwise.
+ /// </returns>
+ /// <exception cref="ProtocolException">Thrown when the response is not valid.</exception>
+ protected override IDictionary<string, string> ReadFromResponseCore(IncomingWebResponse response) {
+ try {
+ return this.keyValueForm.GetDictionary(response.ResponseStream);
+ } catch (FormatException ex) {
+ throw ErrorUtilities.Wrap(ex, ex.Message);
+ }
+ }
+
+ /// <summary>
+ /// Called when receiving a direct response message, before deserialization begins.
+ /// </summary>
+ /// <param name="response">The HTTP direct response.</param>
+ /// <param name="message">The newly instantiated message, prior to deserialization.</param>
+ protected override void OnReceivingDirectResponse(IncomingWebResponse response, IDirectResponseProtocolMessage message) {
+ base.OnReceivingDirectResponse(response, message);
+
+ // Verify that the expected HTTP status code was used for the message,
+ // per OpenID 2.0 section 5.1.2.2.
+ // Note: The v1.1 spec doesn't require 400 responses for some error messages
+ if (message.Version.Major >= 2) {
+ var httpDirectResponse = message as IHttpDirectResponse;
+ if (httpDirectResponse != null) {
+ ErrorUtilities.VerifyProtocol(
+ httpDirectResponse.HttpStatusCode == response.Status,
+ MessagingStrings.UnexpectedHttpStatusCode,
+ (int)httpDirectResponse.HttpStatusCode,
+ (int)response.Status);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Queues a message for sending in the response stream where the fields
+ /// are sent in the response stream in querystring style.
+ /// </summary>
+ /// <param name="response">The message to send as a response.</param>
+ /// <returns>
+ /// The pending user agent redirect based message to be sent as an HttpResponse.
+ /// </returns>
+ /// <remarks>
+ /// This method implements spec V1.0 section 5.3.
+ /// </remarks>
+ protected override OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response) {
+ var messageAccessor = this.MessageDescriptions.GetAccessor(response);
+ var fields = messageAccessor.Serialize();
+ byte[] keyValueEncoding = KeyValueFormEncoding.GetBytes(fields);
+
+ OutgoingWebResponse preparedResponse = new OutgoingWebResponse();
+ ApplyMessageTemplate(response, preparedResponse);
+ preparedResponse.Headers.Add(HttpResponseHeader.ContentType, KeyValueFormContentType);
+ preparedResponse.OriginalMessage = response;
+ preparedResponse.ResponseStream = new MemoryStream(keyValueEncoding);
+
+ IHttpDirectResponse httpMessage = response as IHttpDirectResponse;
+ if (httpMessage != null) {
+ preparedResponse.Status = httpMessage.HttpStatusCode;
+ }
+
+ return preparedResponse;
+ }
+
+ /// <summary>
+ /// Gets the direct response of a direct HTTP request.
+ /// </summary>
+ /// <param name="webRequest">The web request.</param>
+ /// <returns>The response to the web request.</returns>
+ /// <exception cref="ProtocolException">Thrown on network or protocol errors.</exception>
+ protected override IncomingWebResponse GetDirectResponse(HttpWebRequest webRequest) {
+ IncomingWebResponse response = this.WebRequestHandler.GetResponse(webRequest, DirectWebRequestOptions.AcceptAllHttpResponses);
+
+ // Filter the responses to the allowable set of HTTP status codes.
+ if (response.Status != HttpStatusCode.OK && response.Status != HttpStatusCode.BadRequest) {
+ if (Logger.Channel.IsErrorEnabled) {
+ using (var reader = new StreamReader(response.ResponseStream)) {
+ Logger.Channel.ErrorFormat(
+ "Unexpected HTTP status code {0} {1} received in direct response:{2}{3}",
+ (int)response.Status,
+ response.Status,
+ Environment.NewLine,
+ reader.ReadToEnd());
+ }
+ }
+
+ // Call dispose before throwing since we're not including the response in the
+ // exception we're throwing.
+ response.Dispose();
+
+ ErrorUtilities.ThrowProtocol(OpenIdStrings.UnexpectedHttpStatusCode, (int)response.Status, response.Status);
+ }
+
+ return response;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/OriginalStringUriEncoder.cs b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/OriginalStringUriEncoder.cs
new file mode 100644
index 0000000..9496e66
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/OriginalStringUriEncoder.cs
@@ -0,0 +1,47 @@
+//-----------------------------------------------------------------------
+// <copyright file="OriginalStringUriEncoder.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging.Reflection;
+
+ /// <summary>
+ /// A Uri encoder that serializes using <see cref="Uri.OriginalString"/>
+ /// rather than the standard <see cref="Uri.AbsoluteUri"/>.
+ /// </summary>
+ internal class OriginalStringUriEncoder : IMessagePartEncoder {
+ #region IMessagePartEncoder Members
+
+ /// <summary>
+ /// Encodes the specified value.
+ /// </summary>
+ /// <param name="value">The value. Guaranteed to never be null.</param>
+ /// <returns>
+ /// The <paramref name="value"/> in string form, ready for message transport.
+ /// </returns>
+ public string Encode(object value) {
+ Uri uriValue = (Uri)value;
+ return uriValue != null ? uriValue.OriginalString : null;
+ }
+
+ /// <summary>
+ /// Decodes the specified value.
+ /// </summary>
+ /// <param name="value">The string value carried by the transport. Guaranteed to never be null, although it may be empty.</param>
+ /// <returns>
+ /// The deserialized form of the given string.
+ /// </returns>
+ /// <exception cref="FormatException">Thrown when the string value given cannot be decoded into the required object type.</exception>
+ public object Decode(string value) {
+ return value != null ? new Uri(value) : null;
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs
new file mode 100644
index 0000000..fa7768b
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs
@@ -0,0 +1,210 @@
+//-----------------------------------------------------------------------
+// <copyright file="ReturnToSignatureBindingElement.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.Specialized;
+ using System.Diagnostics.Contracts;
+ using System.Security.Cryptography;
+ using System.Web;
+ using DotNetOpenAuth.Configuration;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// This binding element signs a Relying Party's openid.return_to parameter
+ /// so that upon return, it can verify that it hasn't been tampered with.
+ /// </summary>
+ /// <remarks>
+ /// <para>Since Providers can send unsolicited assertions, not all openid.return_to
+ /// values will be signed. But those that are signed will be validated, and
+ /// any invalid or missing signatures will cause this library to not trust
+ /// the parameters in the return_to URL.</para>
+ /// <para>In the messaging stack, this binding element looks like an ordinary
+ /// transform-type of binding element rather than a protection element,
+ /// due to its required order in the channel stack and that it doesn't sign
+ /// anything except a particular message part.</para>
+ /// </remarks>
+ internal class ReturnToSignatureBindingElement : IChannelBindingElement {
+ /// <summary>
+ /// The name of the callback parameter we'll tack onto the return_to value
+ /// to store our signature on the return_to parameter.
+ /// </summary>
+ private const string ReturnToSignatureParameterName = OpenIdUtilities.CustomParameterPrefix + "return_to_sig";
+
+ /// <summary>
+ /// The name of the callback parameter we'll tack onto the return_to value
+ /// to store the handle of the association we use to sign the return_to parameter.
+ /// </summary>
+ private const string ReturnToSignatureHandleParameterName = OpenIdUtilities.CustomParameterPrefix + "return_to_sig_handle";
+
+ /// <summary>
+ /// The URI to use for private associations at this RP.
+ /// </summary>
+ private static readonly Uri SecretUri = new Uri("https://localhost/dnoa/secret");
+
+ /// <summary>
+ /// The key store used to generate the private signature on the return_to parameter.
+ /// </summary>
+ private ICryptoKeyStore cryptoKeyStore;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ReturnToSignatureBindingElement"/> class.
+ /// </summary>
+ /// <param name="cryptoKeyStore">The crypto key store.</param>
+ internal ReturnToSignatureBindingElement(ICryptoKeyStore cryptoKeyStore) {
+ Requires.NotNull(cryptoKeyStore, "cryptoKeyStore");
+
+ this.cryptoKeyStore = cryptoKeyStore;
+ }
+
+ #region IChannelBindingElement Members
+
+ /// <summary>
+ /// Gets or sets the channel that this binding element belongs to.
+ /// </summary>
+ /// <value></value>
+ /// <remarks>
+ /// This property is set by the channel when it is first constructed.
+ /// </remarks>
+ public Channel Channel { get; set; }
+
+ /// <summary>
+ /// Gets the protection offered (if any) by this binding element.
+ /// </summary>
+ /// <value><see cref="MessageProtections.None"/></value>
+ /// <remarks>
+ /// No message protection is reported because this binding element
+ /// does not protect the entire message -- only a part.
+ /// </remarks>
+ public MessageProtections Protection {
+ get { return MessageProtections.None; }
+ }
+
+ /// <summary>
+ /// Prepares a message for sending based on the rules of this channel binding element.
+ /// </summary>
+ /// <param name="message">The message to prepare for sending.</param>
+ /// <returns>
+ /// The protections (if any) that this binding element applied to the message.
+ /// Null if this binding element did not even apply to this binding element.
+ /// </returns>
+ /// <remarks>
+ /// Implementations that provide message protection must honor the
+ /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable.
+ /// </remarks>
+ public MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) {
+ SignedResponseRequest request = message as SignedResponseRequest;
+ if (request != null && request.ReturnTo != null && request.SignReturnTo) {
+ var cryptoKeyPair = this.cryptoKeyStore.GetCurrentKey(SecretUri.AbsoluteUri, OpenIdElement.Configuration.MaxAuthenticationTime);
+ request.AddReturnToArguments(ReturnToSignatureHandleParameterName, cryptoKeyPair.Key);
+ string signature = Convert.ToBase64String(this.GetReturnToSignature(request.ReturnTo, cryptoKeyPair.Value));
+ request.AddReturnToArguments(ReturnToSignatureParameterName, signature);
+
+ // We return none because we are not signing the entire message (only a part).
+ return MessageProtections.None;
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Performs any transformation on an incoming message that may be necessary and/or
+ /// validates an incoming message based on the rules of this channel binding element.
+ /// </summary>
+ /// <param name="message">The incoming message to process.</param>
+ /// <returns>
+ /// The protections (if any) that this binding element applied to the message.
+ /// Null if this binding element did not even apply to this binding element.
+ /// </returns>
+ /// <exception cref="ProtocolException">
+ /// Thrown when the binding element rules indicate that this message is invalid and should
+ /// NOT be processed.
+ /// </exception>
+ /// <remarks>
+ /// Implementations that provide message protection must honor the
+ /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable.
+ /// </remarks>
+ public MessageProtections? ProcessIncomingMessage(IProtocolMessage message) {
+ IndirectSignedResponse response = message as IndirectSignedResponse;
+
+ if (response != null) {
+ // We can't use response.GetReturnToArgument(string) because that relies
+ // on us already having validated this signature.
+ NameValueCollection returnToParameters = HttpUtility.ParseQueryString(response.ReturnTo.Query);
+
+ // Only check the return_to signature if one is present.
+ if (returnToParameters[ReturnToSignatureHandleParameterName] != null) {
+ // Set the safety flag showing whether the return_to url had a valid signature.
+ byte[] expectedBytes = this.GetReturnToSignature(response.ReturnTo);
+ string actual = returnToParameters[ReturnToSignatureParameterName];
+ actual = OpenIdUtilities.FixDoublyUriDecodedBase64String(actual);
+ byte[] actualBytes = Convert.FromBase64String(actual);
+ response.ReturnToParametersSignatureValidated = MessagingUtilities.AreEquivalentConstantTime(actualBytes, expectedBytes);
+ if (!response.ReturnToParametersSignatureValidated) {
+ Logger.Bindings.WarnFormat("The return_to signature failed verification.");
+ }
+
+ return MessageProtections.None;
+ }
+ }
+
+ return null;
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Gets the return to signature.
+ /// </summary>
+ /// <param name="returnTo">The return to.</param>
+ /// <param name="cryptoKey">The crypto key.</param>
+ /// <returns>
+ /// The generated signature.
+ /// </returns>
+ /// <remarks>
+ /// Only the parameters in the return_to URI are signed, rather than the base URI
+ /// itself, in order that OPs that might change the return_to's implicit port :80 part
+ /// or other minor changes do not invalidate the signature.
+ /// </remarks>
+ private byte[] GetReturnToSignature(Uri returnTo, CryptoKey cryptoKey = null) {
+ Requires.NotNull(returnTo, "returnTo");
+
+ // Assemble the dictionary to sign, taking care to remove the signature itself
+ // in order to accurately reproduce the original signature (which of course didn't include
+ // the signature).
+ // Also we need to sort the dictionary's keys so that we sign in the same order as we did
+ // the last time.
+ var returnToParameters = HttpUtility.ParseQueryString(returnTo.Query);
+ returnToParameters.Remove(ReturnToSignatureParameterName);
+ var sortedReturnToParameters = new SortedDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+ foreach (string key in returnToParameters) {
+ sortedReturnToParameters.Add(key, returnToParameters[key]);
+ }
+
+ Logger.Bindings.DebugFormat("ReturnTo signed data: {0}{1}", Environment.NewLine, sortedReturnToParameters.ToStringDeferred());
+
+ // Sign the parameters.
+ byte[] bytesToSign = KeyValueFormEncoding.GetBytes(sortedReturnToParameters);
+ byte[] signature;
+ try {
+ if (cryptoKey == null) {
+ cryptoKey = this.cryptoKeyStore.GetKey(SecretUri.AbsoluteUri, returnToParameters[ReturnToSignatureHandleParameterName]);
+ }
+
+ using (var signer = HmacAlgorithms.Create(HmacAlgorithms.HmacSha256, cryptoKey.Key)) {
+ signature = signer.ComputeHash(bytesToSign);
+ }
+ } catch (ProtocolException ex) {
+ throw ErrorUtilities.Wrap(ex, OpenIdStrings.MaximumAuthenticationTimeExpired);
+ }
+
+ return signature;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/SigningBindingElement.cs b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/SigningBindingElement.cs
new file mode 100644
index 0000000..363ff28
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/SigningBindingElement.cs
@@ -0,0 +1,199 @@
+//-----------------------------------------------------------------------
+// <copyright file="SigningBindingElement.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.Linq;
+ using System.Net.Security;
+ using System.Web;
+ using DotNetOpenAuth.Loggers;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
+ using DotNetOpenAuth.Messaging.Reflection;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// Signs and verifies authentication assertions.
+ /// </summary>
+ [ContractClass(typeof(SigningBindingElementContract))]
+ internal abstract class SigningBindingElement : IChannelBindingElement {
+ #region IChannelBindingElement Properties
+
+ /// <summary>
+ /// Gets the protection offered (if any) by this binding element.
+ /// </summary>
+ /// <value><see cref="MessageProtections.TamperProtection"/></value>
+ public MessageProtections Protection {
+ get { return MessageProtections.TamperProtection; }
+ }
+
+ /// <summary>
+ /// Gets or sets the channel that this binding element belongs to.
+ /// </summary>
+ public Channel Channel { get; set; }
+
+ #endregion
+
+ /// <summary>
+ /// Gets a value indicating whether this binding element is on a Provider channel.
+ /// </summary>
+ protected virtual bool IsOnProvider {
+ get { return false; }
+ }
+
+ #region IChannelBindingElement Methods
+
+ /// <summary>
+ /// Prepares a message for sending based on the rules of this channel binding element.
+ /// </summary>
+ /// <param name="message">The message to prepare for sending.</param>
+ /// <returns>
+ /// The protections (if any) that this binding element applied to the message.
+ /// Null if this binding element did not even apply to this binding element.
+ /// </returns>
+ public virtual MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) {
+ return null;
+ }
+
+ /// <summary>
+ /// Performs any transformation on an incoming message that may be necessary and/or
+ /// validates an incoming message based on the rules of this channel binding element.
+ /// </summary>
+ /// <param name="message">The incoming message to process.</param>
+ /// <returns>
+ /// The protections (if any) that this binding element applied to the message.
+ /// Null if this binding element did not even apply to this binding element.
+ /// </returns>
+ /// <exception cref="ProtocolException">
+ /// Thrown when the binding element rules indicate that this message is invalid and should
+ /// NOT be processed.
+ /// </exception>
+ public MessageProtections? ProcessIncomingMessage(IProtocolMessage message) {
+ var signedMessage = message as ITamperResistantOpenIdMessage;
+ if (signedMessage != null) {
+ Logger.Bindings.DebugFormat("Verifying incoming {0} message signature of: {1}", message.GetType().Name, signedMessage.Signature);
+ MessageProtections protectionsApplied = MessageProtections.TamperProtection;
+
+ this.EnsureParametersRequiringSignatureAreSigned(signedMessage);
+
+ Association association = this.GetSpecificAssociation(signedMessage);
+ if (association != null) {
+ string signature = this.GetSignature(signedMessage, association);
+ if (!MessagingUtilities.EqualsConstantTime(signedMessage.Signature, signature)) {
+ Logger.Bindings.Error("Signature verification failed.");
+ throw new InvalidSignatureException(message);
+ }
+ } else {
+ ErrorUtilities.VerifyInternal(this.Channel != null, "Cannot verify private association signature because we don't have a channel.");
+
+ protectionsApplied = this.VerifySignatureByUnrecognizedHandle(message, signedMessage, protectionsApplied);
+ }
+
+ return protectionsApplied;
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Verifies the signature by unrecognized handle.
+ /// </summary>
+ /// <param name="message">The message.</param>
+ /// <param name="signedMessage">The signed message.</param>
+ /// <param name="protectionsApplied">The protections applied.</param>
+ /// <returns>The applied protections.</returns>
+ protected abstract MessageProtections VerifySignatureByUnrecognizedHandle(IProtocolMessage message, ITamperResistantOpenIdMessage signedMessage, MessageProtections protectionsApplied);
+
+ #endregion
+
+ /// <summary>
+ /// Calculates the signature for a given message.
+ /// </summary>
+ /// <param name="signedMessage">The message to sign or verify.</param>
+ /// <param name="association">The association to use to sign the message.</param>
+ /// <returns>The calculated signature of the method.</returns>
+ protected string GetSignature(ITamperResistantOpenIdMessage signedMessage, Association association) {
+ Requires.NotNull(signedMessage, "signedMessage");
+ Requires.True(!string.IsNullOrEmpty(signedMessage.SignedParameterOrder), "signedMessage");
+ Requires.NotNull(association, "association");
+
+ // Prepare the parts to sign, taking care to replace an openid.mode value
+ // of check_authentication with its original id_res so the signature matches.
+ MessageDictionary dictionary = this.Channel.MessageDescriptions.GetAccessor(signedMessage);
+ var parametersToSign = from name in signedMessage.SignedParameterOrder.Split(',')
+ let prefixedName = Protocol.V20.openid.Prefix + name
+ select new KeyValuePair<string, string>(name, dictionary.GetValueOrThrow(prefixedName, signedMessage));
+
+ byte[] dataToSign = KeyValueFormEncoding.GetBytes(parametersToSign);
+ string signature = Convert.ToBase64String(association.Sign(dataToSign));
+
+ if (Logger.Signatures.IsDebugEnabled) {
+ Logger.Signatures.DebugFormat(
+ "Signing these message parts: {0}{1}{0}Base64 representation of signed data: {2}{0}Signature: {3}",
+ Environment.NewLine,
+ parametersToSign.ToStringDeferred(),
+ Convert.ToBase64String(dataToSign),
+ signature);
+ }
+
+ return signature;
+ }
+
+ /// <summary>
+ /// Gets the association to use to sign or verify a message.
+ /// </summary>
+ /// <param name="signedMessage">The message to sign or verify.</param>
+ /// <returns>The association to use to sign or verify the message.</returns>
+ protected abstract Association GetAssociation(ITamperResistantOpenIdMessage signedMessage);
+
+ /// <summary>
+ /// Gets a specific association referenced in a given message's association handle.
+ /// </summary>
+ /// <param name="signedMessage">The signed message whose association handle should be used to lookup the association to return.</param>
+ /// <returns>The referenced association; or <c>null</c> if such an association cannot be found.</returns>
+ /// <remarks>
+ /// If the association handle set in the message does not match any valid association,
+ /// the association handle property is cleared, and the
+ /// <see cref="ITamperResistantOpenIdMessage.InvalidateHandle"/> property is set to the
+ /// handle that could not be found.
+ /// </remarks>
+ protected abstract Association GetSpecificAssociation(ITamperResistantOpenIdMessage signedMessage);
+
+ /// <summary>
+ /// Gets a private Provider association used for signing messages in "dumb" mode.
+ /// </summary>
+ /// <returns>An existing or newly created association.</returns>
+ protected virtual Association GetDumbAssociationForSigning() {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Ensures that all message parameters that must be signed are in fact included
+ /// in the signature.
+ /// </summary>
+ /// <param name="signedMessage">The signed message.</param>
+ private void EnsureParametersRequiringSignatureAreSigned(ITamperResistantOpenIdMessage signedMessage) {
+ // Verify that the signed parameter order includes the mandated fields.
+ // We do this in such a way that derived classes that add mandated fields automatically
+ // get included in the list of checked parameters.
+ Protocol protocol = Protocol.Lookup(signedMessage.Version);
+ var partsRequiringProtection = from part in this.Channel.MessageDescriptions.Get(signedMessage).Mapping.Values
+ where part.RequiredProtection != ProtectionLevel.None
+ where part.IsRequired || part.IsNondefaultValueSet(signedMessage)
+ select part.Name;
+ ErrorUtilities.VerifyInternal(partsRequiringProtection.All(name => name.StartsWith(protocol.openid.Prefix, StringComparison.Ordinal)), "Signing only works when the parameters start with the 'openid.' prefix.");
+ string[] signedParts = signedMessage.SignedParameterOrder.Split(',');
+ var unsignedParts = from partName in partsRequiringProtection
+ where !signedParts.Contains(partName.Substring(protocol.openid.Prefix.Length))
+ select partName;
+ ErrorUtilities.VerifyProtocol(!unsignedParts.Any(), OpenIdStrings.SignatureDoesNotIncludeMandatoryParts, string.Join(", ", unsignedParts.ToArray()));
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/SigningBindingElementContract.cs b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/SigningBindingElementContract.cs
new file mode 100644
index 0000000..bacbb29
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/SigningBindingElementContract.cs
@@ -0,0 +1,64 @@
+//-----------------------------------------------------------------------
+// <copyright file="SigningBindingElementContract.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.Linq;
+ using System.Net.Security;
+ using System.Web;
+ using DotNetOpenAuth.Loggers;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
+ using DotNetOpenAuth.Messaging.Reflection;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// Code contract for the <see cref="SigningBindingElement"/> class.
+ /// </summary>
+ [ContractClassFor(typeof(SigningBindingElement))]
+ internal abstract class SigningBindingElementContract : SigningBindingElement {
+ /// <summary>
+ /// Verifies the signature by unrecognized handle.
+ /// </summary>
+ /// <param name="message">The message.</param>
+ /// <param name="signedMessage">The signed message.</param>
+ /// <param name="protectionsApplied">The protections applied.</param>
+ /// <returns>
+ /// The applied protections.
+ /// </returns>
+ protected override MessageProtections VerifySignatureByUnrecognizedHandle(IProtocolMessage message, ITamperResistantOpenIdMessage signedMessage, MessageProtections protectionsApplied) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Gets the association to use to sign or verify a message.
+ /// </summary>
+ /// <param name="signedMessage">The message to sign or verify.</param>
+ /// <returns>
+ /// The association to use to sign or verify the message.
+ /// </returns>
+ protected override Association GetAssociation(ITamperResistantOpenIdMessage signedMessage) {
+ Requires.NotNull(signedMessage, "signedMessage");
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Gets a specific association referenced in a given message's association handle.
+ /// </summary>
+ /// <param name="signedMessage">The signed message whose association handle should be used to lookup the association to return.</param>
+ /// <returns>
+ /// The referenced association; or <c>null</c> if such an association cannot be found.
+ /// </returns>
+ protected override Association GetSpecificAssociation(ITamperResistantOpenIdMessage signedMessage) {
+ Requires.NotNull(signedMessage, "signedMessage");
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/SkipSecurityBindingElement.cs b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/SkipSecurityBindingElement.cs
new file mode 100644
index 0000000..d162cf6
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/ChannelElements/SkipSecurityBindingElement.cs
@@ -0,0 +1,87 @@
+//-----------------------------------------------------------------------
+// <copyright file="SkipSecurityBindingElement.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// Spoofs security checks on incoming OpenID messages.
+ /// </summary>
+ internal class SkipSecurityBindingElement : IChannelBindingElement {
+ #region IChannelBindingElement Members
+
+ /// <summary>
+ /// Gets or sets the channel that this binding element belongs to.
+ /// </summary>
+ /// <value></value>
+ /// <remarks>
+ /// This property is set by the channel when it is first constructed.
+ /// </remarks>
+ public Channel Channel { get; set; }
+
+ /// <summary>
+ /// Gets the protection commonly offered (if any) by this binding element.
+ /// </summary>
+ /// <value><see cref="MessageProtections.All"/></value>
+ /// <remarks>
+ /// This value is used to assist in sorting binding elements in the channel stack.
+ /// </remarks>
+ public MessageProtections Protection {
+ get { return MessageProtections.All; }
+ }
+
+ /// <summary>
+ /// Prepares a message for sending based on the rules of this channel binding element.
+ /// </summary>
+ /// <param name="message">The message to prepare for sending.</param>
+ /// <returns>
+ /// The protections (if any) that this binding element applied to the message.
+ /// Null if this binding element did not even apply to this binding element.
+ /// </returns>
+ /// <remarks>
+ /// Implementations that provide message protection must honor the
+ /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable.
+ /// </remarks>
+ public MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) {
+ Debug.Fail("SkipSecurityBindingElement.ProcessOutgoingMessage should never be called.");
+ return null;
+ }
+
+ /// <summary>
+ /// Performs any transformation on an incoming message that may be necessary and/or
+ /// validates an incoming message based on the rules of this channel binding element.
+ /// </summary>
+ /// <param name="message">The incoming message to process.</param>
+ /// <returns>
+ /// The protections (if any) that this binding element applied to the message.
+ /// Null if this binding element did not even apply to this binding element.
+ /// </returns>
+ /// <exception cref="ProtocolException">
+ /// Thrown when the binding element rules indicate that this message is invalid and should
+ /// NOT be processed.
+ /// </exception>
+ /// <remarks>
+ /// Implementations that provide message protection must honor the
+ /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable.
+ /// </remarks>
+ public MessageProtections? ProcessIncomingMessage(IProtocolMessage message) {
+ var signedMessage = message as ITamperResistantOpenIdMessage;
+ if (signedMessage != null) {
+ Logger.Bindings.DebugFormat("Skipped security checks of incoming {0} message for preview purposes.", message.GetType().Name);
+ return this.Protection;
+ }
+
+ return null;
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/DiffieHellmanUtilities.cs b/src/DotNetOpenAuth.OpenId/OpenId/DiffieHellmanUtilities.cs
new file mode 100644
index 0000000..e15bd6e
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/DiffieHellmanUtilities.cs
@@ -0,0 +1,161 @@
+//-----------------------------------------------------------------------
+// <copyright file="DiffieHellmanUtilities.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.Linq;
+ using System.Security.Cryptography;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using Org.Mentalis.Security.Cryptography;
+
+ /// <summary>
+ /// Diffie-Hellman encryption methods used by both the relying party and provider.
+ /// </summary>
+ internal class DiffieHellmanUtilities {
+ /// <summary>
+ /// An array of known Diffie Hellman sessions, sorted by decreasing hash size.
+ /// </summary>
+ private static DHSha[] diffieHellmanSessionTypes = CreateSessionTypes();
+
+ /// <summary>
+ /// Finds the hashing algorithm to use given an openid.session_type value.
+ /// </summary>
+ /// <param name="protocol">The protocol version of the message that named the session_type to be used.</param>
+ /// <param name="sessionType">The value of the openid.session_type parameter.</param>
+ /// <returns>The hashing algorithm to use.</returns>
+ /// <exception cref="ProtocolException">Thrown if no match could be found for the given <paramref name="sessionType"/>.</exception>
+ public static HashAlgorithm Lookup(Protocol protocol, string sessionType) {
+ Requires.NotNull(protocol, "protocol");
+ Requires.NotNull(sessionType, "sessionType");
+
+ // We COULD use just First instead of FirstOrDefault, but we want to throw ProtocolException instead of InvalidOperationException.
+ DHSha match = diffieHellmanSessionTypes.FirstOrDefault(dhsha => string.Equals(dhsha.GetName(protocol), sessionType, StringComparison.Ordinal));
+ ErrorUtilities.VerifyProtocol(match != null, OpenIdStrings.NoSessionTypeFound, sessionType, protocol.Version);
+ return match.Algorithm;
+ }
+
+ /// <summary>
+ /// Looks up the value to be used for the openid.session_type parameter.
+ /// </summary>
+ /// <param name="protocol">The protocol version that is to be used.</param>
+ /// <param name="hashSizeInBits">The hash size (in bits) that the DH session must have.</param>
+ /// <returns>The value to be used for the openid.session_type parameter, or null if no match was found.</returns>
+ internal static string GetNameForSize(Protocol protocol, int hashSizeInBits) {
+ Requires.NotNull(protocol, "protocol");
+ DHSha match = diffieHellmanSessionTypes.FirstOrDefault(dhsha => dhsha.Algorithm.HashSize == hashSizeInBits);
+ return match != null ? match.GetName(protocol) : null;
+ }
+
+ /// <summary>
+ /// Encrypts/decrypts a shared secret.
+ /// </summary>
+ /// <param name="hasher">The hashing algorithm that is agreed by both parties to use as part of the secret exchange.</param>
+ /// <param name="dh">
+ /// If the secret is being encrypted, this is the new Diffie Hellman object to use.
+ /// If the secret is being decrypted, this must be the same Diffie Hellman object used to send the original request message.
+ /// </param>
+ /// <param name="remotePublicKey">The public key of the remote party.</param>
+ /// <param name="plainOrEncryptedSecret">The secret to encode, or the encoded secret. Whichever one is given will generate the opposite in the return value.</param>
+ /// <returns>
+ /// The encrypted version of the secret if the secret itself was given in <paramref name="remotePublicKey"/>.
+ /// The secret itself if the encrypted version of the secret was given in <paramref name="remotePublicKey"/>.
+ /// </returns>
+ internal static byte[] SHAHashXorSecret(HashAlgorithm hasher, DiffieHellman dh, byte[] remotePublicKey, byte[] plainOrEncryptedSecret) {
+ Requires.NotNull(hasher, "hasher");
+ Requires.NotNull(dh, "dh");
+ Requires.NotNull(remotePublicKey, "remotePublicKey");
+ Requires.NotNull(plainOrEncryptedSecret, "plainOrEncryptedSecret");
+
+ byte[] sharedBlock = dh.DecryptKeyExchange(remotePublicKey);
+ byte[] sharedBlockHash = hasher.ComputeHash(EnsurePositive(sharedBlock));
+ ErrorUtilities.VerifyProtocol(sharedBlockHash.Length == plainOrEncryptedSecret.Length, OpenIdStrings.AssociationSecretHashLengthMismatch, plainOrEncryptedSecret.Length, sharedBlockHash.Length);
+
+ byte[] secret = new byte[plainOrEncryptedSecret.Length];
+ for (int i = 0; i < plainOrEncryptedSecret.Length; i++) {
+ secret[i] = (byte)(plainOrEncryptedSecret[i] ^ sharedBlockHash[i]);
+ }
+ return secret;
+ }
+
+ /// <summary>
+ /// Ensures that the big integer represented by a given series of bytes
+ /// is a positive integer.
+ /// </summary>
+ /// <param name="inputBytes">The bytes that make up the big integer.</param>
+ /// <returns>
+ /// A byte array (possibly new if a change was required) whose
+ /// integer is guaranteed to be positive.
+ /// </returns>
+ /// <remarks>
+ /// This is to be consistent with OpenID spec section 4.2.
+ /// </remarks>
+ internal static byte[] EnsurePositive(byte[] inputBytes) {
+ Requires.NotNull(inputBytes, "inputBytes");
+ if (inputBytes.Length == 0) {
+ throw new ArgumentException(MessagingStrings.UnexpectedEmptyArray, "inputBytes");
+ }
+
+ int i = (int)inputBytes[0];
+ if (i > 127) {
+ byte[] nowPositive = new byte[inputBytes.Length + 1];
+ nowPositive[0] = 0;
+ inputBytes.CopyTo(nowPositive, 1);
+ return nowPositive;
+ }
+
+ return inputBytes;
+ }
+
+ /// <summary>
+ /// Returns the value used to initialize the static field storing DH session types.
+ /// </summary>
+ /// <returns>A non-null, non-empty array.</returns>
+ /// <remarks>>
+ /// This is a method rather than being inlined to the field initializer to try to avoid
+ /// the CLR bug that crops up sometimes if we initialize arrays using object initializer syntax.
+ /// </remarks>
+ private static DHSha[] CreateSessionTypes() {
+ return new[] {
+ new DHSha(SHA512.Create(), protocol => protocol.Args.SessionType.DH_SHA512),
+ new DHSha(SHA384.Create(), protocol => protocol.Args.SessionType.DH_SHA384),
+ new DHSha(SHA256.Create(), protocol => protocol.Args.SessionType.DH_SHA256),
+ new DHSha(SHA1.Create(), protocol => protocol.Args.SessionType.DH_SHA1),
+ };
+ }
+
+ /// <summary>
+ /// Provides access to a Diffie-Hellman session algorithm and its name.
+ /// </summary>
+ private class DHSha {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="DHSha"/> class.
+ /// </summary>
+ /// <param name="algorithm">The hashing algorithm used in this particular Diffie-Hellman session type.</param>
+ /// <param name="getName">A function that will return the value of the openid.session_type parameter for a given version of OpenID.</param>
+ public DHSha(HashAlgorithm algorithm, Func<Protocol, string> getName) {
+ Requires.NotNull(algorithm, "algorithm");
+ Requires.NotNull(getName, "getName");
+
+ this.GetName = getName;
+ this.Algorithm = algorithm;
+ }
+
+ /// <summary>
+ /// Gets the function that will return the value of the openid.session_type parameter for a given version of OpenID.
+ /// </summary>
+ internal Func<Protocol, string> GetName { get; private set; }
+
+ /// <summary>
+ /// Gets the hashing algorithm used in this particular Diffie-Hellman session type
+ /// </summary>
+ internal HashAlgorithm Algorithm { get; private set; }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AliasManager.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AliasManager.cs
new file mode 100644
index 0000000..ff3d3f3
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AliasManager.cs
@@ -0,0 +1,187 @@
+//-----------------------------------------------------------------------
+// <copyright file="AliasManager.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// Manages a fast, two-way mapping between type URIs and their aliases.
+ /// </summary>
+ internal class AliasManager {
+ /// <summary>
+ /// The format of auto-generated aliases.
+ /// </summary>
+ private const string AliasFormat = "alias{0}";
+
+ /// <summary>
+ /// Tracks extension Type URIs and aliases assigned to them.
+ /// </summary>
+ private Dictionary<string, string> typeUriToAliasMap = new Dictionary<string, string>();
+
+ /// <summary>
+ /// Tracks extension aliases and Type URIs assigned to them.
+ /// </summary>
+ private Dictionary<string, string> aliasToTypeUriMap = new Dictionary<string, string>();
+
+ /// <summary>
+ /// Gets the aliases that have been set.
+ /// </summary>
+ public IEnumerable<string> Aliases {
+ get { return this.aliasToTypeUriMap.Keys; }
+ }
+
+ /// <summary>
+ /// Gets an alias assigned for a given Type URI. A new alias is assigned if necessary.
+ /// </summary>
+ /// <param name="typeUri">The type URI.</param>
+ /// <returns>The alias assigned to this type URI. Never null.</returns>
+ public string GetAlias(string typeUri) {
+ Requires.NotNullOrEmpty(typeUri, "typeUri");
+ string alias;
+ return this.typeUriToAliasMap.TryGetValue(typeUri, out alias) ? alias : this.AssignNewAlias(typeUri);
+ }
+
+ /// <summary>
+ /// Sets an alias and the value that will be returned by <see cref="ResolveAlias"/>.
+ /// </summary>
+ /// <param name="alias">The alias.</param>
+ /// <param name="typeUri">The type URI.</param>
+ public void SetAlias(string alias, string typeUri) {
+ Requires.NotNullOrEmpty(alias, "alias");
+ Requires.NotNullOrEmpty(typeUri, "typeUri");
+ this.aliasToTypeUriMap.Add(alias, typeUri);
+ this.typeUriToAliasMap.Add(typeUri, alias);
+ }
+
+ /// <summary>
+ /// Takes a sequence of type URIs and assigns aliases for all of them.
+ /// </summary>
+ /// <param name="typeUris">The type URIs to create aliases for.</param>
+ /// <param name="preferredTypeUriToAliases">An optional dictionary of URI/alias pairs that suggest preferred aliases to use if available for certain type URIs.</param>
+ public void AssignAliases(IEnumerable<string> typeUris, IDictionary<string, string> preferredTypeUriToAliases) {
+ Requires.NotNull(typeUris, "typeUris");
+
+ // First go through the actually used type URIs and see which ones have matching preferred aliases.
+ if (preferredTypeUriToAliases != null) {
+ foreach (string typeUri in typeUris) {
+ if (this.typeUriToAliasMap.ContainsKey(typeUri)) {
+ // this Type URI is already mapped to an alias.
+ continue;
+ }
+
+ string preferredAlias;
+ if (preferredTypeUriToAliases.TryGetValue(typeUri, out preferredAlias) && !this.IsAliasUsed(preferredAlias)) {
+ this.SetAlias(preferredAlias, typeUri);
+ }
+ }
+ }
+
+ // Now go through the whole list again and assign whatever is left now that the preferred ones
+ // have gotten their picks where available.
+ foreach (string typeUri in typeUris) {
+ if (this.typeUriToAliasMap.ContainsKey(typeUri)) {
+ // this Type URI is already mapped to an alias.
+ continue;
+ }
+
+ this.AssignNewAlias(typeUri);
+ }
+ }
+
+ /// <summary>
+ /// Sets up aliases for any Type URIs in a dictionary that do not yet have aliases defined,
+ /// and where the given preferred alias is still available.
+ /// </summary>
+ /// <param name="preferredTypeUriToAliases">A dictionary of type URI keys and alias values.</param>
+ public void SetPreferredAliasesWhereNotSet(IDictionary<string, string> preferredTypeUriToAliases) {
+ Requires.NotNull(preferredTypeUriToAliases, "preferredTypeUriToAliases");
+
+ foreach (var pair in preferredTypeUriToAliases) {
+ if (this.typeUriToAliasMap.ContainsKey(pair.Key)) {
+ // type URI is already mapped
+ continue;
+ }
+
+ if (this.aliasToTypeUriMap.ContainsKey(pair.Value)) {
+ // alias is already mapped
+ continue;
+ }
+
+ // The type URI and alias are as yet unset, so go ahead and assign them.
+ this.SetAlias(pair.Value, pair.Key);
+ }
+ }
+
+ /// <summary>
+ /// Gets the Type Uri encoded by a given alias.
+ /// </summary>
+ /// <param name="alias">The alias.</param>
+ /// <returns>The Type URI.</returns>
+ /// <exception cref="ArgumentOutOfRangeException">Thrown if the given alias does not have a matching TypeURI.</exception>
+ public string ResolveAlias(string alias) {
+ Requires.NotNullOrEmpty(alias, "alias");
+ string typeUri = this.TryResolveAlias(alias);
+ if (typeUri == null) {
+ throw new ArgumentOutOfRangeException("alias");
+ }
+ return typeUri;
+ }
+
+ /// <summary>
+ /// Gets the Type Uri encoded by a given alias.
+ /// </summary>
+ /// <param name="alias">The alias.</param>
+ /// <returns>The Type URI for the given alias, or null if none for that alias exist.</returns>
+ public string TryResolveAlias(string alias) {
+ Requires.NotNullOrEmpty(alias, "alias");
+ string typeUri = null;
+ this.aliasToTypeUriMap.TryGetValue(alias, out typeUri);
+ return typeUri;
+ }
+
+ /// <summary>
+ /// Returns a value indicating whether an alias has already been assigned to a type URI.
+ /// </summary>
+ /// <param name="alias">The alias in question.</param>
+ /// <returns>True if the alias has already been assigned. False otherwise.</returns>
+ public bool IsAliasUsed(string alias) {
+ Requires.NotNullOrEmpty(alias, "alias");
+ return this.aliasToTypeUriMap.ContainsKey(alias);
+ }
+
+ /// <summary>
+ /// Determines whether a given TypeURI has an associated alias assigned to it.
+ /// </summary>
+ /// <param name="typeUri">The type URI.</param>
+ /// <returns>
+ /// <c>true</c> if the given type URI already has an alias assigned; <c>false</c> otherwise.
+ /// </returns>
+ public bool IsAliasAssignedTo(string typeUri) {
+ Requires.NotNull(typeUri, "typeUri");
+ return this.typeUriToAliasMap.ContainsKey(typeUri);
+ }
+
+ /// <summary>
+ /// Assigns a new alias to a given Type URI.
+ /// </summary>
+ /// <param name="typeUri">The type URI to assign a new alias to.</param>
+ /// <returns>The newly generated alias.</returns>
+ private string AssignNewAlias(string typeUri) {
+ Requires.NotNullOrEmpty(typeUri, "typeUri");
+ ErrorUtilities.VerifyInternal(!this.typeUriToAliasMap.ContainsKey(typeUri), "Oops! This type URI already has an alias!");
+ string alias = string.Format(CultureInfo.InvariantCulture, AliasFormat, this.typeUriToAliasMap.Count + 1);
+ this.typeUriToAliasMap.Add(typeUri, alias);
+ this.aliasToTypeUriMap.Add(alias, typeUri);
+ return alias;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AXAttributeFormats.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AXAttributeFormats.cs
new file mode 100644
index 0000000..2fafa60
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AXAttributeFormats.cs
@@ -0,0 +1,45 @@
+//-----------------------------------------------------------------------
+// <copyright file="AXAttributeFormats.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange {
+ using System;
+
+ /// <summary>
+ /// The various Type URI formats an AX attribute may use by various remote parties.
+ /// </summary>
+ [Flags]
+ public enum AXAttributeFormats {
+ /// <summary>
+ /// No attribute format.
+ /// </summary>
+ None = 0x0,
+
+ /// <summary>
+ /// AX attributes should use the Type URI format starting with <c>http://axschema.org/</c>.
+ /// </summary>
+ AXSchemaOrg = 0x1,
+
+ /// <summary>
+ /// AX attributes should use the Type URI format starting with <c>http://schema.openid.net/</c>.
+ /// </summary>
+ SchemaOpenIdNet = 0x2,
+
+ /// <summary>
+ /// AX attributes should use the Type URI format starting with <c>http://openid.net/schema/</c>.
+ /// </summary>
+ OpenIdNetSchema = 0x4,
+
+ /// <summary>
+ /// All known schemas.
+ /// </summary>
+ All = AXSchemaOrg | SchemaOpenIdNet | OpenIdNetSchema,
+
+ /// <summary>
+ /// The most common schemas.
+ /// </summary>
+ Common = AXSchemaOrg | SchemaOpenIdNet,
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AXUtilities.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AXUtilities.cs
new file mode 100644
index 0000000..20b8a79
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AXUtilities.cs
@@ -0,0 +1,144 @@
+//-----------------------------------------------------------------------
+// <copyright file="AXUtilities.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// Helper methods shared by multiple messages in the Attribute Exchange extension.
+ /// </summary>
+ public static class AXUtilities {
+ /// <summary>
+ /// Adds a request for an attribute considering it 'required'.
+ /// </summary>
+ /// <param name="collection">The attribute request collection.</param>
+ /// <param name="typeUri">The type URI of the required attribute.</param>
+ public static void AddRequired(this ICollection<AttributeRequest> collection, string typeUri) {
+ Requires.NotNull(collection, "collection");
+ collection.Add(new AttributeRequest(typeUri, true));
+ }
+
+ /// <summary>
+ /// Adds a request for an attribute without considering it 'required'.
+ /// </summary>
+ /// <param name="collection">The attribute request collection.</param>
+ /// <param name="typeUri">The type URI of the requested attribute.</param>
+ public static void AddOptional(this ICollection<AttributeRequest> collection, string typeUri) {
+ Requires.NotNull(collection, "collection");
+ collection.Add(new AttributeRequest(typeUri, false));
+ }
+
+ /// <summary>
+ /// Adds a given attribute with one or more values to the request for storage.
+ /// Applicable to Relying Parties only.
+ /// </summary>
+ /// <param name="collection">The collection of <see cref="AttributeValues"/> to add to.</param>
+ /// <param name="typeUri">The type URI of the attribute.</param>
+ /// <param name="values">The attribute values.</param>
+ public static void Add(this ICollection<AttributeValues> collection, string typeUri, params string[] values) {
+ Requires.NotNull(collection, "collection");
+ collection.Add(new AttributeValues(typeUri, values));
+ }
+
+ /// <summary>
+ /// Serializes a set of attribute values to a dictionary of fields to send in the message.
+ /// </summary>
+ /// <param name="fields">The dictionary to fill with serialized attributes.</param>
+ /// <param name="attributes">The attributes.</param>
+ internal static void SerializeAttributes(IDictionary<string, string> fields, IEnumerable<AttributeValues> attributes) {
+ Requires.NotNull(fields, "fields");
+ Requires.NotNull(attributes, "attributes");
+
+ AliasManager aliasManager = new AliasManager();
+ foreach (var att in attributes) {
+ string alias = aliasManager.GetAlias(att.TypeUri);
+ fields.Add("type." + alias, att.TypeUri);
+ if (att.Values == null) {
+ continue;
+ }
+ if (att.Values.Count != 1) {
+ fields.Add("count." + alias, att.Values.Count.ToString(CultureInfo.InvariantCulture));
+ for (int i = 0; i < att.Values.Count; i++) {
+ fields.Add(string.Format(CultureInfo.InvariantCulture, "value.{0}.{1}", alias, i + 1), att.Values[i]);
+ }
+ } else {
+ fields.Add("value." + alias, att.Values[0]);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Deserializes attribute values from an incoming set of message data.
+ /// </summary>
+ /// <param name="fields">The data coming in with the message.</param>
+ /// <returns>The attribute values found in the message.</returns>
+ internal static IEnumerable<AttributeValues> DeserializeAttributes(IDictionary<string, string> fields) {
+ AliasManager aliasManager = ParseAliases(fields);
+ foreach (string alias in aliasManager.Aliases) {
+ AttributeValues att = new AttributeValues(aliasManager.ResolveAlias(alias));
+ int count = 1;
+ bool countSent = false;
+ string countString;
+ if (fields.TryGetValue("count." + alias, out countString)) {
+ if (!int.TryParse(countString, out count) || count < 0) {
+ Logger.OpenId.ErrorFormat("Failed to parse count.{0} value to a non-negative integer.", alias);
+ continue;
+ }
+ countSent = true;
+ }
+ if (countSent) {
+ for (int i = 1; i <= count; i++) {
+ string value;
+ if (fields.TryGetValue(string.Format(CultureInfo.InvariantCulture, "value.{0}.{1}", alias, i), out value)) {
+ att.Values.Add(value);
+ } else {
+ Logger.OpenId.ErrorFormat("Missing value for attribute '{0}'.", att.TypeUri);
+ continue;
+ }
+ }
+ } else {
+ string value;
+ if (fields.TryGetValue("value." + alias, out value)) {
+ att.Values.Add(value);
+ } else {
+ Logger.OpenId.ErrorFormat("Missing value for attribute '{0}'.", att.TypeUri);
+ continue;
+ }
+ }
+ yield return att;
+ }
+ }
+
+ /// <summary>
+ /// Reads through the attributes included in the response to discover
+ /// the alias-TypeURI relationships.
+ /// </summary>
+ /// <param name="fields">The data included in the extension message.</param>
+ /// <returns>The alias manager that provides lookup between aliases and type URIs.</returns>
+ private static AliasManager ParseAliases(IDictionary<string, string> fields) {
+ Requires.NotNull(fields, "fields");
+
+ AliasManager aliasManager = new AliasManager();
+ const string TypePrefix = "type.";
+ foreach (var pair in fields) {
+ if (!pair.Key.StartsWith(TypePrefix, StringComparison.Ordinal)) {
+ continue;
+ }
+ string alias = pair.Key.Substring(TypePrefix.Length);
+ if (alias.IndexOfAny(FetchRequest.IllegalAliasCharacters) >= 0) {
+ Logger.OpenId.ErrorFormat("Illegal characters in alias name '{0}'.", alias);
+ continue;
+ }
+ aliasManager.SetAlias(alias, pair.Value);
+ }
+ return aliasManager;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AttributeRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AttributeRequest.cs
new file mode 100644
index 0000000..6590cd1
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AttributeRequest.cs
@@ -0,0 +1,156 @@
+//-----------------------------------------------------------------------
+// <copyright file="AttributeRequest.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange {
+ using System;
+ using System.Diagnostics;
+ using System.Diagnostics.Contracts;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// An individual attribute to be requested of the OpenID Provider using
+ /// the Attribute Exchange extension.
+ /// </summary>
+ [Serializable]
+ [DebuggerDisplay("{TypeUri} (required: {IsRequired}) ({Count})")]
+ public class AttributeRequest {
+ /// <summary>
+ /// Backing field for the <see cref="Count"/> property.
+ /// </summary>
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private int count = 1;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AttributeRequest"/> class
+ /// with <see cref="IsRequired"/> = false, <see cref="Count"/> = 1.
+ /// </summary>
+ public AttributeRequest() {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AttributeRequest"/> class
+ /// with <see cref="IsRequired"/> = false, <see cref="Count"/> = 1.
+ /// </summary>
+ /// <param name="typeUri">The unique TypeURI for that describes the attribute being sought.</param>
+ public AttributeRequest(string typeUri) {
+ Requires.NotNullOrEmpty(typeUri, "typeUri");
+ this.TypeUri = typeUri;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AttributeRequest"/> class
+ /// with <see cref="Count"/> = 1.
+ /// </summary>
+ /// <param name="typeUri">The unique TypeURI for that describes the attribute being sought.</param>
+ /// <param name="isRequired">A value indicating whether the Relying Party considers this attribute to be required for registration.</param>
+ public AttributeRequest(string typeUri, bool isRequired)
+ : this(typeUri) {
+ this.IsRequired = isRequired;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AttributeRequest"/> class.
+ /// </summary>
+ /// <param name="typeUri">The unique TypeURI for that describes the attribute being sought.</param>
+ /// <param name="isRequired">A value indicating whether the Relying Party considers this attribute to be required for registration.</param>
+ /// <param name="count">The maximum number of values for this attribute the Relying Party is prepared to receive.</param>
+ public AttributeRequest(string typeUri, bool isRequired, int count)
+ : this(typeUri, isRequired) {
+ this.Count = count;
+ }
+
+ /// <summary>
+ /// Gets or sets the URI uniquely identifying the attribute being requested.
+ /// </summary>
+ public string TypeUri { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the relying party considers this a required field.
+ /// Note that even if set to true, the Provider may not provide the value.
+ /// </summary>
+ public bool IsRequired { get; set; }
+
+ /// <summary>
+ /// Gets or sets the maximum number of values for this attribute the
+ /// Relying Party wishes to receive from the OpenID Provider.
+ /// A value of int.MaxValue is considered infinity.
+ /// </summary>
+ public int Count {
+ get {
+ return this.count;
+ }
+
+ set {
+ Requires.InRange(value > 0, "value");
+ this.count = value;
+ }
+ }
+
+ /// <summary>
+ /// Used by a Provider to create a response to a request for an attribute's value(s)
+ /// using a given array of strings.
+ /// </summary>
+ /// <param name="values">The values for the requested attribute.</param>
+ /// <returns>
+ /// The newly created <see cref="AttributeValues"/> object that should be added to
+ /// the <see cref="FetchResponse"/> object.
+ /// </returns>
+ public AttributeValues Respond(params string[] values) {
+ Requires.NotNull(values, "values");
+ Requires.True(values.Length <= this.Count, "values");
+ return new AttributeValues(this.TypeUri, values);
+ }
+
+ /// <summary>
+ /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
+ /// </summary>
+ /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
+ /// <returns>
+ /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
+ /// </returns>
+ /// <exception cref="T:System.NullReferenceException">
+ /// The <paramref name="obj"/> parameter is null.
+ /// </exception>
+ public override bool Equals(object obj) {
+ AttributeRequest other = obj as AttributeRequest;
+ if (other == null) {
+ return false;
+ }
+
+ if (this.TypeUri != other.TypeUri) {
+ return false;
+ }
+
+ if (this.Count != other.Count) {
+ return false;
+ }
+
+ if (this.IsRequired != other.IsRequired) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Serves as a hash function for a particular type.
+ /// </summary>
+ /// <returns>
+ /// A hash code for the current <see cref="T:System.Object"/>.
+ /// </returns>
+ public override int GetHashCode() {
+ int hashCode = this.IsRequired ? 1 : 0;
+ unchecked {
+ hashCode += this.Count;
+ if (this.TypeUri != null) {
+ hashCode += this.TypeUri.GetHashCode();
+ }
+ }
+
+ return hashCode;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AttributeValues.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AttributeValues.cs
new file mode 100644
index 0000000..44aad04
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/AttributeValues.cs
@@ -0,0 +1,114 @@
+//-----------------------------------------------------------------------
+// <copyright file="AttributeValues.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Diagnostics.Contracts;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// An individual attribute's value(s) as supplied by an OpenID Provider
+ /// in response to a prior request by an OpenID Relying Party as part of
+ /// a fetch request, or by a relying party as part of a store request.
+ /// </summary>
+ [Serializable]
+ [DebuggerDisplay("{TypeUri}")]
+ public class AttributeValues {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AttributeValues"/> class.
+ /// </summary>
+ /// <param name="typeUri">The TypeURI that uniquely identifies the attribute.</param>
+ /// <param name="values">The values for the attribute.</param>
+ public AttributeValues(string typeUri, params string[] values) {
+ Requires.NotNullOrEmpty(typeUri, "typeUri");
+
+ this.TypeUri = typeUri;
+ this.Values = (IList<string>)values ?? EmptyList<string>.Instance;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AttributeValues"/> class.
+ /// </summary>
+ /// <remarks>
+ /// This is internal because web sites should be using the
+ /// <see cref="AttributeRequest.Respond"/> method to instantiate.
+ /// </remarks>
+ internal AttributeValues() {
+ this.Values = new List<string>(1);
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AttributeValues"/> class.
+ /// </summary>
+ /// <param name="typeUri">The TypeURI of the attribute whose values are being provided.</param>
+ internal AttributeValues(string typeUri) {
+ Requires.NotNullOrEmpty(typeUri, "typeUri");
+
+ this.TypeUri = typeUri;
+ this.Values = new List<string>(1);
+ }
+
+ /// <summary>
+ /// Gets the URI uniquely identifying the attribute whose value is being supplied.
+ /// </summary>
+ public string TypeUri { get; internal set; }
+
+ /// <summary>
+ /// Gets the values supplied by the Provider.
+ /// </summary>
+ public IList<string> Values { get; private set; }
+
+ /// <summary>
+ /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
+ /// </summary>
+ /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
+ /// <returns>
+ /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
+ /// </returns>
+ /// <exception cref="T:System.NullReferenceException">
+ /// The <paramref name="obj"/> parameter is null.
+ /// </exception>
+ public override bool Equals(object obj) {
+ AttributeValues other = obj as AttributeValues;
+ if (other == null) {
+ return false;
+ }
+
+ if (this.TypeUri != other.TypeUri) {
+ return false;
+ }
+
+ if (!MessagingUtilities.AreEquivalent<string>(this.Values, other.Values)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Serves as a hash function for a particular type.
+ /// </summary>
+ /// <returns>
+ /// A hash code for the current <see cref="T:System.Object"/>.
+ /// </returns>
+ public override int GetHashCode() {
+ int hashCode = 0;
+ unchecked {
+ if (this.TypeUri != null) {
+ hashCode += this.TypeUri.GetHashCode();
+ }
+
+ foreach (string value in this.Values) {
+ hashCode += value.GetHashCode();
+ }
+ }
+
+ return hashCode;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/Constants.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/Constants.cs
new file mode 100644
index 0000000..0f13306
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/Constants.cs
@@ -0,0 +1,18 @@
+//-----------------------------------------------------------------------
+// <copyright file="Constants.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange {
+ /// <summary>
+ /// Attribute Exchange constants
+ /// </summary>
+ internal static class Constants {
+ /// <summary>
+ /// The TypeURI by which the AX extension is recognized in
+ /// OpenID messages and in XRDS documents.
+ /// </summary>
+ internal const string TypeUri = "http://openid.net/srv/ax/1.0";
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/FetchRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/FetchRequest.cs
new file mode 100644
index 0000000..ff47ee6
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/FetchRequest.cs
@@ -0,0 +1,285 @@
+//-----------------------------------------------------------------------
+// <copyright file="FetchRequest.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange {
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.ObjectModel;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.Linq;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// The Attribute Exchange Fetch message, request leg.
+ /// </summary>
+ [Serializable]
+ public sealed class FetchRequest : ExtensionBase, IMessageWithEvents {
+ /// <summary>
+ /// The factory method that may be used in deserialization of this message.
+ /// </summary>
+ internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => {
+ if (typeUri == Constants.TypeUri && isProviderRole) {
+ string mode;
+ if (data.TryGetValue("mode", out mode) && mode == Mode) {
+ return new FetchRequest();
+ }
+ }
+
+ return null;
+ };
+
+ /// <summary>
+ /// Characters that may not appear in an attribute alias list.
+ /// </summary>
+ internal static readonly char[] IllegalAliasListCharacters = new[] { '.', '\n' };
+
+ /// <summary>
+ /// Characters that may not appear in an attribute Type URI alias.
+ /// </summary>
+ internal static readonly char[] IllegalAliasCharacters = new[] { '.', ',', ':' };
+
+ /// <summary>
+ /// The value for the 'mode' parameter.
+ /// </summary>
+ [MessagePart("mode", IsRequired = true)]
+ private const string Mode = "fetch_request";
+
+ /// <summary>
+ /// The collection of requested attributes.
+ /// </summary>
+ private readonly KeyedCollection<string, AttributeRequest> attributes = new KeyedCollectionDelegate<string, AttributeRequest>(ar => ar.TypeUri);
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="FetchRequest"/> class.
+ /// </summary>
+ public FetchRequest()
+ : base(new Version(1, 0), Constants.TypeUri, null) {
+ }
+
+ /// <summary>
+ /// Gets a collection of the attributes whose values are
+ /// requested by the Relying Party.
+ /// </summary>
+ /// <value>A collection where the keys are the attribute type URIs, and the value
+ /// is all the attribute request details.</value>
+ public KeyedCollection<string, AttributeRequest> Attributes {
+ get {
+ Contract.Ensures(Contract.Result<KeyedCollection<string, AttributeRequest>>() != null);
+ return this.attributes;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the URL that the OpenID Provider may re-post the fetch response
+ /// message to at some time after the initial response has been sent, using an
+ /// OpenID Authentication Positive Assertion to inform the relying party of updates
+ /// to the requested fields.
+ /// </summary>
+ [MessagePart("update_url", IsRequired = false)]
+ public Uri UpdateUrl { get; set; }
+
+ /// <summary>
+ /// Gets or sets a list of aliases for optional attributes.
+ /// </summary>
+ /// <value>A comma-delimited list of aliases.</value>
+ [MessagePart("if_available", IsRequired = false)]
+ private string OptionalAliases { get; set; }
+
+ /// <summary>
+ /// Gets or sets a list of aliases for required attributes.
+ /// </summary>
+ /// <value>A comma-delimited list of aliases.</value>
+ [MessagePart("required", IsRequired = false)]
+ private string RequiredAliases { get; set; }
+
+ /// <summary>
+ /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
+ /// </summary>
+ /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
+ /// <returns>
+ /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
+ /// </returns>
+ /// <exception cref="T:System.NullReferenceException">
+ /// The <paramref name="obj"/> parameter is null.
+ /// </exception>
+ public override bool Equals(object obj) {
+ FetchRequest other = obj as FetchRequest;
+ if (other == null) {
+ return false;
+ }
+
+ if (this.Version != other.Version) {
+ return false;
+ }
+
+ if (this.UpdateUrl != other.UpdateUrl) {
+ return false;
+ }
+
+ if (!MessagingUtilities.AreEquivalentUnordered(this.Attributes.ToList(), other.Attributes.ToList())) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Serves as a hash function for a particular type.
+ /// </summary>
+ /// <returns>
+ /// A hash code for the current <see cref="T:System.Object"/>.
+ /// </returns>
+ public override int GetHashCode() {
+ unchecked {
+ int hashCode = this.Version.GetHashCode();
+
+ if (this.UpdateUrl != null) {
+ hashCode += this.UpdateUrl.GetHashCode();
+ }
+
+ foreach (AttributeRequest att in this.Attributes) {
+ hashCode += att.GetHashCode();
+ }
+
+ return hashCode;
+ }
+ }
+
+ #region IMessageWithEvents Members
+
+ /// <summary>
+ /// Called when the message is about to be transmitted,
+ /// before it passes through the channel binding elements.
+ /// </summary>
+ void IMessageWithEvents.OnSending() {
+ var fields = ((IMessage)this).ExtraData;
+ fields.Clear();
+
+ List<string> requiredAliases = new List<string>(), optionalAliases = new List<string>();
+ AliasManager aliasManager = new AliasManager();
+ foreach (var att in this.attributes) {
+ string alias = aliasManager.GetAlias(att.TypeUri);
+
+ // define the alias<->typeUri mapping
+ fields.Add("type." + alias, att.TypeUri);
+
+ // set how many values the relying party wants max
+ fields.Add("count." + alias, att.Count.ToString(CultureInfo.InvariantCulture));
+
+ if (att.IsRequired) {
+ requiredAliases.Add(alias);
+ } else {
+ optionalAliases.Add(alias);
+ }
+ }
+
+ // Set optional/required lists
+ this.OptionalAliases = optionalAliases.Count > 0 ? string.Join(",", optionalAliases.ToArray()) : null;
+ this.RequiredAliases = requiredAliases.Count > 0 ? string.Join(",", requiredAliases.ToArray()) : null;
+ }
+
+ /// <summary>
+ /// Called when the message has been received,
+ /// after it passes through the channel binding elements.
+ /// </summary>
+ void IMessageWithEvents.OnReceiving() {
+ var extraData = ((IMessage)this).ExtraData;
+ var requiredAliases = ParseAliasList(this.RequiredAliases);
+ var optionalAliases = ParseAliasList(this.OptionalAliases);
+
+ // if an alias shows up in both lists, an exception will result implicitly.
+ var allAliases = new List<string>(requiredAliases.Count + optionalAliases.Count);
+ allAliases.AddRange(requiredAliases);
+ allAliases.AddRange(optionalAliases);
+ if (allAliases.Count == 0) {
+ Logger.OpenId.Error("Attribute Exchange extension did not provide any aliases in the if_available or required lists.");
+ return;
+ }
+
+ AliasManager aliasManager = new AliasManager();
+ foreach (var alias in allAliases) {
+ string attributeTypeUri;
+ if (extraData.TryGetValue("type." + alias, out attributeTypeUri)) {
+ aliasManager.SetAlias(alias, attributeTypeUri);
+ AttributeRequest att = new AttributeRequest {
+ TypeUri = attributeTypeUri,
+ IsRequired = requiredAliases.Contains(alias),
+ };
+ string countString;
+ if (extraData.TryGetValue("count." + alias, out countString)) {
+ if (countString == "unlimited") {
+ att.Count = int.MaxValue;
+ } else {
+ int count;
+ if (int.TryParse(countString, out count) && count > 0) {
+ att.Count = count;
+ } else {
+ Logger.OpenId.Error("count." + alias + " could not be parsed into a positive integer.");
+ }
+ }
+ } else {
+ att.Count = 1;
+ }
+ this.Attributes.Add(att);
+ } else {
+ Logger.OpenId.Error("Type URI definition of alias " + alias + " is missing.");
+ }
+ }
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ /// <remarks>
+ /// <para>Some messages have required fields, or combinations of fields that must relate to each other
+ /// in specialized ways. After deserializing a message, this method checks the state of the
+ /// message to see if it conforms to the protocol.</para>
+ /// <para>Note that this property should <i>not</i> check signatures or perform any state checks
+ /// outside this scope of this particular message.</para>
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception>
+ protected override void EnsureValidMessage() {
+ base.EnsureValidMessage();
+
+ if (this.UpdateUrl != null && !this.UpdateUrl.IsAbsoluteUri) {
+ this.UpdateUrl = null;
+ Logger.OpenId.ErrorFormat("The AX fetch request update_url parameter was not absolute ('{0}'). Ignoring value.", this.UpdateUrl);
+ }
+
+ if (this.OptionalAliases != null) {
+ if (this.OptionalAliases.IndexOfAny(IllegalAliasListCharacters) >= 0) {
+ Logger.OpenId.Error("Illegal characters found in Attribute Exchange if_available alias list. Ignoring value.");
+ this.OptionalAliases = null;
+ }
+ }
+
+ if (this.RequiredAliases != null) {
+ if (this.RequiredAliases.IndexOfAny(IllegalAliasListCharacters) >= 0) {
+ Logger.OpenId.Error("Illegal characters found in Attribute Exchange required alias list. Ignoring value.");
+ this.RequiredAliases = null;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Splits a list of aliases by their commas.
+ /// </summary>
+ /// <param name="aliasList">The comma-delimited list of aliases. May be null or empty.</param>
+ /// <returns>The list of aliases. Never null, but may be empty.</returns>
+ private static IList<string> ParseAliasList(string aliasList) {
+ if (string.IsNullOrEmpty(aliasList)) {
+ return EmptyList<string>.Instance;
+ }
+
+ return aliasList.Split(',');
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/FetchResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/FetchResponse.cs
new file mode 100644
index 0000000..d5633c3
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/FetchResponse.cs
@@ -0,0 +1,207 @@
+//-----------------------------------------------------------------------
+// <copyright file="FetchResponse.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange {
+ using System;
+ using System.Collections.ObjectModel;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// The Attribute Exchange Fetch message, response leg.
+ /// </summary>
+ [Serializable]
+ public sealed class FetchResponse : ExtensionBase, IMessageWithEvents {
+ /// <summary>
+ /// The factory method that may be used in deserialization of this message.
+ /// </summary>
+ internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => {
+ if (typeUri == Constants.TypeUri && !isProviderRole) {
+ string mode;
+ if (data.TryGetValue("mode", out mode) && mode == Mode) {
+ return new FetchResponse();
+ }
+ }
+
+ return null;
+ };
+
+ /// <summary>
+ /// The value of the 'mode' parameter.
+ /// </summary>
+ [MessagePart("mode", IsRequired = true)]
+ private const string Mode = "fetch_response";
+
+ /// <summary>
+ /// The collection of provided attributes. This field will never be null.
+ /// </summary>
+ private readonly KeyedCollection<string, AttributeValues> attributesProvided = new KeyedCollectionDelegate<string, AttributeValues>(av => av.TypeUri);
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="FetchResponse"/> class.
+ /// </summary>
+ public FetchResponse()
+ : base(new Version(1, 0), Constants.TypeUri, null) {
+ }
+
+ /// <summary>
+ /// Gets a sequence of the attributes whose values are provided by the OpenID Provider.
+ /// </summary>
+ public KeyedCollection<string, AttributeValues> Attributes {
+ get {
+ Contract.Ensures(Contract.Result<KeyedCollection<string, AttributeValues>>() != null);
+ return this.attributesProvided;
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether the OpenID Provider intends to
+ /// honor the request for updates.
+ /// </summary>
+ public bool UpdateUrlSupported {
+ get { return this.UpdateUrl != null; }
+ }
+
+ /// <summary>
+ /// Gets or sets the URL the OpenID Provider will post updates to.
+ /// Must be set if the Provider supports and will use this feature.
+ /// </summary>
+ [MessagePart("update_url", IsRequired = false)]
+ public Uri UpdateUrl { get; set; }
+
+ /// <summary>
+ /// Gets a value indicating whether this extension is signed by the Provider.
+ /// </summary>
+ /// <value>
+ /// <c>true</c> if this instance is signed by the Provider; otherwise, <c>false</c>.
+ /// </value>
+ public bool IsSignedByProvider {
+ get { return this.IsSignedByRemoteParty; }
+ }
+
+ /// <summary>
+ /// Gets the first attribute value provided for a given attribute Type URI.
+ /// </summary>
+ /// <param name="typeUri">
+ /// The type URI of the attribute.
+ /// Usually a constant from <see cref="WellKnownAttributes"/>.</param>
+ /// <returns>
+ /// The first value provided for the attribute, or <c>null</c> if the attribute is missing or no values were provided.
+ /// </returns>
+ /// <remarks>
+ /// This is meant as a helper method for the common case of just wanting one attribute value.
+ /// For greater flexibility or to retrieve more than just the first value for an attribute,
+ /// use the <see cref="Attributes"/> collection directly.
+ /// </remarks>
+ public string GetAttributeValue(string typeUri) {
+ if (this.Attributes.Contains(typeUri)) {
+ return this.Attributes[typeUri].Values.FirstOrDefault();
+ } else {
+ return null;
+ }
+ }
+
+ /// <summary>
+ /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
+ /// </summary>
+ /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
+ /// <returns>
+ /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
+ /// </returns>
+ /// <exception cref="T:System.NullReferenceException">
+ /// The <paramref name="obj"/> parameter is null.
+ /// </exception>
+ public override bool Equals(object obj) {
+ FetchResponse other = obj as FetchResponse;
+ if (other == null) {
+ return false;
+ }
+
+ if (this.Version != other.Version) {
+ return false;
+ }
+
+ if (this.UpdateUrl != other.UpdateUrl) {
+ return false;
+ }
+
+ if (!MessagingUtilities.AreEquivalentUnordered(this.Attributes.ToList(), other.Attributes.ToList())) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Serves as a hash function for a particular type.
+ /// </summary>
+ /// <returns>
+ /// A hash code for the current <see cref="T:System.Object"/>.
+ /// </returns>
+ public override int GetHashCode() {
+ unchecked {
+ int hashCode = this.Version.GetHashCode();
+
+ if (this.UpdateUrl != null) {
+ hashCode += this.UpdateUrl.GetHashCode();
+ }
+
+ foreach (AttributeValues value in this.Attributes) {
+ hashCode += value.GetHashCode();
+ }
+
+ return hashCode;
+ }
+ }
+
+ #region IMessageWithEvents Members
+
+ /// <summary>
+ /// Called when the message is about to be transmitted,
+ /// before it passes through the channel binding elements.
+ /// </summary>
+ void IMessageWithEvents.OnSending() {
+ var extraData = ((IMessage)this).ExtraData;
+ AXUtilities.SerializeAttributes(extraData, this.attributesProvided);
+ }
+
+ /// <summary>
+ /// Called when the message has been received,
+ /// after it passes through the channel binding elements.
+ /// </summary>
+ void IMessageWithEvents.OnReceiving() {
+ var extraData = ((IMessage)this).ExtraData;
+ foreach (var att in AXUtilities.DeserializeAttributes(extraData)) {
+ this.Attributes.Add(att);
+ }
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ /// <remarks>
+ /// <para>Some messages have required fields, or combinations of fields that must relate to each other
+ /// in specialized ways. After deserializing a message, this method checks the state of the
+ /// message to see if it conforms to the protocol.</para>
+ /// <para>Note that this property should <i>not</i> check signatures or perform any state checks
+ /// outside this scope of this particular message.</para>
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception>
+ protected override void EnsureValidMessage() {
+ base.EnsureValidMessage();
+
+ if (this.UpdateUrl != null && !this.UpdateUrl.IsAbsoluteUri) {
+ this.UpdateUrl = null;
+ Logger.OpenId.ErrorFormat("The AX fetch response update_url parameter was not absolute ('{0}'). Ignoring value.", this.UpdateUrl);
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/StoreRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/StoreRequest.cs
new file mode 100644
index 0000000..57ce43a
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/StoreRequest.cs
@@ -0,0 +1,127 @@
+//-----------------------------------------------------------------------
+// <copyright file="StoreRequest.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange {
+ using System;
+ using System.Collections.ObjectModel;
+ using System.Linq;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// The Attribute Exchange Store message, request leg.
+ /// </summary>
+ [Serializable]
+ public sealed class StoreRequest : ExtensionBase, IMessageWithEvents {
+ /// <summary>
+ /// The factory method that may be used in deserialization of this message.
+ /// </summary>
+ internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => {
+ if (typeUri == Constants.TypeUri && isProviderRole) {
+ string mode;
+ if (data.TryGetValue("mode", out mode) && mode == Mode) {
+ return new StoreRequest();
+ }
+ }
+
+ return null;
+ };
+
+ /// <summary>
+ /// The value of the 'mode' parameter.
+ /// </summary>
+ [MessagePart("mode", IsRequired = true)]
+ private const string Mode = "store_request";
+
+ /// <summary>
+ /// The collection of provided attribute values. This field will never be null.
+ /// </summary>
+ private readonly KeyedCollection<string, AttributeValues> attributesProvided = new KeyedCollectionDelegate<string, AttributeValues>(av => av.TypeUri);
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StoreRequest"/> class.
+ /// </summary>
+ public StoreRequest()
+ : base(new Version(1, 0), Constants.TypeUri, null) {
+ }
+
+ /// <summary>
+ /// Gets the collection of all the attributes that are included in the store request.
+ /// </summary>
+ public KeyedCollection<string, AttributeValues> Attributes {
+ get { return this.attributesProvided; }
+ }
+
+ #region IMessageWithEvents Members
+
+ /// <summary>
+ /// Called when the message is about to be transmitted,
+ /// before it passes through the channel binding elements.
+ /// </summary>
+ void IMessageWithEvents.OnSending() {
+ var fields = ((IMessage)this).ExtraData;
+ fields.Clear();
+
+ AXUtilities.SerializeAttributes(fields, this.attributesProvided);
+ }
+
+ /// <summary>
+ /// Called when the message has been received,
+ /// after it passes through the channel binding elements.
+ /// </summary>
+ void IMessageWithEvents.OnReceiving() {
+ var fields = ((IMessage)this).ExtraData;
+ foreach (var att in AXUtilities.DeserializeAttributes(fields)) {
+ this.Attributes.Add(att);
+ }
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
+ /// </summary>
+ /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
+ /// <returns>
+ /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
+ /// </returns>
+ /// <exception cref="T:System.NullReferenceException">
+ /// The <paramref name="obj"/> parameter is null.
+ /// </exception>
+ public override bool Equals(object obj) {
+ var other = obj as StoreRequest;
+ if (other == null) {
+ return false;
+ }
+
+ if (this.Version != other.Version) {
+ return false;
+ }
+
+ if (!MessagingUtilities.AreEquivalentUnordered(this.Attributes.ToList(), other.Attributes.ToList())) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Serves as a hash function for a particular type.
+ /// </summary>
+ /// <returns>
+ /// A hash code for the current <see cref="T:System.Object"/>.
+ /// </returns>
+ public override int GetHashCode() {
+ unchecked {
+ int hashCode = this.Version.GetHashCode();
+ foreach (AttributeValues att in this.Attributes) {
+ hashCode += att.GetHashCode();
+ }
+ return hashCode;
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/StoreResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/StoreResponse.cs
new file mode 100644
index 0000000..aff6bd6
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/StoreResponse.cs
@@ -0,0 +1,164 @@
+//-----------------------------------------------------------------------
+// <copyright file="StoreResponse.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange {
+ using System;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// The Attribute Exchange Store message, response leg.
+ /// </summary>
+ [Serializable]
+ public sealed class StoreResponse : ExtensionBase {
+ /// <summary>
+ /// The factory method that may be used in deserialization of this message.
+ /// </summary>
+ internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => {
+ if (typeUri == Constants.TypeUri && !isProviderRole) {
+ string mode;
+ if (data.TryGetValue("mode", out mode) && (mode == SuccessMode || mode == FailureMode)) {
+ return new StoreResponse();
+ }
+ }
+
+ return null;
+ };
+
+ /// <summary>
+ /// The value of the mode parameter used to express a successful store operation.
+ /// </summary>
+ private const string SuccessMode = "store_response_success";
+
+ /// <summary>
+ /// The value of the mode parameter used to express a store operation failure.
+ /// </summary>
+ private const string FailureMode = "store_response_failure";
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StoreResponse"/> class
+ /// to represent a successful store operation.
+ /// </summary>
+ public StoreResponse()
+ : base(new Version(1, 0), Constants.TypeUri, null) {
+ this.Succeeded = true;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StoreResponse"/> class
+ /// to represent a failed store operation.
+ /// </summary>
+ /// <param name="failureReason">The reason for failure.</param>
+ public StoreResponse(string failureReason)
+ : this() {
+ this.Succeeded = false;
+ this.FailureReason = failureReason;
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the storage request succeeded.
+ /// </summary>
+ /// <value>Defaults to <c>true</c>.</value>
+ public bool Succeeded {
+ get { return this.Mode == SuccessMode; }
+ set { this.Mode = value ? SuccessMode : FailureMode; }
+ }
+
+ /// <summary>
+ /// Gets or sets the reason for the failure, if applicable.
+ /// </summary>
+ [MessagePart("error", IsRequired = false)]
+ public string FailureReason { get; set; }
+
+ /// <summary>
+ /// Gets a value indicating whether this extension is signed by the Provider.
+ /// </summary>
+ /// <value>
+ /// <c>true</c> if this instance is signed by the Provider; otherwise, <c>false</c>.
+ /// </value>
+ public bool IsSignedByProvider {
+ get { return this.IsSignedByRemoteParty; }
+ }
+
+ /// <summary>
+ /// Gets or sets the mode argument.
+ /// </summary>
+ /// <value>One of 'store_response_success' or 'store_response_failure'.</value>
+ [MessagePart("mode", IsRequired = true)]
+ private string Mode { get; set; }
+
+ /// <summary>
+ /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
+ /// </summary>
+ /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
+ /// <returns>
+ /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
+ /// </returns>
+ /// <exception cref="T:System.NullReferenceException">
+ /// The <paramref name="obj"/> parameter is null.
+ /// </exception>
+ public override bool Equals(object obj) {
+ var other = obj as StoreResponse;
+ if (other == null) {
+ return false;
+ }
+
+ if (this.Version != other.Version) {
+ return false;
+ }
+
+ if (this.Succeeded != other.Succeeded) {
+ return false;
+ }
+
+ if (this.FailureReason != other.FailureReason) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Serves as a hash function for a particular type.
+ /// </summary>
+ /// <returns>
+ /// A hash code for the current <see cref="T:System.Object"/>.
+ /// </returns>
+ public override int GetHashCode() {
+ unchecked {
+ int hashCode = this.Version.GetHashCode();
+ hashCode += this.Succeeded ? 0 : 1;
+ if (this.FailureReason != null) {
+ hashCode += this.FailureReason.GetHashCode();
+ }
+
+ return hashCode;
+ }
+ }
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ /// <remarks>
+ /// <para>Some messages have required fields, or combinations of fields that must relate to each other
+ /// in specialized ways. After deserializing a message, this method checks the state of the
+ /// message to see if it conforms to the protocol.</para>
+ /// <para>Note that this property should <i>not</i> check signatures or perform any state checks
+ /// outside this scope of this particular message.</para>
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception>
+ protected override void EnsureValidMessage() {
+ base.EnsureValidMessage();
+
+ ErrorUtilities.VerifyProtocol(
+ this.Mode == SuccessMode || this.Mode == FailureMode,
+ MessagingStrings.UnexpectedMessagePartValue,
+ "mode",
+ this.Mode);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/WellKnownAttributes.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/WellKnownAttributes.cs
new file mode 100644
index 0000000..e96ef2e
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/AttributeExchange/WellKnownAttributes.cs
@@ -0,0 +1,325 @@
+//-----------------------------------------------------------------------
+// <copyright file="WellKnownAttributes.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange {
+ using System.Diagnostics.CodeAnalysis;
+
+ /// <summary>
+ /// Attribute types defined at http://www.axschema.org/types/.
+ /// </summary>
+ /// <remarks>
+ /// If you don't see what you need here, check that URL to see if any have been added.
+ /// You can use new ones directly without adding them to this class, and can even make
+ /// up your own if you expect the other end to understand what you make up.
+ /// </remarks>
+ [SuppressMessage("Microsoft.StyleCop.CSharp.DocumentationRules", "SA1630:DocumentationTextMustContainWhitespace", Justification = "The samples are string literals.")]
+ [SuppressMessage("Microsoft.StyleCop.CSharp.DocumentationRules", "SA1631:DocumentationMustMeetCharacterPercentage", Justification = "The samples are string literals.")]
+ public static class WellKnownAttributes {
+ /// <summary>
+ /// Inherent attributes about a personality such as gender and bio.
+ /// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Required for desired autocompletion.")]
+ public static class Person {
+ /// <summary>Gender, either "M" or "F"</summary>
+ /// <example>"M", "F"</example>
+ public const string Gender = "http://axschema.org/person/gender";
+
+ /// <summary>Biography (text)</summary>
+ /// <example>"I am the very model of a modern Major General."</example>
+ public const string Biography = "http://axschema.org/media/biography";
+ }
+
+ /// <summary>
+ /// Preferences such as language and timezone.
+ /// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Required for desired autocompletion.")]
+ public static class Preferences {
+ /// <summary>Preferred language, as per RFC4646</summary>
+ /// <example>"en-US"</example>
+ public const string Language = "http://axschema.org/pref/language";
+
+ /// <summary>Home time zone information (as specified in <a href="http://en.wikipedia.org/wiki/List_of_tz_zones_by_name">zoneinfo</a>)</summary>
+ /// <example>"America/Pacific"</example>
+ public const string TimeZone = "http://axschema.org/pref/timezone";
+ }
+
+ /// <summary>
+ /// The names a person goes by.
+ /// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Required for desired autocompletion.")]
+ public static class Name {
+ /// <summary>Subject's alias or "screen" name</summary>
+ /// <example>"Johnny5"</example>
+ public const string Alias = "http://axschema.org/namePerson/friendly";
+
+ /// <summary>Full name of subject</summary>
+ /// <example>"John Doe"</example>
+ public const string FullName = "http://axschema.org/namePerson";
+
+ /// <summary>Honorific prefix for the subject's name</summary>
+ /// <example>"Mr.", "Mrs.", "Dr."</example>
+ public const string Prefix = "http://axschema.org/namePerson/prefix";
+
+ /// <summary>First or given name of subject</summary>
+ /// <example>"John"</example>
+ public const string First = "http://axschema.org/namePerson/first";
+
+ /// <summary>Last name or surname of subject</summary>
+ /// <example>"Smith"</example>
+ public const string Last = "http://axschema.org/namePerson/last";
+
+ /// <summary>Middle name(s) of subject</summary>
+ /// <example>"Robert"</example>
+ public const string Middle = "http://axschema.org/namePerson/middle";
+
+ /// <summary>Suffix of subject's name</summary>
+ /// <example>"III", "Jr."</example>
+ public const string Suffix = "http://axschema.org/namePerson/suffix";
+ }
+
+ /// <summary>
+ /// Business affiliation.
+ /// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Required for desired autocompletion.")]
+ public static class Company {
+ /// <summary>Company name (employer)</summary>
+ /// <example>"Springfield Power"</example>
+ public const string CompanyName = "http://axschema.org/company/name";
+
+ /// <summary>Employee title</summary>
+ /// <example>"Engineer"</example>
+ public const string JobTitle = "http://axschema.org/company/title";
+ }
+
+ /// <summary>
+ /// Information about a person's birthdate.
+ /// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Required for desired autocompletion.")]
+ public static class BirthDate {
+ /// <summary>Date of birth.</summary>
+ /// <example>"1979-01-01"</example>
+ public const string WholeBirthDate = "http://axschema.org/birthDate";
+
+ /// <summary>Year of birth (four digits)</summary>
+ /// <example>"1979"</example>
+ public const string Year = "http://axschema.org/birthDate/birthYear";
+
+ /// <summary>Month of birth (1-12)</summary>
+ /// <example>"05"</example>
+ public const string Month = "http://axschema.org/birthDate/birthMonth";
+
+ /// <summary>Day of birth</summary>
+ /// <example>"31"</example>
+ public const string DayOfMonth = "http://axschema.org/birthDate/birthday";
+ }
+
+ /// <summary>
+ /// Various ways to contact a person.
+ /// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Required for desired autocompletion.")]
+ public static class Contact {
+ /// <summary>Internet SMTP email address as per RFC2822</summary>
+ /// <example>"jsmith@isp.example.com"</example>
+ public const string Email = "http://axschema.org/contact/email";
+
+ /// <summary>
+ /// Various types of phone numbers.
+ /// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Required for desired autocompletion.")]
+ public static class Phone {
+ /// <summary>Main phone number (preferred)</summary>
+ /// <example>+1-800-555-1234</example>
+ public const string Preferred = "http://axschema.org/contact/phone/default";
+
+ /// <summary>Home phone number</summary>
+ /// <example>+1-800-555-1234</example>
+ public const string Home = "http://axschema.org/contact/phone/home";
+
+ /// <summary>Business phone number</summary>
+ /// <example>+1-800-555-1234</example>
+ public const string Work = "http://axschema.org/contact/phone/business";
+
+ /// <summary>Cellular (or mobile) phone number</summary>
+ /// <example>+1-800-555-1234</example>
+ public const string Mobile = "http://axschema.org/contact/phone/cell";
+
+ /// <summary>Fax number</summary>
+ /// <example>+1-800-555-1234</example>
+ public const string Fax = "http://axschema.org/contact/phone/fax";
+ }
+
+ /// <summary>
+ /// The many fields that make up an address.
+ /// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Required for desired autocompletion.")]
+ public static class HomeAddress {
+ /// <summary>Home postal address: street number, name and apartment number</summary>
+ /// <example>"#42 135 East 1st Street"</example>
+ public const string StreetAddressLine1 = "http://axschema.org/contact/postalAddress/home";
+
+ /// <summary>"#42 135 East 1st Street"</summary>
+ /// <example>"Box 67"</example>
+ public const string StreetAddressLine2 = "http://axschema.org/contact/postalAddressAdditional/home";
+
+ /// <summary>Home city name</summary>
+ /// <example>"Vancouver"</example>
+ public const string City = "http://axschema.org/contact/city/home";
+
+ /// <summary>Home state or province name</summary>
+ /// <example>"BC"</example>
+ public const string State = "http://axschema.org/contact/state/home";
+
+ /// <summary>Home country code in ISO.3166.1988 (alpha 2) format</summary>
+ /// <example>"CA"</example>
+ public const string Country = "http://axschema.org/contact/country/home";
+
+ /// <summary>Home postal code; region specific format</summary>
+ /// <example>"V5A 4B2"</example>
+ public const string PostalCode = "http://axschema.org/contact/postalCode/home";
+ }
+
+ /// <summary>
+ /// The many fields that make up an address.
+ /// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Required for desired autocompletion.")]
+ public static class WorkAddress {
+ /// <summary>Business postal address: street number, name and apartment number</summary>
+ /// <example>"#42 135 East 1st Street"</example>
+ public const string StreetAddressLine1 = "http://axschema.org/contact/postalAddress/business";
+
+ /// <summary>"#42 135 East 1st Street"</summary>
+ /// <example>"Box 67"</example>
+ public const string StreetAddressLine2 = "http://axschema.org/contact/postalAddressAdditional/business";
+
+ /// <summary>Business city name</summary>
+ /// <example>"Vancouver"</example>
+ public const string City = "http://axschema.org/contact/city/business";
+
+ /// <summary>Business state or province name</summary>
+ /// <example>"BC"</example>
+ public const string State = "http://axschema.org/contact/state/business";
+
+ /// <summary>Business country code in ISO.3166.1988 (alpha 2) format</summary>
+ /// <example>"CA"</example>
+ public const string Country = "http://axschema.org/contact/country/business";
+
+ /// <summary>Business postal code; region specific format</summary>
+ /// <example>"V5A 4B2"</example>
+ public const string PostalCode = "http://axschema.org/contact/postalCode/business";
+ }
+
+ /// <summary>
+ /// Various handles for instant message clients.
+ /// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Required for desired autocompletion.")]
+ public static class IM {
+ /// <summary>AOL instant messaging service handle</summary>
+ /// <example>"jsmith421234"</example>
+ [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "AOL", Justification = "By design")]
+ public const string AOL = "http://axschema.org/contact/IM/AIM";
+
+ /// <summary>ICQ instant messaging service handle</summary>
+ /// <example>"1234567"</example>
+ [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "ICQ", Justification = "By design")]
+ public const string ICQ = "http://axschema.org/contact/IM/ICQ";
+
+ /// <summary>MSN instant messaging service handle</summary>
+ /// <example>"jsmith42@hotmail.com"</example>
+ [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "MSN", Justification = "By design")]
+ public const string MSN = "http://axschema.org/contact/IM/MSN";
+
+ /// <summary>Yahoo! instant messaging service handle</summary>
+ /// <example>"jsmith421234"</example>
+ public const string Yahoo = "http://axschema.org/contact/IM/Yahoo";
+
+ /// <summary>Jabber instant messaging service handle</summary>
+ /// <example>"jsmith@jabber.example.com"</example>
+ public const string Jabber = "http://axschema.org/contact/IM/Jabber";
+
+ /// <summary>Skype instant messaging service handle</summary>
+ /// <example>"jsmith42"</example>
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Skype", Justification = "By design")]
+ public const string Skype = "http://axschema.org/contact/IM/Skype";
+ }
+
+ /// <summary>
+ /// Various web addresses connected with this personality.
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces", Justification = "By design"), SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Required for desired autocompletion.")]
+ public static class Web {
+ /// <summary>Web site URL</summary>
+ /// <example>"http://example.com/~jsmith/"</example>
+ public const string Homepage = "http://axschema.org/contact/web/default";
+
+ /// <summary>Blog home page URL</summary>
+ /// <example>"http://example.com/jsmith_blog/"</example>
+ public const string Blog = "http://axschema.org/contact/web/blog";
+
+ /// <summary>LinkedIn URL</summary>
+ /// <example>"http://www.linkedin.com/pub/1/234/56"</example>
+ public const string LinkedIn = "http://axschema.org/contact/web/Linkedin";
+
+ /// <summary>Amazon URL</summary>
+ /// <example>"http://www.amazon.com/gp/pdp/profile/A24DLKJ825"</example>
+ public const string Amazon = "http://axschema.org/contact/web/Amazon";
+
+ /// <summary>Flickr URL</summary>
+ /// <example>"http://flickr.com/photos/jsmith42/"</example>
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Flickr", Justification = "By design")]
+ public const string Flickr = "http://axschema.org/contact/web/Flickr";
+
+ /// <summary>del.icio.us URL</summary>
+ /// <example>"http://del.icio.us/jsmith42"</example>
+ public const string Delicious = "http://axschema.org/contact/web/Delicious";
+ }
+ }
+
+ /// <summary>
+ /// Audio and images of this personality.
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces", Justification = "By design"), SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Required for desired autocompletion.")]
+ public static class Media {
+ /// <summary>Spoken name (web URL)</summary>
+ /// <example>"http://example.com/~jsmith/john_smith.wav"</example>
+ public const string SpokenName = "http://axschema.org/media/spokenname";
+
+ /// <summary>Audio greeting (web URL)</summary>
+ /// <example>"http://example.com/~jsmith/i_greet_you.wav"</example>
+ public const string AudioGreeting = "http://axschema.org/media/greeting/audio";
+
+ /// <summary>Video greeting (web URL)</summary>
+ /// <example>"http://example.com/~jsmith/i_greet_you.mov"</example>
+ public const string VideoGreeting = "http://axschema.org/media/greeting/video";
+
+ /// <summary>
+ /// Images of this personality.
+ /// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Required for desired autocompletion.")]
+ public static class Images {
+ /// <summary>Image (web URL); unspecified dimension</summary>
+ /// <example>"http://example.com/~jsmith/image.jpg"</example>
+ public const string Default = "http://axschema.org/media/image/default";
+
+ /// <summary>Image (web URL) with equal width and height</summary>
+ /// <example>"http://example.com/~jsmith/image.jpg"</example>
+ public const string Aspect11 = "http://axschema.org/media/image/aspect11";
+
+ /// <summary>Image (web URL) 4:3 aspect ratio - landscape</summary>
+ /// <example>"http://example.com/~jsmith/image.jpg"</example>
+ public const string Aspect43 = "http://axschema.org/media/image/aspect43";
+
+ /// <summary>Image (web URL) 4:3 aspect ratio - landscape</summary>
+ /// <example>"http://example.com/~jsmith/image.jpg"</example>
+ public const string Aspect34 = "http://axschema.org/media/image/aspect34";
+
+ /// <summary>Image (web URL); favicon format as per FAVICON-W3C. The format for the image must be 16x16 pixels or 32x32 pixels, using either 8-bit or 24-bit colors. The format of the image must be one of PNG (a W3C standard), GIF, or ICO.</summary>
+ /// <example>"http://example.com/~jsmith/image.jpg"</example>
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Fav", Justification = "By design")]
+ public const string FavIcon = "http://axschema.org/media/image/favicon";
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ExtensionArgumentsManager.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ExtensionArgumentsManager.cs
new file mode 100644
index 0000000..1d795da
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ExtensionArgumentsManager.cs
@@ -0,0 +1,223 @@
+//-----------------------------------------------------------------------
+// <copyright file="ExtensionArgumentsManager.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// Manages the processing and construction of OpenID extensions parts.
+ /// </summary>
+ internal class ExtensionArgumentsManager {
+ /// <summary>
+ /// This contains a set of aliases that we must be willing to implicitly
+ /// match to namespaces for backward compatibility with other OpenID libraries.
+ /// </summary>
+ private static readonly Dictionary<string, string> typeUriToAliasAffinity = new Dictionary<string, string> {
+ { Extensions.SimpleRegistration.Constants.TypeUris.Standard, Extensions.SimpleRegistration.Constants.sreg_compatibility_alias },
+ { Extensions.ProviderAuthenticationPolicy.Constants.TypeUri, Extensions.ProviderAuthenticationPolicy.Constants.CompatibilityAlias },
+ };
+
+ /// <summary>
+ /// The version of OpenID that the message is using.
+ /// </summary>
+ private Protocol protocol;
+
+ /// <summary>
+ /// Whether extensions are being read or written.
+ /// </summary>
+ private bool isReadMode;
+
+ /// <summary>
+ /// The alias manager that will track Type URI to alias mappings.
+ /// </summary>
+ private AliasManager aliasManager = new AliasManager();
+
+ /// <summary>
+ /// A complex dictionary where the key is the Type URI of the extension,
+ /// and the value is another dictionary of the name/value args of the extension.
+ /// </summary>
+ private Dictionary<string, IDictionary<string, string>> extensions = new Dictionary<string, IDictionary<string, string>>();
+
+ /// <summary>
+ /// Prevents a default instance of the <see cref="ExtensionArgumentsManager"/> class from being created.
+ /// </summary>
+ private ExtensionArgumentsManager() { }
+
+ /// <summary>
+ /// Gets a value indicating whether the extensions are being read (as opposed to written).
+ /// </summary>
+ internal bool ReadMode {
+ get { return this.isReadMode; }
+ }
+
+ /// <summary>
+ /// Creates a <see cref="ExtensionArgumentsManager"/> instance to process incoming extensions.
+ /// </summary>
+ /// <param name="query">The parameters in the OpenID message.</param>
+ /// <returns>The newly created instance of <see cref="ExtensionArgumentsManager"/>.</returns>
+ public static ExtensionArgumentsManager CreateIncomingExtensions(IDictionary<string, string> query) {
+ Requires.NotNull(query, "query");
+ var mgr = new ExtensionArgumentsManager();
+ mgr.protocol = Protocol.Detect(query);
+ mgr.isReadMode = true;
+ string aliasPrefix = mgr.protocol.openid.ns + ".";
+
+ // First pass looks for namespace aliases
+ foreach (var pair in query) {
+ if (pair.Key.StartsWith(aliasPrefix, StringComparison.Ordinal)) {
+ mgr.aliasManager.SetAlias(pair.Key.Substring(aliasPrefix.Length), pair.Value);
+ }
+ }
+
+ // For backwards compatibility, add certain aliases if they aren't defined.
+ if (mgr.protocol.Version.Major < 2) {
+ foreach (var pair in typeUriToAliasAffinity) {
+ if (!mgr.aliasManager.IsAliasAssignedTo(pair.Key) &&
+ !mgr.aliasManager.IsAliasUsed(pair.Value)) {
+ mgr.aliasManager.SetAlias(pair.Value, pair.Key);
+ }
+ }
+ }
+
+ // Second pass looks for extensions using those aliases
+ foreach (var pair in query) {
+ if (!pair.Key.StartsWith(mgr.protocol.openid.Prefix, StringComparison.Ordinal)) {
+ continue;
+ }
+ string possibleAlias = pair.Key.Substring(mgr.protocol.openid.Prefix.Length);
+ int periodIndex = possibleAlias.IndexOf(".", StringComparison.Ordinal);
+ if (periodIndex >= 0) {
+ possibleAlias = possibleAlias.Substring(0, periodIndex);
+ }
+ string typeUri;
+ if ((typeUri = mgr.aliasManager.TryResolveAlias(possibleAlias)) != null) {
+ if (!mgr.extensions.ContainsKey(typeUri)) {
+ mgr.extensions[typeUri] = new Dictionary<string, string>();
+ }
+ string key = periodIndex >= 0 ? pair.Key.Substring(mgr.protocol.openid.Prefix.Length + possibleAlias.Length + 1) : string.Empty;
+ mgr.extensions[typeUri].Add(key, pair.Value);
+ }
+ }
+ return mgr;
+ }
+
+ /// <summary>
+ /// Creates a <see cref="ExtensionArgumentsManager"/> instance to prepare outgoing extensions.
+ /// </summary>
+ /// <param name="protocol">The protocol version used for the outgoing message.</param>
+ /// <returns>
+ /// The newly created instance of <see cref="ExtensionArgumentsManager"/>.
+ /// </returns>
+ public static ExtensionArgumentsManager CreateOutgoingExtensions(Protocol protocol) {
+ var mgr = new ExtensionArgumentsManager();
+ mgr.protocol = protocol;
+
+ // Affinity for certain alias for backwards compatibility
+ foreach (var pair in typeUriToAliasAffinity) {
+ mgr.aliasManager.SetAlias(pair.Value, pair.Key);
+ }
+ return mgr;
+ }
+
+ /// <summary>
+ /// Adds query parameters for OpenID extensions to the request directed
+ /// at the OpenID provider.
+ /// </summary>
+ /// <param name="extensionTypeUri">The extension type URI.</param>
+ /// <param name="arguments">The arguments for this extension to add to the message.</param>
+ public void AddExtensionArguments(string extensionTypeUri, IDictionary<string, string> arguments) {
+ Requires.ValidState(!this.ReadMode);
+ Requires.NotNullOrEmpty(extensionTypeUri, "extensionTypeUri");
+ Requires.NotNull(arguments, "arguments");
+ if (arguments.Count == 0) {
+ return;
+ }
+
+ IDictionary<string, string> extensionArgs;
+ if (!this.extensions.TryGetValue(extensionTypeUri, out extensionArgs)) {
+ this.extensions.Add(extensionTypeUri, extensionArgs = new Dictionary<string, string>(arguments.Count));
+ }
+
+ ErrorUtilities.VerifyProtocol(extensionArgs.Count == 0, OpenIdStrings.ExtensionAlreadyAddedWithSameTypeURI, extensionTypeUri);
+ foreach (var pair in arguments) {
+ extensionArgs.Add(pair.Key, pair.Value);
+ }
+ }
+
+ /// <summary>
+ /// Gets the actual arguments to add to a querystring or other response,
+ /// where type URI, alias, and actual key/values are all defined.
+ /// </summary>
+ /// <param name="includeOpenIdPrefix">
+ /// <c>true</c> if the generated parameter names should include the 'openid.' prefix.
+ /// This should be <c>true</c> for all but direct response messages.
+ /// </param>
+ /// <returns>A dictionary of key=value pairs to add to the message to carry the extension.</returns>
+ internal IDictionary<string, string> GetArgumentsToSend(bool includeOpenIdPrefix) {
+ Requires.ValidState(!this.ReadMode);
+ Dictionary<string, string> args = new Dictionary<string, string>();
+ foreach (var typeUriAndExtension in this.extensions) {
+ string typeUri = typeUriAndExtension.Key;
+ var extensionArgs = typeUriAndExtension.Value;
+ if (extensionArgs.Count == 0) {
+ continue;
+ }
+ string alias = this.aliasManager.GetAlias(typeUri);
+
+ // send out the alias declaration
+ string openidPrefix = includeOpenIdPrefix ? this.protocol.openid.Prefix : string.Empty;
+ args.Add(openidPrefix + this.protocol.openidnp.ns + "." + alias, typeUri);
+ string prefix = openidPrefix + alias;
+ foreach (var pair in extensionArgs) {
+ string key = prefix;
+ if (pair.Key.Length > 0) {
+ key += "." + pair.Key;
+ }
+ args.Add(key, pair.Value);
+ }
+ }
+ return args;
+ }
+
+ /// <summary>
+ /// Gets the fields carried by a given OpenId extension.
+ /// </summary>
+ /// <param name="extensionTypeUri">The type URI of the extension whose fields are being queried for.</param>
+ /// <returns>
+ /// The fields included in the given extension, or null if the extension is not present.
+ /// </returns>
+ internal IDictionary<string, string> GetExtensionArguments(string extensionTypeUri) {
+ Requires.NotNullOrEmpty(extensionTypeUri, "extensionTypeUri");
+ Requires.ValidState(this.ReadMode);
+
+ IDictionary<string, string> extensionArgs;
+ this.extensions.TryGetValue(extensionTypeUri, out extensionArgs);
+ return extensionArgs;
+ }
+
+ /// <summary>
+ /// Gets whether any arguments for a given extension are present.
+ /// </summary>
+ /// <param name="extensionTypeUri">The extension Type URI in question.</param>
+ /// <returns><c>true</c> if this extension is present; <c>false</c> otherwise.</returns>
+ internal bool ContainsExtension(string extensionTypeUri) {
+ return this.extensions.ContainsKey(extensionTypeUri);
+ }
+
+ /// <summary>
+ /// Gets the type URIs of all discovered extensions in the message.
+ /// </summary>
+ /// <returns>A sequence of the type URIs.</returns>
+ internal IEnumerable<string> GetExtensionTypeUris() {
+ return this.extensions.Keys;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ExtensionBase.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ExtensionBase.cs
new file mode 100644
index 0000000..09d690a
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ExtensionBase.cs
@@ -0,0 +1,190 @@
+//-----------------------------------------------------------------------
+// <copyright file="ExtensionBase.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// A handy base class for built-in extensions.
+ /// </summary>
+ [Serializable]
+ public class ExtensionBase : IOpenIdMessageExtension {
+ /// <summary>
+ /// Backing store for the <see cref="IOpenIdMessageExtension.TypeUri"/> property.
+ /// </summary>
+ private string typeUri;
+
+ /// <summary>
+ /// Backing store for the <see cref="IOpenIdMessageExtension.AdditionalSupportedTypeUris"/> property.
+ /// </summary>
+ private IEnumerable<string> additionalSupportedTypeUris;
+
+ /// <summary>
+ /// Backing store for the <see cref="IMessage.ExtraData"/> property.
+ /// </summary>
+ private Dictionary<string, string> extraData = new Dictionary<string, string>();
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ExtensionBase"/> class.
+ /// </summary>
+ /// <param name="version">The version of the extension.</param>
+ /// <param name="typeUri">The type URI to use in the OpenID message.</param>
+ /// <param name="additionalSupportedTypeUris">The additional supported type URIs by which this extension might be recognized. May be null.</param>
+ protected ExtensionBase(Version version, string typeUri, IEnumerable<string> additionalSupportedTypeUris) {
+ this.Version = version;
+ this.typeUri = typeUri;
+ this.additionalSupportedTypeUris = additionalSupportedTypeUris ?? EmptyList<string>.Instance;
+ }
+
+ #region IOpenIdProtocolMessageExtension Members
+
+ /// <summary>
+ /// Gets the TypeURI the extension uses in the OpenID protocol and in XRDS advertisements.
+ /// </summary>
+ string IOpenIdMessageExtension.TypeUri {
+ get { return this.TypeUri; }
+ }
+
+ /// <summary>
+ /// Gets the additional TypeURIs that are supported by this extension, in preferred order.
+ /// May be empty if none other than <see cref="IOpenIdMessageExtension.TypeUri"/> is supported, but
+ /// should not be null.
+ /// </summary>
+ /// <remarks>
+ /// Useful for reading in messages with an older version of an extension.
+ /// The value in the <see cref="IOpenIdMessageExtension.TypeUri"/> property is always checked before
+ /// trying this list.
+ /// If you do support multiple versions of an extension using this method,
+ /// consider adding a CreateResponse method to your request extension class
+ /// so that the response can have the context it needs to remain compatible
+ /// given the version of the extension in the request message.
+ /// The <see cref="SimpleRegistration.ClaimsRequest.CreateResponse"/> for an example.
+ /// </remarks>
+ IEnumerable<string> IOpenIdMessageExtension.AdditionalSupportedTypeUris {
+ get { return this.AdditionalSupportedTypeUris; }
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this extension was
+ /// signed by the OpenID Provider.
+ /// </summary>
+ /// <value>
+ /// <c>true</c> if this instance is signed by the provider; otherwise, <c>false</c>.
+ /// </value>
+ bool IOpenIdMessageExtension.IsSignedByRemoteParty {
+ get { return this.IsSignedByRemoteParty; }
+ set { this.IsSignedByRemoteParty = value; }
+ }
+
+ #endregion
+
+ #region IMessage Properties
+
+ /// <summary>
+ /// Gets the version of the protocol or extension this message is prepared to implement.
+ /// </summary>
+ public Version Version { get; private set; }
+
+ /// <summary>
+ /// Gets the extra, non-standard Protocol parameters included in the message.
+ /// </summary>
+ /// <remarks>
+ /// Implementations of this interface should ensure that this property never returns null.
+ /// </remarks>
+ IDictionary<string, string> IMessage.ExtraData {
+ get { return this.ExtraData; }
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Gets the TypeURI the extension uses in the OpenID protocol and in XRDS advertisements.
+ /// </summary>
+ protected string TypeUri {
+ get { return this.typeUri; }
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this extension was
+ /// signed by the OpenID Provider.
+ /// </summary>
+ /// <value>
+ /// <c>true</c> if this instance is signed by the provider; otherwise, <c>false</c>.
+ /// </value>
+ protected bool IsSignedByRemoteParty { get; set; }
+
+ /// <summary>
+ /// Gets the additional TypeURIs that are supported by this extension, in preferred order.
+ /// May be empty if none other than <see cref="IOpenIdMessageExtension.TypeUri"/> is supported, but
+ /// should not be null.
+ /// </summary>
+ /// <value></value>
+ /// <remarks>
+ /// Useful for reading in messages with an older version of an extension.
+ /// The value in the <see cref="IOpenIdMessageExtension.TypeUri"/> property is always checked before
+ /// trying this list.
+ /// If you do support multiple versions of an extension using this method,
+ /// consider adding a CreateResponse method to your request extension class
+ /// so that the response can have the context it needs to remain compatible
+ /// given the version of the extension in the request message.
+ /// The <see cref="Extensions.SimpleRegistration.ClaimsRequest.CreateResponse"/> for an example.
+ /// </remarks>
+ protected IEnumerable<string> AdditionalSupportedTypeUris {
+ get { return this.additionalSupportedTypeUris; }
+ }
+
+ /// <summary>
+ /// Gets the extra, non-standard Protocol parameters included in the message.
+ /// </summary>
+ /// <value></value>
+ /// <remarks>
+ /// Implementations of this interface should ensure that this property never returns null.
+ /// </remarks>
+ protected IDictionary<string, string> ExtraData {
+ get { return this.extraData; }
+ }
+
+ #region IMessage Methods
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ /// <remarks>
+ /// <para>Some messages have required fields, or combinations of fields that must relate to each other
+ /// in specialized ways. After deserializing a message, this method checks the state of the
+ /// message to see if it conforms to the protocol.</para>
+ /// <para>Note that this property should <i>not</i> check signatures or perform any state checks
+ /// outside this scope of this particular message.</para>
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception>
+ void IMessage.EnsureValidMessage() {
+ this.EnsureValidMessage();
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ /// <remarks>
+ /// <para>Some messages have required fields, or combinations of fields that must relate to each other
+ /// in specialized ways. After deserializing a message, this method checks the state of the
+ /// message to see if it conforms to the protocol.</para>
+ /// <para>Note that this property should <i>not</i> check signatures or perform any state checks
+ /// outside this scope of this particular message.</para>
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception>
+ protected virtual void EnsureValidMessage() {
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/IClientScriptExtensionResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/IClientScriptExtensionResponse.cs
new file mode 100644
index 0000000..d60e78e
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/IClientScriptExtensionResponse.cs
@@ -0,0 +1,32 @@
+//-----------------------------------------------------------------------
+// <copyright file="IClientScriptExtensionResponse.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions {
+ using System.Collections.Generic;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// An interface that OpenID extensions can implement to allow authentication response
+ /// messages with included extensions to be processed by Javascript on the user agent.
+ /// </summary>
+ public interface IClientScriptExtensionResponse : IExtensionMessage {
+ /// <summary>
+ /// Reads the extension information on an authentication response from the provider.
+ /// </summary>
+ /// <param name="response">The incoming OpenID response carrying the extension.</param>
+ /// <returns>
+ /// A Javascript snippet that when executed on the user agent returns an object with
+ /// the information deserialized from the extension response.
+ /// </returns>
+ /// <remarks>
+ /// This method is called <b>before</b> the signature on the assertion response has been
+ /// verified. Therefore all information in these fields should be assumed unreliable
+ /// and potentially falsified.
+ /// </remarks>
+ string InitializeJavaScriptData(IProtocolMessageWithExtensions response);
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OAuth/AuthorizationApprovedResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OAuth/AuthorizationApprovedResponse.cs
new file mode 100644
index 0000000..2e0f468
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OAuth/AuthorizationApprovedResponse.cs
@@ -0,0 +1,48 @@
+//-----------------------------------------------------------------------
+// <copyright file="AuthorizationApprovedResponse.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.OAuth {
+ using System;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// The OAuth response that a Provider may include with a positive
+ /// OpenID identity assertion with an approved request token.
+ /// </summary>
+ [Serializable]
+ public class AuthorizationApprovedResponse : ExtensionBase {
+ /// <summary>
+ /// The factory method that may be used in deserialization of this message.
+ /// </summary>
+ internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => {
+ if (typeUri == Constants.TypeUri && !isProviderRole && data.ContainsKey(Constants.RequestTokenParameter)) {
+ return new AuthorizationApprovedResponse();
+ }
+
+ return null;
+ };
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AuthorizationApprovedResponse"/> class.
+ /// </summary>
+ public AuthorizationApprovedResponse()
+ : base(new Version(1, 0), Constants.TypeUri, null) {
+ }
+
+ /// <summary>
+ /// Gets or sets the user-approved request token.
+ /// </summary>
+ /// <value>The request token.</value>
+ [MessagePart(Constants.RequestTokenParameter, IsRequired = true, AllowEmpty = false)]
+ public string RequestToken { get; set; }
+
+ /// <summary>
+ /// Gets or sets a string that encodes, in a way possibly specific to the Combined Provider, one or more scopes that the returned request token is valid for. This will typically indicate a subset of the scopes requested in Section 8.
+ /// </summary>
+ [MessagePart("scope", IsRequired = false, AllowEmpty = true)]
+ public string Scope { get; set; }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OAuth/AuthorizationDeclinedResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OAuth/AuthorizationDeclinedResponse.cs
new file mode 100644
index 0000000..3925a1d
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OAuth/AuthorizationDeclinedResponse.cs
@@ -0,0 +1,34 @@
+//-----------------------------------------------------------------------
+// <copyright file="AuthorizationDeclinedResponse.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.OAuth {
+ using System;
+
+ /// <summary>
+ /// The OAuth response that a Provider should include with a positive
+ /// OpenID identity assertion when OAuth authorization was declined.
+ /// </summary>
+ [Serializable]
+ public class AuthorizationDeclinedResponse : ExtensionBase {
+ /// <summary>
+ /// The factory method that may be used in deserialization of this message.
+ /// </summary>
+ internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => {
+ if (typeUri == Constants.TypeUri && !isProviderRole && !data.ContainsKey(Constants.RequestTokenParameter)) {
+ return new AuthorizationDeclinedResponse();
+ }
+
+ return null;
+ };
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AuthorizationDeclinedResponse"/> class.
+ /// </summary>
+ public AuthorizationDeclinedResponse()
+ : base(new Version(1, 0), Constants.TypeUri, null) {
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OAuth/AuthorizationRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OAuth/AuthorizationRequest.cs
new file mode 100644
index 0000000..9e0116a
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OAuth/AuthorizationRequest.cs
@@ -0,0 +1,57 @@
+//-----------------------------------------------------------------------
+// <copyright file="AuthorizationRequest.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.OAuth {
+ using System;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// An extension to include with an authentication request in order to also
+ /// obtain authorization to access user data at the combined OpenID Provider
+ /// and Service Provider.
+ /// </summary>
+ /// <remarks>
+ /// <para>When requesting OpenID Authentication via the protocol mode "checkid_setup"
+ /// or "checkid_immediate", this extension can be used to request that the end
+ /// user authorize an OAuth access token at the same time as an OpenID
+ /// authentication. This is done by sending the following parameters as part
+ /// of the OpenID request. (Note that the use of "oauth" as part of the parameter
+ /// names here and in subsequent sections is just an example. See Section 5 for details.)</para>
+ /// <para>See section 8.</para>
+ /// </remarks>
+ [Serializable]
+ public class AuthorizationRequest : ExtensionBase {
+ /// <summary>
+ /// The factory method that may be used in deserialization of this message.
+ /// </summary>
+ internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => {
+ if (typeUri == Constants.TypeUri && isProviderRole) {
+ return new AuthorizationRequest();
+ }
+
+ return null;
+ };
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AuthorizationRequest"/> class.
+ /// </summary>
+ public AuthorizationRequest()
+ : base(new Version(1, 0), Constants.TypeUri, null) {
+ }
+
+ /// <summary>
+ /// Gets or sets the consumer key agreed upon between the Consumer and Service Provider.
+ /// </summary>
+ [MessagePart("consumer", IsRequired = true, AllowEmpty = false)]
+ public string Consumer { get; set; }
+
+ /// <summary>
+ /// Gets or sets a string that encodes, in a way possibly specific to the Combined Provider, one or more scopes for the OAuth token expected in the authentication response.
+ /// </summary>
+ [MessagePart("scope", IsRequired = false)]
+ public string Scope { get; set; }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OAuth/Constants.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OAuth/Constants.cs
new file mode 100644
index 0000000..e439aff
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OAuth/Constants.cs
@@ -0,0 +1,22 @@
+//-----------------------------------------------------------------------
+// <copyright file="Constants.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.OAuth {
+ /// <summary>
+ /// Constants used in the OpenID OAuth extension.
+ /// </summary>
+ internal static class Constants {
+ /// <summary>
+ /// The TypeURI for the OpenID OAuth extension.
+ /// </summary>
+ internal const string TypeUri = "http://specs.openid.net/extensions/oauth/1.0";
+
+ /// <summary>
+ /// The name of the parameter that carries the request token in the response.
+ /// </summary>
+ internal const string RequestTokenParameter = "request_token";
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OpenIdExtensionFactoryAggregator.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OpenIdExtensionFactoryAggregator.cs
new file mode 100644
index 0000000..2b851dd
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OpenIdExtensionFactoryAggregator.cs
@@ -0,0 +1,81 @@
+//-----------------------------------------------------------------------
+// <copyright file="OpenIdExtensionFactoryAggregator.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions {
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.ChannelElements;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// An OpenID extension factory that only delegates extension
+ /// instantiation requests to other factories.
+ /// </summary>
+ internal class OpenIdExtensionFactoryAggregator : IOpenIdExtensionFactory {
+ /// <summary>
+ /// The list of factories this factory delegates to.
+ /// </summary>
+ private List<IOpenIdExtensionFactory> factories = new List<IOpenIdExtensionFactory>(2);
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OpenIdExtensionFactoryAggregator"/> class.
+ /// </summary>
+ internal OpenIdExtensionFactoryAggregator() {
+ }
+
+ /// <summary>
+ /// Gets the extension factories that this aggregating factory delegates to.
+ /// </summary>
+ /// <value>A list of factories. May be empty, but never null.</value>
+ internal IList<IOpenIdExtensionFactory> Factories {
+ get { return this.factories; }
+ }
+
+ #region IOpenIdExtensionFactory Members
+
+ /// <summary>
+ /// Creates a new instance of some extension based on the received extension parameters.
+ /// </summary>
+ /// <param name="typeUri">The type URI of the extension.</param>
+ /// <param name="data">The parameters associated specifically with this extension.</param>
+ /// <param name="baseMessage">The OpenID message carrying this extension.</param>
+ /// <param name="isProviderRole">A value indicating whether this extension is being received at the OpenID Provider.</param>
+ /// <returns>
+ /// An instance of <see cref="IOpenIdMessageExtension"/> if the factory recognizes
+ /// the extension described in the input parameters; <c>null</c> otherwise.
+ /// </returns>
+ /// <remarks>
+ /// This factory method need only initialize properties in the instantiated extension object
+ /// that are not bound using <see cref="MessagePartAttribute"/>.
+ /// </remarks>
+ public IOpenIdMessageExtension Create(string typeUri, IDictionary<string, string> data, IProtocolMessageWithExtensions baseMessage, bool isProviderRole) {
+ foreach (var factory in this.factories) {
+ IOpenIdMessageExtension result = factory.Create(typeUri, data, baseMessage, isProviderRole);
+ if (result != null) {
+ return result;
+ }
+ }
+
+ return null;
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Loads the default factory and additional ones given by the configuration.
+ /// </summary>
+ /// <returns>A new instance of <see cref="OpenIdExtensionFactoryAggregator"/>.</returns>
+ internal static OpenIdExtensionFactoryAggregator LoadFromConfiguration() {
+ Contract.Ensures(Contract.Result<OpenIdExtensionFactoryAggregator>() != null);
+ var factoriesElement = DotNetOpenAuth.Configuration.OpenIdElement.Configuration.ExtensionFactories;
+ var aggregator = new OpenIdExtensionFactoryAggregator();
+ aggregator.Factories.Add(new StandardOpenIdExtensionFactory());
+ aggregator.factories.AddRange(factoriesElement.CreateInstances(false));
+ return aggregator;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OpenIdExtensionsInteropHelper.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OpenIdExtensionsInteropHelper.cs
new file mode 100644
index 0000000..f9d3062
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/OpenIdExtensionsInteropHelper.cs
@@ -0,0 +1,124 @@
+//-----------------------------------------------------------------------
+// <copyright file="OpenIdExtensionsInteropHelper.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Extensions.AttributeExchange;
+ using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// A set of methods designed to assist in improving interop across different
+ /// OpenID implementations and their extensions.
+ /// </summary>
+ internal static class OpenIdExtensionsInteropHelper {
+ /// <summary>
+ /// The gender decoder to translate AX genders to Sreg.
+ /// </summary>
+ private static GenderEncoder genderEncoder = new GenderEncoder();
+
+ /// <summary>
+ /// Gets the gender decoder to translate AX genders to Sreg.
+ /// </summary>
+ internal static GenderEncoder GenderEncoder {
+ get { return genderEncoder; }
+ }
+
+ /// <summary>
+ /// Splits the AX attribute format flags into individual values for processing.
+ /// </summary>
+ /// <param name="formats">The formats to split up into individual flags.</param>
+ /// <returns>A sequence of individual flags.</returns>
+ internal static IEnumerable<AXAttributeFormats> ForEachFormat(AXAttributeFormats formats) {
+ if ((formats & AXAttributeFormats.AXSchemaOrg) != 0) {
+ yield return AXAttributeFormats.AXSchemaOrg;
+ }
+
+ if ((formats & AXAttributeFormats.OpenIdNetSchema) != 0) {
+ yield return AXAttributeFormats.OpenIdNetSchema;
+ }
+
+ if ((formats & AXAttributeFormats.SchemaOpenIdNet) != 0) {
+ yield return AXAttributeFormats.SchemaOpenIdNet;
+ }
+ }
+
+ /// <summary>
+ /// Transforms an AX attribute type URI from the axschema.org format into a given format.
+ /// </summary>
+ /// <param name="axSchemaOrgFormatTypeUri">The ax schema org format type URI.</param>
+ /// <param name="targetFormat">The target format. Only one flag should be set.</param>
+ /// <returns>The AX attribute type URI in the target format.</returns>
+ internal static string TransformAXFormat(string axSchemaOrgFormatTypeUri, AXAttributeFormats targetFormat) {
+ Requires.NotNullOrEmpty(axSchemaOrgFormatTypeUri, "axSchemaOrgFormatTypeUri");
+
+ switch (targetFormat) {
+ case AXAttributeFormats.AXSchemaOrg:
+ return axSchemaOrgFormatTypeUri;
+ case AXAttributeFormats.SchemaOpenIdNet:
+ return axSchemaOrgFormatTypeUri.Replace("axschema.org", "schema.openid.net");
+ case AXAttributeFormats.OpenIdNetSchema:
+ return axSchemaOrgFormatTypeUri.Replace("axschema.org", "openid.net/schema");
+ default:
+ throw new ArgumentOutOfRangeException("targetFormat");
+ }
+ }
+
+ /// <summary>
+ /// Detects the AX attribute type URI format from a given sample.
+ /// </summary>
+ /// <param name="typeURIs">The type URIs to scan for recognized formats.</param>
+ /// <returns>The first AX type URI format recognized in the list.</returns>
+ internal static AXAttributeFormats DetectAXFormat(IEnumerable<string> typeURIs) {
+ Requires.NotNull(typeURIs, "typeURIs");
+
+ if (typeURIs.Any(uri => uri.StartsWith("http://axschema.org/", StringComparison.Ordinal))) {
+ return AXAttributeFormats.AXSchemaOrg;
+ }
+
+ if (typeURIs.Any(uri => uri.StartsWith("http://schema.openid.net/", StringComparison.Ordinal))) {
+ return AXAttributeFormats.SchemaOpenIdNet;
+ }
+
+ if (typeURIs.Any(uri => uri.StartsWith("http://openid.net/schema/", StringComparison.Ordinal))) {
+ return AXAttributeFormats.OpenIdNetSchema;
+ }
+
+ return AXAttributeFormats.None;
+ }
+
+ /// <summary>
+ /// Adds an attribute fetch request if it is not already present in the AX request.
+ /// </summary>
+ /// <param name="ax">The AX request to add the attribute request to.</param>
+ /// <param name="format">The format of the attribute's Type URI to use.</param>
+ /// <param name="axSchemaOrgFormatAttribute">The attribute in axschema.org format.</param>
+ /// <param name="demandLevel">The demand level.</param>
+ internal static void FetchAttribute(FetchRequest ax, AXAttributeFormats format, string axSchemaOrgFormatAttribute, DemandLevel demandLevel) {
+ Requires.NotNull(ax, "ax");
+ Requires.NotNullOrEmpty(axSchemaOrgFormatAttribute, "axSchemaOrgFormatAttribute");
+
+ string typeUri = TransformAXFormat(axSchemaOrgFormatAttribute, format);
+ if (!ax.Attributes.Contains(typeUri)) {
+ switch (demandLevel) {
+ case DemandLevel.Request:
+ ax.Attributes.AddOptional(typeUri);
+ break;
+ case DemandLevel.Require:
+ ax.Attributes.AddRequired(typeUri);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs
new file mode 100644
index 0000000..cb44d63
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs
@@ -0,0 +1,70 @@
+//-----------------------------------------------------------------------
+// <copyright file="AuthenticationPolicies.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy {
+ using System.Diagnostics.CodeAnalysis;
+
+ /// <summary>
+ /// Well-known authentication policies defined in the PAPE extension spec or by a recognized
+ /// standards body.
+ /// </summary>
+ /// <remarks>
+ /// This is a class of constants rather than a flags enum because policies may be
+ /// freely defined and used by anyone, just by using a new Uri.
+ /// </remarks>
+ public static class AuthenticationPolicies {
+ /// <summary>
+ /// An authentication mechanism where the End User does not provide a shared secret to a party potentially under the control of the Relying Party. (Note that the potentially malicious Relying Party controls where the User-Agent is redirected to and thus may not send it to the End User's actual OpenID Provider).
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Phishing", Justification = "By design")]
+ public const string PhishingResistant = "http://schemas.openid.net/pape/policies/2007/06/phishing-resistant";
+
+ /// <summary>
+ /// An authentication mechanism where the End User authenticates to the OpenID Provider by providing over one authentication factor. Common authentication factors are something you know, something you have, and something you are. An example would be authentication using a password and a software token or digital certificate.
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi", Justification = "By design")]
+ [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "MultiFactor", Justification = "By design")]
+ public const string MultiFactor = "http://schemas.openid.net/pape/policies/2007/06/multi-factor";
+
+ /// <summary>
+ /// An authentication mechanism where the End User authenticates to the OpenID Provider by providing over one authentication factor where at least one of the factors is a physical factor such as a hardware device or biometric. Common authentication factors are something you know, something you have, and something you are. This policy also implies the Multi-Factor Authentication policy (http://schemas.openid.net/pape/policies/2007/06/multi-factor) and both policies MAY BE specified in conjunction without conflict. An example would be authentication using a password and a hardware token.
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "MultiFactor", Justification = "By design")]
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi", Justification = "By design")]
+ public const string PhysicalMultiFactor = "http://schemas.openid.net/pape/policies/2007/06/multi-factor-physical";
+
+ /// <summary>
+ /// Indicates that the Provider MUST use a pair-wise pseudonym for the user that is persistent
+ /// and unique across the requesting realm as the openid.claimed_id and openid.identity (see Section 4.2).
+ /// </summary>
+ public const string PrivatePersonalIdentifier = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier";
+
+ /// <summary>
+ /// Indicates that the OP MUST only respond with a positive assertion if the requirements demonstrated
+ /// by the OP to obtain certification by a Federally adopted Trust Framework Provider have been met.
+ /// </summary>
+ /// <remarks>
+ /// Notwithstanding the RP may request this authentication policy, the RP MUST still
+ /// verify that this policy appears in the positive assertion response rather than assume the OP
+ /// recognized and complied with the request.
+ /// </remarks>
+ public const string USGovernmentTrustLevel1 = "http://www.idmanagement.gov/schema/2009/05/icam/openid-trust-level1.pdf";
+
+ /// <summary>
+ /// Indicates that the OP MUST not include any OpenID Attribute Exchange or Simple Registration
+ /// information regarding the user in the assertion.
+ /// </summary>
+ public const string NoPersonallyIdentifiableInformation = "http://www.idmanagement.gov/schema/2009/05/icam/no-pii.pdf";
+
+ /// <summary>
+ /// Used in a PAPE response to indicate that no PAPE authentication policies could be satisfied.
+ /// </summary>
+ /// <remarks>
+ /// Used internally by the PAPE extension, so that users don't have to know about it.
+ /// </remarks>
+ internal const string None = "http://schemas.openid.net/pape/policies/2007/06/none";
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/Constants.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/Constants.cs
new file mode 100644
index 0000000..33dd990
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/Constants.cs
@@ -0,0 +1,77 @@
+//-----------------------------------------------------------------------
+// <copyright file="Constants.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy {
+ using System;
+ using System.Collections.Generic;
+ using System.Text;
+
+ /// <summary>
+ /// OpenID Provider Authentication Policy extension constants.
+ /// </summary>
+ internal static class Constants {
+ /// <summary>
+ /// The namespace used by this extension in messages.
+ /// </summary>
+ internal const string TypeUri = "http://specs.openid.net/extensions/pape/1.0";
+
+ /// <summary>
+ /// The namespace alias to use for OpenID 1.x interop, where aliases are not defined in the message.
+ /// </summary>
+ internal const string CompatibilityAlias = "pape";
+
+ /// <summary>
+ /// The string to prepend on an Auth Level Type alias definition.
+ /// </summary>
+ internal const string AuthLevelNamespaceDeclarationPrefix = "auth_level.ns.";
+
+ /// <summary>
+ /// Well-known assurance level Type URIs.
+ /// </summary>
+ internal static class AssuranceLevels {
+ /// <summary>
+ /// A mapping between the PAPE TypeURI and the alias to use if
+ /// possible for backward compatibility reasons.
+ /// </summary>
+ internal static readonly IDictionary<string, string> PreferredTypeUriToAliasMap = new Dictionary<string, string> {
+ { NistTypeUri, "nist" },
+ };
+
+ /// <summary>
+ /// The Type URI of the NIST assurance level.
+ /// </summary>
+ internal const string NistTypeUri = "http://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdf";
+ }
+
+ /// <summary>
+ /// Parameters to be included with PAPE requests.
+ /// </summary>
+ internal static class RequestParameters {
+ /// <summary>
+ /// Optional. If the End User has not actively authenticated to the OP within the number of seconds specified in a manner fitting the requested policies, the OP SHOULD authenticate the End User for this request.
+ /// </summary>
+ /// <value>Integer value greater than or equal to zero in seconds.</value>
+ /// <remarks>
+ /// The OP should realize that not adhering to the request for re-authentication most likely means that the End User will not be allowed access to the services provided by the RP. If this parameter is absent in the request, the OP should authenticate the user at its own discretion.
+ /// </remarks>
+ internal const string MaxAuthAge = "max_auth_age";
+
+ /// <summary>
+ /// Zero or more authentication policy URIs that the OP SHOULD conform to when authenticating the user. If multiple policies are requested, the OP SHOULD satisfy as many as it can.
+ /// </summary>
+ /// <value>Space separated list of authentication policy URIs.</value>
+ /// <remarks>
+ /// If no policies are requested, the RP may be interested in other information such as the authentication age.
+ /// </remarks>
+ internal const string PreferredAuthPolicies = "preferred_auth_policies";
+
+ /// <summary>
+ /// The space separated list of the name spaces of the custom Assurance Level that RP requests, in the order of its preference.
+ /// </summary>
+ internal const string PreferredAuthLevelTypes = "preferred_auth_level_types";
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/DateTimeEncoder.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/DateTimeEncoder.cs
new file mode 100644
index 0000000..621042a
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/DateTimeEncoder.cs
@@ -0,0 +1,69 @@
+//-----------------------------------------------------------------------
+// <copyright file="DateTimeEncoder.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy {
+ using System;
+ using System.Globalization;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Reflection;
+
+ /// <summary>
+ /// An encoder/decoder design for DateTimes that must conform to the PAPE spec.
+ /// </summary>
+ /// <remarks>
+ /// The timestamp MUST be formatted as specified in section 5.6 of [RFC3339] (Klyne, G. and C. Newman, “Date and Time on the Internet: Timestamps,” .), with the following restrictions:
+ /// * All times must be in the UTC timezone, indicated with a "Z".
+ /// * No fractional seconds are allowed
+ /// For example: 2005-05-15T17:11:51Z
+ /// </remarks>
+ internal class DateTimeEncoder : IMessagePartEncoder {
+ /// <summary>
+ /// An array of the date/time formats allowed by the PAPE extension.
+ /// </summary>
+ /// <remarks>
+ /// TODO: This array of formats is not yet a complete list.
+ /// </remarks>
+ private static readonly string[] PermissibleDateTimeFormats = { "yyyy-MM-ddTHH:mm:ssZ" };
+
+ #region IMessagePartEncoder Members
+
+ /// <summary>
+ /// Encodes the specified value.
+ /// </summary>
+ /// <param name="value">The value. Guaranteed to never be null.</param>
+ /// <returns>
+ /// The <paramref name="value"/> in string form, ready for message transport.
+ /// </returns>
+ public string Encode(object value) {
+ DateTime? dateTime = value as DateTime?;
+ if (dateTime.HasValue) {
+ return dateTime.Value.ToUniversalTimeSafe().ToString(PermissibleDateTimeFormats[0], CultureInfo.InvariantCulture);
+ } else {
+ return null;
+ }
+ }
+
+ /// <summary>
+ /// Decodes the specified value.
+ /// </summary>
+ /// <param name="value">The string value carried by the transport. Guaranteed to never be null, although it may be empty.</param>
+ /// <returns>
+ /// The deserialized form of the given string.
+ /// </returns>
+ /// <exception cref="FormatException">Thrown when the string value given cannot be decoded into the required object type.</exception>
+ public object Decode(string value) {
+ DateTime dateTime;
+ if (DateTime.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out dateTime) && dateTime.Kind == DateTimeKind.Utc) { // may be unspecified per our option above
+ return dateTime;
+ } else {
+ Logger.OpenId.ErrorFormat("Invalid format for message part: {0}", value);
+ return null;
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/NistAssuranceLevel.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/NistAssuranceLevel.cs
new file mode 100644
index 0000000..95977af
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/NistAssuranceLevel.cs
@@ -0,0 +1,60 @@
+//-----------------------------------------------------------------------
+// <copyright file="NistAssuranceLevel.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Text;
+
+ /// <summary>
+ /// Descriptions for NIST-defined levels of assurance that a credential
+ /// has not been compromised and therefore the extent to which an
+ /// authentication assertion can be trusted.
+ /// </summary>
+ /// <remarks>
+ /// <para>One using this enum should review the following publication for details
+ /// before asserting or interpreting what these levels signify, notwithstanding
+ /// the brief summaries attached to each level in DotNetOpenAuth documentation.
+ /// http://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdf</para>
+ /// <para>
+ /// See PAPE spec Appendix A.1.2 (NIST Assurance Levels) for high-level example classifications of authentication methods within the defined levels.
+ /// </para>
+ /// </remarks>
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Nist", Justification = "By design")]
+ public enum NistAssuranceLevel {
+ /// <summary>
+ /// Not an assurance level defined by NIST, but rather SHOULD be used to
+ /// signify that the OP recognizes the parameter and the End User
+ /// authentication did not meet the requirements of Level 1.
+ /// </summary>
+ InsufficientForLevel1 = 0,
+
+ /// <summary>
+ /// See this document for a thorough description:
+ /// http://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdf
+ /// </summary>
+ Level1 = 1,
+
+ /// <summary>
+ /// See this document for a thorough description:
+ /// http://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdf
+ /// </summary>
+ Level2 = 2,
+
+ /// <summary>
+ /// See this document for a thorough description:
+ /// http://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdf
+ /// </summary>
+ Level3 = 3,
+
+ /// <summary>
+ /// See this document for a thorough description:
+ /// http://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdf
+ /// </summary>
+ Level4 = 4,
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PapeUtilities.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PapeUtilities.cs
new file mode 100644
index 0000000..f017031
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PapeUtilities.cs
@@ -0,0 +1,65 @@
+//-----------------------------------------------------------------------
+// <copyright file="PapeUtilities.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// Utility methods for use by the PAPE extension.
+ /// </summary>
+ internal static class PapeUtilities {
+ /// <summary>
+ /// Looks at the incoming fields and figures out what the aliases and name spaces for auth level types are.
+ /// </summary>
+ /// <param name="fields">The incoming message data in which to discover TypeURIs and aliases.</param>
+ /// <returns>The <see cref="AliasManager"/> initialized with the given data.</returns>
+ internal static AliasManager FindIncomingAliases(IDictionary<string, string> fields) {
+ AliasManager aliasManager = new AliasManager();
+
+ foreach (var pair in fields) {
+ if (!pair.Key.StartsWith(Constants.AuthLevelNamespaceDeclarationPrefix, StringComparison.Ordinal)) {
+ continue;
+ }
+
+ string alias = pair.Key.Substring(Constants.AuthLevelNamespaceDeclarationPrefix.Length);
+ aliasManager.SetAlias(alias, pair.Value);
+ }
+
+ aliasManager.SetPreferredAliasesWhereNotSet(Constants.AssuranceLevels.PreferredTypeUriToAliasMap);
+
+ return aliasManager;
+ }
+
+ /// <summary>
+ /// Concatenates a sequence of strings using a space as a separator.
+ /// </summary>
+ /// <param name="values">The elements to concatenate together..</param>
+ /// <returns>The concatenated string of elements.</returns>
+ /// <exception cref="FormatException">Thrown if any element in the sequence includes a space.</exception>
+ internal static string ConcatenateListOfElements(IEnumerable<string> values) {
+ Requires.NotNull(values, "values");
+
+ StringBuilder valuesList = new StringBuilder();
+ foreach (string value in values.Distinct()) {
+ if (value.Contains(" ")) {
+ throw new FormatException(string.Format(CultureInfo.CurrentCulture, OpenIdStrings.InvalidUri, value));
+ }
+ valuesList.Append(value);
+ valuesList.Append(" ");
+ }
+ if (valuesList.Length > 0) {
+ valuesList.Length -= 1; // remove trailing space
+ }
+ return valuesList.ToString();
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyRequest.cs
new file mode 100644
index 0000000..0749205
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyRequest.cs
@@ -0,0 +1,222 @@
+//-----------------------------------------------------------------------
+// <copyright file="PolicyRequest.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy {
+ using System;
+ using System.Collections.Generic;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// The PAPE request part of an OpenID Authentication request message.
+ /// </summary>
+ [Serializable]
+ public sealed class PolicyRequest : ExtensionBase, IMessageWithEvents {
+ /// <summary>
+ /// The factory method that may be used in deserialization of this message.
+ /// </summary>
+ internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => {
+ if (typeUri == Constants.TypeUri && isProviderRole) {
+ return new PolicyRequest();
+ }
+
+ return null;
+ };
+
+ /// <summary>
+ /// The transport field for the RP's preferred authentication policies.
+ /// </summary>
+ /// <remarks>
+ /// This field is written to/read from during custom serialization.
+ /// </remarks>
+ [MessagePart("preferred_auth_policies", IsRequired = true)]
+ private string preferredPoliciesString;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PolicyRequest"/> class.
+ /// </summary>
+ public PolicyRequest()
+ : base(new Version(1, 0), Constants.TypeUri, null) {
+ this.PreferredPolicies = new List<string>(1);
+ this.PreferredAuthLevelTypes = new List<string>(1);
+ }
+
+ /// <summary>
+ /// Gets or sets the maximum acceptable time since the End User has
+ /// actively authenticated to the OP in a manner fitting the requested
+ /// policies, beyond which the Provider SHOULD authenticate the
+ /// End User for this request.
+ /// </summary>
+ /// <remarks>
+ /// The OP should realize that not adhering to the request for re-authentication
+ /// most likely means that the End User will not be allowed access to the
+ /// services provided by the RP. If this parameter is absent in the request,
+ /// the OP should authenticate the user at its own discretion.
+ /// </remarks>
+ [MessagePart("max_auth_age", IsRequired = false, Encoder = typeof(TimespanSecondsEncoder))]
+ public TimeSpan? MaximumAuthenticationAge { get; set; }
+
+ /// <summary>
+ /// Gets the list of authentication policy URIs that the OP SHOULD
+ /// conform to when authenticating the user. If multiple policies are
+ /// requested, the OP SHOULD satisfy as many as it can.
+ /// </summary>
+ /// <value>List of authentication policy URIs obtainable from
+ /// the <see cref="AuthenticationPolicies"/> class or from a custom
+ /// list.</value>
+ /// <remarks>
+ /// If no policies are requested, the RP may be interested in other
+ /// information such as the authentication age.
+ /// </remarks>
+ public IList<string> PreferredPolicies { get; private set; }
+
+ /// <summary>
+ /// Gets the namespaces of the custom Assurance Level the
+ /// Relying Party requests, in the order of its preference.
+ /// </summary>
+ public IList<string> PreferredAuthLevelTypes { get; private set; }
+
+ #region IMessageWithEvents Members
+
+ /// <summary>
+ /// Called when the message is about to be transmitted,
+ /// before it passes through the channel binding elements.
+ /// </summary>
+ void IMessageWithEvents.OnSending() {
+ var extraData = ((IMessage)this).ExtraData;
+ extraData.Clear();
+
+ this.preferredPoliciesString = SerializePolicies(this.PreferredPolicies);
+
+ if (this.PreferredAuthLevelTypes.Count > 0) {
+ AliasManager authLevelAliases = new AliasManager();
+ authLevelAliases.AssignAliases(this.PreferredAuthLevelTypes, Constants.AssuranceLevels.PreferredTypeUriToAliasMap);
+
+ // Add a definition for each Auth Level Type alias.
+ foreach (string alias in authLevelAliases.Aliases) {
+ extraData.Add(Constants.AuthLevelNamespaceDeclarationPrefix + alias, authLevelAliases.ResolveAlias(alias));
+ }
+
+ // Now use the aliases for those type URIs to list a preferred order.
+ extraData.Add(Constants.RequestParameters.PreferredAuthLevelTypes, SerializeAuthLevels(this.PreferredAuthLevelTypes, authLevelAliases));
+ }
+ }
+
+ /// <summary>
+ /// Called when the message has been received,
+ /// after it passes through the channel binding elements.
+ /// </summary>
+ void IMessageWithEvents.OnReceiving() {
+ var extraData = ((IMessage)this).ExtraData;
+
+ this.PreferredPolicies.Clear();
+ string[] preferredPolicies = this.preferredPoliciesString.Split(' ');
+ foreach (string policy in preferredPolicies) {
+ if (policy.Length > 0) {
+ this.PreferredPolicies.Add(policy);
+ }
+ }
+
+ this.PreferredAuthLevelTypes.Clear();
+ AliasManager authLevelAliases = PapeUtilities.FindIncomingAliases(extraData);
+ string preferredAuthLevelAliases;
+ if (extraData.TryGetValue(Constants.RequestParameters.PreferredAuthLevelTypes, out preferredAuthLevelAliases)) {
+ foreach (string authLevelAlias in preferredAuthLevelAliases.Split(' ')) {
+ if (authLevelAlias.Length == 0) {
+ continue;
+ }
+ this.PreferredAuthLevelTypes.Add(authLevelAliases.ResolveAlias(authLevelAlias));
+ }
+ }
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
+ /// </summary>
+ /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
+ /// <returns>
+ /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
+ /// </returns>
+ /// <exception cref="T:System.NullReferenceException">
+ /// The <paramref name="obj"/> parameter is null.
+ /// </exception>
+ public override bool Equals(object obj) {
+ PolicyRequest other = obj as PolicyRequest;
+ if (other == null) {
+ return false;
+ }
+
+ if (this.MaximumAuthenticationAge != other.MaximumAuthenticationAge) {
+ return false;
+ }
+
+ if (this.PreferredPolicies.Count != other.PreferredPolicies.Count) {
+ return false;
+ }
+
+ foreach (string policy in this.PreferredPolicies) {
+ if (!other.PreferredPolicies.Contains(policy)) {
+ return false;
+ }
+ }
+
+ if (this.PreferredAuthLevelTypes.Count != other.PreferredAuthLevelTypes.Count) {
+ return false;
+ }
+
+ foreach (string authLevel in this.PreferredAuthLevelTypes) {
+ if (!other.PreferredAuthLevelTypes.Contains(authLevel)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Serves as a hash function for a particular type.
+ /// </summary>
+ /// <returns>
+ /// A hash code for the current <see cref="T:System.Object"/>.
+ /// </returns>
+ public override int GetHashCode() {
+ // This is a poor hash function, but an site that cares will likely have a bunch
+ // of look-alike instances anyway, so a good hash function would still bunch
+ // all the instances into the same hash code.
+ if (this.MaximumAuthenticationAge.HasValue) {
+ return this.MaximumAuthenticationAge.Value.GetHashCode();
+ } else {
+ return 1;
+ }
+ }
+
+ /// <summary>
+ /// Serializes the policies as a single string per the PAPE spec..
+ /// </summary>
+ /// <param name="policies">The policies to include in the list.</param>
+ /// <returns>The concatenated string of the given policies.</returns>
+ private static string SerializePolicies(IEnumerable<string> policies) {
+ return PapeUtilities.ConcatenateListOfElements(policies);
+ }
+
+ /// <summary>
+ /// Serializes the auth levels to a list of aliases.
+ /// </summary>
+ /// <param name="preferredAuthLevelTypes">The preferred auth level types.</param>
+ /// <param name="aliases">The alias manager.</param>
+ /// <returns>A space-delimited list of aliases.</returns>
+ private static string SerializeAuthLevels(IList<string> preferredAuthLevelTypes, AliasManager aliases) {
+ var aliasList = new List<string>();
+ foreach (string typeUri in preferredAuthLevelTypes) {
+ aliasList.Add(aliases.GetAlias(typeUri));
+ }
+
+ return PapeUtilities.ConcatenateListOfElements(aliasList);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs
new file mode 100644
index 0000000..880a25e
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs
@@ -0,0 +1,282 @@
+//-----------------------------------------------------------------------
+// <copyright file="PolicyResponse.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// The PAPE response part of an OpenID Authentication response message.
+ /// </summary>
+ [Serializable]
+ public sealed class PolicyResponse : ExtensionBase, IMessageWithEvents {
+ /// <summary>
+ /// The factory method that may be used in deserialization of this message.
+ /// </summary>
+ internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => {
+ if (typeUri == Constants.TypeUri && !isProviderRole) {
+ return new PolicyResponse();
+ }
+
+ return null;
+ };
+
+ /// <summary>
+ /// The first part of a parameter name that gives the custom string value for
+ /// the assurance level. The second part of the parameter name is the alias for
+ /// that assurance level.
+ /// </summary>
+ private const string AuthLevelAliasPrefix = "auth_level.";
+
+ /// <summary>
+ /// One or more authentication policy URIs that the OP conformed to when authenticating the End User.
+ /// </summary>
+ /// <value>Space separated list of authentication policy URIs.</value>
+ /// <remarks>
+ /// If no policies were met though the OP wishes to convey other information in the response, this parameter MUST be included with the value of "none".
+ /// </remarks>
+ [MessagePart("auth_policies", IsRequired = true)]
+ private string actualPoliciesString;
+
+ /// <summary>
+ /// Backing field for the <see cref="AuthenticationTimeUtc"/> property.
+ /// </summary>
+ private DateTime? authenticationTimeUtc;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PolicyResponse"/> class.
+ /// </summary>
+ public PolicyResponse()
+ : base(new Version(1, 0), Constants.TypeUri, null) {
+ this.ActualPolicies = new List<string>(1);
+ this.AssuranceLevels = new Dictionary<string, string>(1);
+ }
+
+ /// <summary>
+ /// Gets a list of authentication policy URIs that the
+ /// OP conformed to when authenticating the End User.
+ /// </summary>
+ public IList<string> ActualPolicies { get; private set; }
+
+ /// <summary>
+ /// Gets or sets the most recent timestamp when the End User has
+ /// actively authenticated to the OP in a manner fitting the asserted policies.
+ /// </summary>
+ /// <remarks>
+ /// If the RP's request included the "openid.pape.max_auth_age" parameter
+ /// then the OP MUST include "openid.pape.auth_time" in its response.
+ /// If "openid.pape.max_auth_age" was not requested, the OP MAY choose to include
+ /// "openid.pape.auth_time" in its response.
+ /// </remarks>
+ [MessagePart("auth_time", Encoder = typeof(DateTimeEncoder))]
+ public DateTime? AuthenticationTimeUtc {
+ get {
+ return this.authenticationTimeUtc;
+ }
+
+ set {
+ Requires.True(!value.HasValue || value.Value.Kind != DateTimeKind.Unspecified, "value", OpenIdStrings.UnspecifiedDateTimeKindNotAllowed);
+
+ // Make sure that whatever is set here, it becomes UTC time.
+ if (value.HasValue) {
+ // Convert to UTC and cut to the second, since the protocol only allows for
+ // that level of precision.
+ this.authenticationTimeUtc = OpenIdUtilities.CutToSecond(value.Value.ToUniversalTimeSafe());
+ } else {
+ this.authenticationTimeUtc = null;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the Assurance Level as defined by the National
+ /// Institute of Standards and Technology (NIST) in Special Publication
+ /// 800-63 (Burr, W., Dodson, D., and W. Polk, Ed., “Electronic
+ /// Authentication Guideline,” April 2006.) [NIST_SP800‑63] corresponding
+ /// to the authentication method and policies employed by the OP when
+ /// authenticating the End User.
+ /// </summary>
+ /// <remarks>
+ /// See PAPE spec Appendix A.1.2 (NIST Assurance Levels) for high-level
+ /// example classifications of authentication methods within the defined
+ /// levels.
+ /// </remarks>
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Nist", Justification = "Acronym")]
+ public NistAssuranceLevel? NistAssuranceLevel {
+ get {
+ string levelString;
+ if (this.AssuranceLevels.TryGetValue(Constants.AssuranceLevels.NistTypeUri, out levelString)) {
+ return (NistAssuranceLevel)Enum.Parse(typeof(NistAssuranceLevel), levelString);
+ } else {
+ return null;
+ }
+ }
+
+ set {
+ if (value != null) {
+ this.AssuranceLevels[Constants.AssuranceLevels.NistTypeUri] = ((int)value).ToString(CultureInfo.InvariantCulture);
+ } else {
+ this.AssuranceLevels.Remove(Constants.AssuranceLevels.NistTypeUri);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets a dictionary where keys are the authentication level type URIs and
+ /// the values are the per authentication level defined custom value.
+ /// </summary>
+ /// <remarks>
+ /// A very common key is <see cref="Constants.AssuranceLevels.NistTypeUri"/>
+ /// and values for this key are available in <see cref="NistAssuranceLevel"/>.
+ /// </remarks>
+ public IDictionary<string, string> AssuranceLevels { get; private set; }
+
+ /// <summary>
+ /// Gets a value indicating whether this extension is signed by the Provider.
+ /// </summary>
+ /// <value>
+ /// <c>true</c> if this instance is signed by the Provider; otherwise, <c>false</c>.
+ /// </value>
+ public bool IsSignedByProvider {
+ get { return this.IsSignedByRemoteParty; }
+ }
+
+ #region IMessageWithEvents Members
+
+ /// <summary>
+ /// Called when the message is about to be transmitted,
+ /// before it passes through the channel binding elements.
+ /// </summary>
+ void IMessageWithEvents.OnSending() {
+ var extraData = ((IMessage)this).ExtraData;
+ extraData.Clear();
+
+ this.actualPoliciesString = SerializePolicies(this.ActualPolicies);
+
+ if (this.AssuranceLevels.Count > 0) {
+ AliasManager aliases = new AliasManager();
+ aliases.AssignAliases(this.AssuranceLevels.Keys, Constants.AssuranceLevels.PreferredTypeUriToAliasMap);
+
+ // Add a definition for each Auth Level Type alias.
+ foreach (string alias in aliases.Aliases) {
+ extraData.Add(Constants.AuthLevelNamespaceDeclarationPrefix + alias, aliases.ResolveAlias(alias));
+ }
+
+ // Now use the aliases for those type URIs to list the individual values.
+ foreach (var pair in this.AssuranceLevels) {
+ extraData.Add(AuthLevelAliasPrefix + aliases.GetAlias(pair.Key), pair.Value);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Called when the message has been received,
+ /// after it passes through the channel binding elements.
+ /// </summary>
+ void IMessageWithEvents.OnReceiving() {
+ var extraData = ((IMessage)this).ExtraData;
+
+ this.ActualPolicies.Clear();
+ string[] actualPolicies = this.actualPoliciesString.Split(' ');
+ foreach (string policy in actualPolicies) {
+ if (policy.Length > 0 && policy != AuthenticationPolicies.None) {
+ this.ActualPolicies.Add(policy);
+ }
+ }
+
+ this.AssuranceLevels.Clear();
+ AliasManager authLevelAliases = PapeUtilities.FindIncomingAliases(extraData);
+ foreach (string authLevelAlias in authLevelAliases.Aliases) {
+ string authValue;
+ if (extraData.TryGetValue(AuthLevelAliasPrefix + authLevelAlias, out authValue)) {
+ string authLevelType = authLevelAliases.ResolveAlias(authLevelAlias);
+ this.AssuranceLevels[authLevelType] = authValue;
+ }
+ }
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
+ /// </summary>
+ /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
+ /// <returns>
+ /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
+ /// </returns>
+ /// <exception cref="T:System.NullReferenceException">
+ /// The <paramref name="obj"/> parameter is null.
+ /// </exception>
+ public override bool Equals(object obj) {
+ PolicyResponse other = obj as PolicyResponse;
+ if (other == null) {
+ return false;
+ }
+
+ if (this.AuthenticationTimeUtc != other.AuthenticationTimeUtc) {
+ return false;
+ }
+
+ if (this.AssuranceLevels.Count != other.AssuranceLevels.Count) {
+ return false;
+ }
+
+ foreach (var pair in this.AssuranceLevels) {
+ if (!other.AssuranceLevels.Contains(pair)) {
+ return false;
+ }
+ }
+
+ if (this.ActualPolicies.Count != other.ActualPolicies.Count) {
+ return false;
+ }
+
+ foreach (string policy in this.ActualPolicies) {
+ if (!other.ActualPolicies.Contains(policy)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Serves as a hash function for a particular type.
+ /// </summary>
+ /// <returns>
+ /// A hash code for the current <see cref="T:System.Object"/>.
+ /// </returns>
+ public override int GetHashCode() {
+ // This is a poor hash function, but an site that cares will likely have a bunch
+ // of look-alike instances anyway, so a good hash function would still bunch
+ // all the instances into the same hash code.
+ if (this.AuthenticationTimeUtc.HasValue) {
+ return this.AuthenticationTimeUtc.Value.GetHashCode();
+ } else {
+ return 1;
+ }
+ }
+
+ /// <summary>
+ /// Serializes the applied policies for transmission from the Provider
+ /// to the Relying Party.
+ /// </summary>
+ /// <param name="policies">The applied policies.</param>
+ /// <returns>A space-delimited list of applied policies.</returns>
+ private static string SerializePolicies(IList<string> policies) {
+ if (policies.Count == 0) {
+ return AuthenticationPolicies.None;
+ } else {
+ return PapeUtilities.ConcatenateListOfElements(policies);
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs
new file mode 100644
index 0000000..ab08cbb
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs
@@ -0,0 +1,316 @@
+//-----------------------------------------------------------------------
+// <copyright file="ClaimsRequest.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// Carries the request/require/none demand state of the simple registration fields.
+ /// </summary>
+ [Serializable]
+ public sealed class ClaimsRequest : ExtensionBase {
+ /// <summary>
+ /// The factory method that may be used in deserialization of this message.
+ /// </summary>
+ internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => {
+ if (typeUri == Constants.TypeUris.Standard && isProviderRole) {
+ return new ClaimsRequest(typeUri);
+ }
+
+ return null;
+ };
+
+ /// <summary>
+ /// The type URI that this particular (deserialized) extension was read in using,
+ /// allowing a response to alter be crafted using the same type URI.
+ /// </summary>
+ private string typeUriDeserializedFrom;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ClaimsRequest"/> class.
+ /// </summary>
+ public ClaimsRequest()
+ : base(new Version(1, 0), Constants.TypeUris.Standard, Constants.AdditionalTypeUris) {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ClaimsRequest"/> class
+ /// by deserializing from a message.
+ /// </summary>
+ /// <param name="typeUri">The type URI this extension was recognized by in the OpenID message.</param>
+ internal ClaimsRequest(string typeUri)
+ : this() {
+ Requires.NotNullOrEmpty(typeUri, "typeUri");
+
+ this.typeUriDeserializedFrom = typeUri;
+ }
+
+ /// <summary>
+ /// Gets or sets the URL the consumer site provides for the authenticating user to review
+ /// for how his claims will be used by the consumer web site.
+ /// </summary>
+ [MessagePart(Constants.policy_url, IsRequired = false)]
+ public Uri PolicyUrl { get; set; }
+
+ /// <summary>
+ /// Gets or sets the level of interest a relying party has in the nickname of the user.
+ /// </summary>
+ public DemandLevel Nickname { get; set; }
+
+ /// <summary>
+ /// Gets or sets the level of interest a relying party has in the email of the user.
+ /// </summary>
+ public DemandLevel Email { get; set; }
+
+ /// <summary>
+ /// Gets or sets the level of interest a relying party has in the full name of the user.
+ /// </summary>
+ public DemandLevel FullName { get; set; }
+
+ /// <summary>
+ /// Gets or sets the level of interest a relying party has in the birthdate of the user.
+ /// </summary>
+ public DemandLevel BirthDate { get; set; }
+
+ /// <summary>
+ /// Gets or sets the level of interest a relying party has in the gender of the user.
+ /// </summary>
+ public DemandLevel Gender { get; set; }
+
+ /// <summary>
+ /// Gets or sets the level of interest a relying party has in the postal code of the user.
+ /// </summary>
+ public DemandLevel PostalCode { get; set; }
+
+ /// <summary>
+ /// Gets or sets the level of interest a relying party has in the Country of the user.
+ /// </summary>
+ public DemandLevel Country { get; set; }
+
+ /// <summary>
+ /// Gets or sets the level of interest a relying party has in the language of the user.
+ /// </summary>
+ public DemandLevel Language { get; set; }
+
+ /// <summary>
+ /// Gets or sets the level of interest a relying party has in the time zone of the user.
+ /// </summary>
+ public DemandLevel TimeZone { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this <see cref="ClaimsRequest"/> instance
+ /// is synthesized from an AX request at the Provider.
+ /// </summary>
+ internal bool Synthesized { get; set; }
+
+ /// <summary>
+ /// Gets or sets the value of the sreg.required parameter.
+ /// </summary>
+ /// <value>A comma-delimited list of sreg fields.</value>
+ [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by messaging framework via reflection.")]
+ [MessagePart(Constants.required, AllowEmpty = true)]
+ private string RequiredList {
+ get { return string.Join(",", this.AssembleProfileFields(DemandLevel.Require)); }
+ set { this.SetProfileRequestFromList(value.Split(','), DemandLevel.Require); }
+ }
+
+ /// <summary>
+ /// Gets or sets the value of the sreg.optional parameter.
+ /// </summary>
+ /// <value>A comma-delimited list of sreg fields.</value>
+ [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by messaging framework via reflection.")]
+ [MessagePart(Constants.optional, AllowEmpty = true)]
+ private string OptionalList {
+ get { return string.Join(",", this.AssembleProfileFields(DemandLevel.Request)); }
+ set { this.SetProfileRequestFromList(value.Split(','), DemandLevel.Request); }
+ }
+
+ /// <summary>
+ /// Tests equality between two <see cref="ClaimsRequest"/> structs.
+ /// </summary>
+ /// <param name="one">One instance to compare.</param>
+ /// <param name="other">Another instance to compare.</param>
+ /// <returns>The result of the operator.</returns>
+ public static bool operator ==(ClaimsRequest one, ClaimsRequest other) {
+ return one.EqualsNullSafe(other);
+ }
+
+ /// <summary>
+ /// Tests inequality between two <see cref="ClaimsRequest"/> structs.
+ /// </summary>
+ /// <param name="one">One instance to compare.</param>
+ /// <param name="other">Another instance to compare.</param>
+ /// <returns>The result of the operator.</returns>
+ public static bool operator !=(ClaimsRequest one, ClaimsRequest other) {
+ return !(one == other);
+ }
+
+ /// <summary>
+ /// Tests equality between two <see cref="ClaimsRequest"/> structs.
+ /// </summary>
+ /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
+ /// <returns>
+ /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
+ /// </returns>
+ /// <exception cref="T:System.NullReferenceException">
+ /// The <paramref name="obj"/> parameter is null.
+ /// </exception>
+ public override bool Equals(object obj) {
+ ClaimsRequest other = obj as ClaimsRequest;
+ if (other == null) {
+ return false;
+ }
+
+ return
+ this.BirthDate.Equals(other.BirthDate) &&
+ this.Country.Equals(other.Country) &&
+ this.Language.Equals(other.Language) &&
+ this.Email.Equals(other.Email) &&
+ this.FullName.Equals(other.FullName) &&
+ this.Gender.Equals(other.Gender) &&
+ this.Nickname.Equals(other.Nickname) &&
+ this.PostalCode.Equals(other.PostalCode) &&
+ this.TimeZone.Equals(other.TimeZone) &&
+ this.PolicyUrl.EqualsNullSafe(other.PolicyUrl);
+ }
+
+ /// <summary>
+ /// Serves as a hash function for a particular type.
+ /// </summary>
+ /// <returns>
+ /// A hash code for the current <see cref="T:System.Object"/>.
+ /// </returns>
+ public override int GetHashCode() {
+ // It's important that if Equals returns true that the hash code also equals,
+ // so returning base.GetHashCode() is a BAD option.
+ // Return 1 is simple and poor for dictionary storage, but considering that every
+ // ClaimsRequest formulated at a single RP will likely have all the same fields,
+ // even a good hash code function will likely generate the same hash code. So
+ // we just cut to the chase and return a simple one.
+ return 1;
+ }
+
+ /// <summary>
+ /// Renders the requested information as a string.
+ /// </summary>
+ /// <returns>
+ /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
+ /// </returns>
+ public override string ToString() {
+ string format = @"Nickname = '{0}'
+Email = '{1}'
+FullName = '{2}'
+Birthdate = '{3}'
+Gender = '{4}'
+PostalCode = '{5}'
+Country = '{6}'
+Language = '{7}'
+TimeZone = '{8}'";
+ return string.Format(CultureInfo.CurrentCulture, format, this.Nickname, this.Email, this.FullName, this.BirthDate, this.Gender, this.PostalCode, this.Country, this.Language, this.TimeZone);
+ }
+
+ /// <summary>
+ /// Prepares a Simple Registration response extension that is compatible with the
+ /// version of Simple Registration used in the request message.
+ /// </summary>
+ /// <returns>The newly created <see cref="ClaimsResponse"/> instance.</returns>
+ public ClaimsResponse CreateResponse() {
+ if (this.typeUriDeserializedFrom == null) {
+ throw new InvalidOperationException(OpenIdStrings.CallDeserializeBeforeCreateResponse);
+ }
+
+ return new ClaimsResponse(this.typeUriDeserializedFrom);
+ }
+
+ /// <summary>
+ /// Sets the profile request properties according to a list of
+ /// field names that might have been passed in the OpenId query dictionary.
+ /// </summary>
+ /// <param name="fieldNames">
+ /// The list of field names that should receive a given
+ /// <paramref name="requestLevel"/>. These field names should match
+ /// the OpenId specification for field names, omitting the 'openid.sreg' prefix.
+ /// </param>
+ /// <param name="requestLevel">The none/request/require state of the listed fields.</param>
+ internal void SetProfileRequestFromList(IEnumerable<string> fieldNames, DemandLevel requestLevel) {
+ foreach (string field in fieldNames) {
+ switch (field) {
+ case "": // this occurs for empty lists
+ break;
+ case Constants.nickname:
+ this.Nickname = requestLevel;
+ break;
+ case Constants.email:
+ this.Email = requestLevel;
+ break;
+ case Constants.fullname:
+ this.FullName = requestLevel;
+ break;
+ case Constants.dob:
+ this.BirthDate = requestLevel;
+ break;
+ case Constants.gender:
+ this.Gender = requestLevel;
+ break;
+ case Constants.postcode:
+ this.PostalCode = requestLevel;
+ break;
+ case Constants.country:
+ this.Country = requestLevel;
+ break;
+ case Constants.language:
+ this.Language = requestLevel;
+ break;
+ case Constants.timezone:
+ this.TimeZone = requestLevel;
+ break;
+ default:
+ Logger.OpenId.WarnFormat("ClaimsRequest.SetProfileRequestFromList: Unrecognized field name '{0}'.", field);
+ break;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Assembles the profile parameter names that have a given <see cref="DemandLevel"/>.
+ /// </summary>
+ /// <param name="level">The demand level (request, require, none).</param>
+ /// <returns>An array of the profile parameter names that meet the criteria.</returns>
+ [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by messaging framework via reflection.")]
+ private string[] AssembleProfileFields(DemandLevel level) {
+ List<string> fields = new List<string>(10);
+ if (this.Nickname == level) {
+ fields.Add(Constants.nickname);
+ } if (this.Email == level) {
+ fields.Add(Constants.email);
+ } if (this.FullName == level) {
+ fields.Add(Constants.fullname);
+ } if (this.BirthDate == level) {
+ fields.Add(Constants.dob);
+ } if (this.Gender == level) {
+ fields.Add(Constants.gender);
+ } if (this.PostalCode == level) {
+ fields.Add(Constants.postcode);
+ } if (this.Country == level) {
+ fields.Add(Constants.country);
+ } if (this.Language == level) {
+ fields.Add(Constants.language);
+ } if (this.TimeZone == level) {
+ fields.Add(Constants.timezone);
+ }
+
+ return fields.ToArray();
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs
new file mode 100644
index 0000000..af60596
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs
@@ -0,0 +1,360 @@
+//-----------------------------------------------------------------------
+// <copyright file="ClaimsResponse.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.Net.Mail;
+ using System.Text;
+ using System.Text.RegularExpressions;
+ using System.Xml.Serialization;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// A struct storing Simple Registration field values describing an
+ /// authenticating user.
+ /// </summary>
+ [Serializable]
+ public sealed class ClaimsResponse : ExtensionBase, IClientScriptExtensionResponse, IMessageWithEvents {
+ /// <summary>
+ /// The factory method that may be used in deserialization of this message.
+ /// </summary>
+ internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => {
+ if ((typeUri == Constants.TypeUris.Standard || Array.IndexOf(Constants.AdditionalTypeUris, typeUri) >= 0) && !isProviderRole) {
+ return new ClaimsResponse(typeUri);
+ }
+
+ return null;
+ };
+
+ /// <summary>
+ /// The allowed format for birthdates.
+ /// </summary>
+ private static readonly Regex birthDateValidator = new Regex(@"^\d\d\d\d-\d\d-\d\d$");
+
+ /// <summary>
+ /// Storage for the raw string birthdate value.
+ /// </summary>
+ private string birthDateRaw;
+
+ /// <summary>
+ /// Backing field for the <see cref="BirthDate"/> property.
+ /// </summary>
+ private DateTime? birthDate;
+
+ /// <summary>
+ /// Backing field for the <see cref="Culture"/> property.
+ /// </summary>
+ private CultureInfo culture;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ClaimsResponse"/> class
+ /// using the most common, and spec prescribed type URI.
+ /// </summary>
+ public ClaimsResponse()
+ : this(Constants.TypeUris.Standard) {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ClaimsResponse"/> class.
+ /// </summary>
+ /// <param name="typeUriToUse">
+ /// The type URI that must be used to identify this extension in the response message.
+ /// This value should be the same one the relying party used to send the extension request.
+ /// Commonly used type URIs supported by relying parties are defined in the
+ /// <see cref="Constants.TypeUris"/> class.
+ /// </param>
+ public ClaimsResponse(string typeUriToUse = Constants.TypeUris.Standard)
+ : base(new Version(1, 0), typeUriToUse, Constants.AdditionalTypeUris) {
+ Requires.NotNullOrEmpty(typeUriToUse, "typeUriToUse");
+ }
+
+ /// <summary>
+ /// Gets or sets the nickname the user goes by.
+ /// </summary>
+ [MessagePart(Constants.nickname)]
+ public string Nickname { get; set; }
+
+ /// <summary>
+ /// Gets or sets the user's email address.
+ /// </summary>
+ [MessagePart(Constants.email)]
+ public string Email { get; set; }
+
+ /// <summary>
+ /// Gets or sets the full name of a user as a single string.
+ /// </summary>
+ [MessagePart(Constants.fullname)]
+ public string FullName { get; set; }
+
+ /// <summary>
+ /// Gets or sets the user's birthdate.
+ /// </summary>
+ public DateTime? BirthDate {
+ get {
+ return this.birthDate;
+ }
+
+ set {
+ this.birthDate = value;
+
+ // Don't use property accessor for peer property to avoid infinite loop between the two proeprty accessors.
+ if (value.HasValue) {
+ this.birthDateRaw = value.Value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
+ } else {
+ this.birthDateRaw = null;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the raw birth date string given by the extension.
+ /// </summary>
+ /// <value>A string in the format yyyy-MM-dd.</value>
+ [MessagePart(Constants.dob)]
+ public string BirthDateRaw {
+ get {
+ return this.birthDateRaw;
+ }
+
+ set {
+ ErrorUtilities.VerifyArgument(value == null || birthDateValidator.IsMatch(value), OpenIdStrings.SregInvalidBirthdate);
+ if (value != null) {
+ // Update the BirthDate property, if possible.
+ // Don't use property accessor for peer property to avoid infinite loop between the two proeprty accessors.
+ // Some valid sreg dob values like "2000-00-00" will not work as a DateTime struct,
+ // in which case we null it out, but don't show any error.
+ DateTime newBirthDate;
+ if (DateTime.TryParse(value, out newBirthDate)) {
+ this.birthDate = newBirthDate;
+ } else {
+ Logger.OpenId.WarnFormat("Simple Registration birthdate '{0}' could not be parsed into a DateTime and may not include month and/or day information. Setting BirthDate property to null.", value);
+ this.birthDate = null;
+ }
+ } else {
+ this.birthDate = null;
+ }
+
+ this.birthDateRaw = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the gender of the user.
+ /// </summary>
+ [MessagePart(Constants.gender, Encoder = typeof(GenderEncoder))]
+ public Gender? Gender { get; set; }
+
+ /// <summary>
+ /// Gets or sets the zip code / postal code of the user.
+ /// </summary>
+ [MessagePart(Constants.postcode)]
+ public string PostalCode { get; set; }
+
+ /// <summary>
+ /// Gets or sets the country of the user.
+ /// </summary>
+ [MessagePart(Constants.country)]
+ public string Country { get; set; }
+
+ /// <summary>
+ /// Gets or sets the primary/preferred language of the user.
+ /// </summary>
+ [MessagePart(Constants.language)]
+ public string Language { get; set; }
+
+ /// <summary>
+ /// Gets or sets the user's timezone.
+ /// </summary>
+ [MessagePart(Constants.timezone)]
+ public string TimeZone { get; set; }
+
+ /// <summary>
+ /// Gets a combination of the user's full name and email address.
+ /// </summary>
+ public MailAddress MailAddress {
+ get {
+ if (string.IsNullOrEmpty(this.Email)) {
+ return null;
+ } else if (string.IsNullOrEmpty(this.FullName)) {
+ return new MailAddress(this.Email);
+ } else {
+ return new MailAddress(this.Email, this.FullName);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets a combination o the language and country of the user.
+ /// </summary>
+ [XmlIgnore]
+ public CultureInfo Culture {
+ get {
+ if (this.culture == null && !string.IsNullOrEmpty(this.Language)) {
+ string cultureString = string.Empty;
+ cultureString = this.Language;
+ if (!string.IsNullOrEmpty(this.Country)) {
+ cultureString += "-" + this.Country;
+ }
+ this.culture = CultureInfo.GetCultureInfo(cultureString);
+ }
+
+ return this.culture;
+ }
+
+ set {
+ this.culture = value;
+ this.Language = (value != null) ? value.TwoLetterISOLanguageName : null;
+ int indexOfHyphen = (value != null) ? value.Name.IndexOf('-') : -1;
+ this.Country = indexOfHyphen > 0 ? value.Name.Substring(indexOfHyphen + 1) : null;
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this extension is signed by the Provider.
+ /// </summary>
+ /// <value>
+ /// <c>true</c> if this instance is signed by the Provider; otherwise, <c>false</c>.
+ /// </value>
+ public bool IsSignedByProvider {
+ get { return this.IsSignedByRemoteParty; }
+ }
+
+ /// <summary>
+ /// Tests equality of two <see cref="ClaimsResponse"/> objects.
+ /// </summary>
+ /// <param name="one">One instance to compare.</param>
+ /// <param name="other">Another instance to compare.</param>
+ /// <returns>The result of the operator.</returns>
+ public static bool operator ==(ClaimsResponse one, ClaimsResponse other) {
+ return one.EqualsNullSafe(other);
+ }
+
+ /// <summary>
+ /// Tests inequality of two <see cref="ClaimsResponse"/> objects.
+ /// </summary>
+ /// <param name="one">One instance to compare.</param>
+ /// <param name="other">Another instance to compare.</param>
+ /// <returns>The result of the operator.</returns>
+ public static bool operator !=(ClaimsResponse one, ClaimsResponse other) {
+ return !(one == other);
+ }
+
+ /// <summary>
+ /// Tests equality of two <see cref="ClaimsResponse"/> objects.
+ /// </summary>
+ /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
+ /// <returns>
+ /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
+ /// </returns>
+ /// <exception cref="T:System.NullReferenceException">
+ /// The <paramref name="obj"/> parameter is null.
+ /// </exception>
+ public override bool Equals(object obj) {
+ ClaimsResponse other = obj as ClaimsResponse;
+ if (other == null) {
+ return false;
+ }
+
+ return
+ this.BirthDateRaw.EqualsNullSafe(other.BirthDateRaw) &&
+ this.Country.EqualsNullSafe(other.Country) &&
+ this.Language.EqualsNullSafe(other.Language) &&
+ this.Email.EqualsNullSafe(other.Email) &&
+ this.FullName.EqualsNullSafe(other.FullName) &&
+ this.Gender.Equals(other.Gender) &&
+ this.Nickname.EqualsNullSafe(other.Nickname) &&
+ this.PostalCode.EqualsNullSafe(other.PostalCode) &&
+ this.TimeZone.EqualsNullSafe(other.TimeZone);
+ }
+
+ /// <summary>
+ /// Serves as a hash function for a particular type.
+ /// </summary>
+ /// <returns>
+ /// A hash code for the current <see cref="T:System.Object"/>.
+ /// </returns>
+ public override int GetHashCode() {
+ return (this.Nickname != null) ? this.Nickname.GetHashCode() : base.GetHashCode();
+ }
+
+ #region IClientScriptExtension Members
+
+ /// <summary>
+ /// Reads the extension information on an authentication response from the provider.
+ /// </summary>
+ /// <param name="response">The incoming OpenID response carrying the extension.</param>
+ /// <returns>
+ /// A Javascript snippet that when executed on the user agent returns an object with
+ /// the information deserialized from the extension response.
+ /// </returns>
+ /// <remarks>
+ /// This method is called <b>before</b> the signature on the assertion response has been
+ /// verified. Therefore all information in these fields should be assumed unreliable
+ /// and potentially falsified.
+ /// </remarks>
+ string IClientScriptExtensionResponse.InitializeJavaScriptData(IProtocolMessageWithExtensions response) {
+ var sreg = new Dictionary<string, string>(15);
+
+ // Although we could probably whip up a trip with MessageDictionary
+ // to avoid explicitly setting each field, doing so would likely
+ // open ourselves up to security exploits from the OP as it would
+ // make possible sending arbitrary javascript in arbitrary field names.
+ sreg[Constants.nickname] = this.Nickname;
+ sreg[Constants.email] = this.Email;
+ sreg[Constants.fullname] = this.FullName;
+ sreg[Constants.dob] = this.BirthDateRaw;
+ sreg[Constants.gender] = this.Gender.HasValue ? this.Gender.Value.ToString() : null;
+ sreg[Constants.postcode] = this.PostalCode;
+ sreg[Constants.country] = this.Country;
+ sreg[Constants.language] = this.Language;
+ sreg[Constants.timezone] = this.TimeZone;
+
+ return MessagingUtilities.CreateJsonObject(sreg, false);
+ }
+
+ #endregion
+
+ #region IMessageWithEvents Members
+
+ /// <summary>
+ /// Called when the message is about to be transmitted,
+ /// before it passes through the channel binding elements.
+ /// </summary>
+ void IMessageWithEvents.OnSending() {
+ // Null out empty values so we don't send out a lot of empty parameters.
+ this.Country = EmptyToNull(this.Country);
+ this.Email = EmptyToNull(this.Email);
+ this.FullName = EmptyToNull(this.FullName);
+ this.Language = EmptyToNull(this.Language);
+ this.Nickname = EmptyToNull(this.Nickname);
+ this.PostalCode = EmptyToNull(this.PostalCode);
+ this.TimeZone = EmptyToNull(this.TimeZone);
+ }
+
+ /// <summary>
+ /// Called when the message has been received,
+ /// after it passes through the channel binding elements.
+ /// </summary>
+ void IMessageWithEvents.OnReceiving() {
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Translates an empty string value to null, or passes through non-empty values.
+ /// </summary>
+ /// <param name="value">The value to consider changing to null.</param>
+ /// <returns>Either null or a non-empty string.</returns>
+ private static string EmptyToNull(string value) {
+ return string.IsNullOrEmpty(value) ? null : value;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/Constants.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/Constants.cs
new file mode 100644
index 0000000..30cd748
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/Constants.cs
@@ -0,0 +1,67 @@
+// <auto-generated/> // disable StyleCop on this file
+//-----------------------------------------------------------------------
+// <copyright file="Constants.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration {
+ using System;
+ using System.Collections.Generic;
+ using System.Text;
+
+ /// <summary>
+ /// Simple Registration constants
+ /// </summary>
+ public static class Constants {
+ /// <summary>
+ /// Commonly used type URIs to represent the Simple Registration extension.
+ /// </summary>
+ public static class TypeUris {
+ /// <summary>
+ /// The URI "http://openid.net/extensions/sreg/1.1".
+ /// </summary>
+ /// <remarks>
+ /// This is the type URI prescribed by the Simple Registration 1.1 spec.
+ /// http://openid.net/specs/openid-simple-registration-extension-1_1-01.html#anchor3
+ /// </remarks>
+ public const string Standard = "http://openid.net/extensions/sreg/1.1";
+
+ /// <summary>
+ /// The URI "http://openid.net/sreg/1.0"
+ /// </summary>
+ public const string Variant10 = "http://openid.net/sreg/1.0";
+
+ /// <summary>
+ /// The URI "http://openid.net/sreg/1.1"
+ /// </summary>
+ public const string Variant11 = "http://openid.net/sreg/1.1";
+ }
+
+ internal const string sreg_compatibility_alias = "sreg";
+ internal const string policy_url = "policy_url";
+ internal const string optional = "optional";
+ internal const string required = "required";
+ internal const string nickname = "nickname";
+ internal const string email = "email";
+ internal const string fullname = "fullname";
+ internal const string dob = "dob";
+ internal const string gender = "gender";
+ internal const string postcode = "postcode";
+ internal const string country = "country";
+ internal const string language = "language";
+ internal const string timezone = "timezone";
+ internal static class Genders {
+ internal const string Male = "M";
+ internal const string Female = "F";
+ }
+
+ /// <summary>
+ /// Additional type URIs that this extension is sometimes known by remote parties.
+ /// </summary>
+ internal static readonly string[] AdditionalTypeUris = new string[] {
+ Constants.TypeUris.Variant10,
+ Constants.TypeUris.Variant11,
+ };
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/DemandLevel.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/DemandLevel.cs
new file mode 100644
index 0000000..b814e84
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/DemandLevel.cs
@@ -0,0 +1,32 @@
+//-----------------------------------------------------------------------
+// <copyright file="DemandLevel.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration {
+ /// <summary>
+ /// Specifies what level of interest a relying party has in obtaining the value
+ /// of a given field offered by the Simple Registration extension.
+ /// </summary>
+ public enum DemandLevel {
+ /// <summary>
+ /// The relying party has no interest in obtaining this field.
+ /// </summary>
+ NoRequest,
+
+ /// <summary>
+ /// The relying party would like the value of this field, but wants
+ /// the Provider to display the field to the user as optionally provided.
+ /// </summary>
+ Request,
+
+ /// <summary>
+ /// The relying party considers this a required field as part of
+ /// authentication. The Provider and/or user agent MAY still choose to
+ /// not provide the value of the field however, according to the
+ /// Simple Registration extension specification.
+ /// </summary>
+ Require,
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/Gender.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/Gender.cs
new file mode 100644
index 0000000..c0b1c03
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/Gender.cs
@@ -0,0 +1,22 @@
+//-----------------------------------------------------------------------
+// <copyright file="Gender.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration {
+ /// <summary>
+ /// Indicates the gender of a user.
+ /// </summary>
+ public enum Gender {
+ /// <summary>
+ /// The user is male.
+ /// </summary>
+ Male,
+
+ /// <summary>
+ /// The user is female.
+ /// </summary>
+ Female,
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/GenderEncoder.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/GenderEncoder.cs
new file mode 100644
index 0000000..abc51c1
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/SimpleRegistration/GenderEncoder.cs
@@ -0,0 +1,54 @@
+//-----------------------------------------------------------------------
+// <copyright file="GenderEncoder.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration {
+ using System;
+ using DotNetOpenAuth.Messaging.Reflection;
+
+ /// <summary>
+ /// Encodes/decodes the Simple Registration Gender type to its string representation.
+ /// </summary>
+ internal class GenderEncoder : IMessagePartEncoder {
+ #region IMessagePartEncoder Members
+
+ /// <summary>
+ /// Encodes the specified value.
+ /// </summary>
+ /// <param name="value">The value. Guaranteed to never be null.</param>
+ /// <returns>
+ /// The <paramref name="value"/> in string form, ready for message transport.
+ /// </returns>
+ public string Encode(object value) {
+ var gender = (Gender?)value;
+ if (gender.HasValue) {
+ switch (gender.Value) {
+ case Gender.Male: return Constants.Genders.Male;
+ case Gender.Female: return Constants.Genders.Female;
+ }
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Decodes the specified value.
+ /// </summary>
+ /// <param name="value">The string value carried by the transport. Guaranteed to never be null, although it may be empty.</param>
+ /// <returns>
+ /// The deserialized form of the given string.
+ /// </returns>
+ /// <exception cref="FormatException">Thrown when the string value given cannot be decoded into the required object type.</exception>
+ public object Decode(string value) {
+ switch (value) {
+ case Constants.Genders.Male: return SimpleRegistration.Gender.Male;
+ case Constants.Genders.Female: return SimpleRegistration.Gender.Female;
+ default: throw new FormatException();
+ }
+ }
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/StandardOpenIdExtensionFactory.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/StandardOpenIdExtensionFactory.cs
new file mode 100644
index 0000000..1e43c4a
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/StandardOpenIdExtensionFactory.cs
@@ -0,0 +1,97 @@
+//-----------------------------------------------------------------------
+// <copyright file="StandardOpenIdExtensionFactory.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions {
+ using System.Collections.Generic;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.ChannelElements;
+ using DotNetOpenAuth.OpenId.Extensions.AttributeExchange;
+ using DotNetOpenAuth.OpenId.Extensions.OAuth;
+ using DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy;
+ using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration;
+ using DotNetOpenAuth.OpenId.Extensions.UI;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// An OpenID extension factory that supports registration so that third-party
+ /// extensions can add themselves to this library's supported extension list.
+ /// </summary>
+ internal class StandardOpenIdExtensionFactory : IOpenIdExtensionFactory {
+ /// <summary>
+ /// A collection of the registered OpenID extensions.
+ /// </summary>
+ private List<CreateDelegate> registeredExtensions = new List<CreateDelegate>();
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StandardOpenIdExtensionFactory"/> class.
+ /// </summary>
+ internal StandardOpenIdExtensionFactory() {
+ this.RegisterExtension(ClaimsRequest.Factory);
+ this.RegisterExtension(ClaimsResponse.Factory);
+ this.RegisterExtension(FetchRequest.Factory);
+ this.RegisterExtension(FetchResponse.Factory);
+ this.RegisterExtension(StoreRequest.Factory);
+ this.RegisterExtension(StoreResponse.Factory);
+ this.RegisterExtension(PolicyRequest.Factory);
+ this.RegisterExtension(PolicyResponse.Factory);
+ this.RegisterExtension(AuthorizationRequest.Factory);
+ this.RegisterExtension(AuthorizationApprovedResponse.Factory);
+ this.RegisterExtension(AuthorizationDeclinedResponse.Factory);
+ this.RegisterExtension(UIRequest.Factory);
+ }
+
+ /// <summary>
+ /// A delegate that individual extensions may register with this factory.
+ /// </summary>
+ /// <param name="typeUri">The type URI of the extension.</param>
+ /// <param name="data">The parameters associated specifically with this extension.</param>
+ /// <param name="baseMessage">The OpenID message carrying this extension.</param>
+ /// <param name="isProviderRole">A value indicating whether this extension is being received at the OpenID Provider.</param>
+ /// <returns>
+ /// An instance of <see cref="IOpenIdMessageExtension"/> if the factory recognizes
+ /// the extension described in the input parameters; <c>null</c> otherwise.
+ /// </returns>
+ internal delegate IOpenIdMessageExtension CreateDelegate(string typeUri, IDictionary<string, string> data, IProtocolMessageWithExtensions baseMessage, bool isProviderRole);
+
+ #region IOpenIdExtensionFactory Members
+
+ /// <summary>
+ /// Creates a new instance of some extension based on the received extension parameters.
+ /// </summary>
+ /// <param name="typeUri">The type URI of the extension.</param>
+ /// <param name="data">The parameters associated specifically with this extension.</param>
+ /// <param name="baseMessage">The OpenID message carrying this extension.</param>
+ /// <param name="isProviderRole">A value indicating whether this extension is being received at the OpenID Provider.</param>
+ /// <returns>
+ /// An instance of <see cref="IOpenIdMessageExtension"/> if the factory recognizes
+ /// the extension described in the input parameters; <c>null</c> otherwise.
+ /// </returns>
+ /// <remarks>
+ /// This factory method need only initialize properties in the instantiated extension object
+ /// that are not bound using <see cref="MessagePartAttribute"/>.
+ /// </remarks>
+ public IOpenIdMessageExtension Create(string typeUri, IDictionary<string, string> data, IProtocolMessageWithExtensions baseMessage, bool isProviderRole) {
+ foreach (var factoryMethod in this.registeredExtensions) {
+ IOpenIdMessageExtension result = factoryMethod(typeUri, data, baseMessage, isProviderRole);
+ if (result != null) {
+ return result;
+ }
+ }
+
+ return null;
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Registers a new extension delegate.
+ /// </summary>
+ /// <param name="creator">The factory method that can create the extension.</param>
+ internal void RegisterExtension(CreateDelegate creator) {
+ this.registeredExtensions.Add(creator);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/UI/UIConstants.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/UI/UIConstants.cs
new file mode 100644
index 0000000..9b15e4a
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/UI/UIConstants.cs
@@ -0,0 +1,34 @@
+//-----------------------------------------------------------------------
+// <copyright file="UIConstants.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.UI {
+ /// <summary>
+ /// Constants used to support the UI extension.
+ /// </summary>
+ internal static class UIConstants {
+ /// <summary>
+ /// The type URI associated with this extension.
+ /// </summary>
+ internal const string UITypeUri = "http://specs.openid.net/extensions/ui/1.0";
+
+ /// <summary>
+ /// The Type URI that appears in an XRDS document when the OP supports popups through the UI extension.
+ /// </summary>
+ internal const string PopupSupported = "http://specs.openid.net/extensions/ui/1.0/mode/popup";
+
+ /// <summary>
+ /// The Type URI that appears in an XRDS document when the OP supports the RP
+ /// specifying the user's preferred language through the UI extension.
+ /// </summary>
+ internal const string LangPrefSupported = "http://specs.openid.net/extensions/ui/1.0/lang-pref";
+
+ /// <summary>
+ /// The Type URI that appears in the XRDS document when the OP supports the RP
+ /// specifying the icon for the OP to display during authentication through the UI extension.
+ /// </summary>
+ internal const string IconSupported = "http://specs.openid.net/extensions/ui/1.0/icon";
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/UI/UIModes.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/UI/UIModes.cs
new file mode 100644
index 0000000..10f1ed3
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/UI/UIModes.cs
@@ -0,0 +1,25 @@
+//-----------------------------------------------------------------------
+// <copyright file="UIModes.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.UI {
+ /// <summary>
+ /// Valid values for the <c>mode</c> parameter of the OpenID User Interface extension.
+ /// </summary>
+ public static class UIModes {
+ /// <summary>
+ /// Indicates that the Provider's authentication page appears in a popup window.
+ /// </summary>
+ /// <value>The constant <c>"popup"</c>.</value>
+ /// <remarks>
+ /// <para>The RP SHOULD create the popup to be 450 pixels wide and 500 pixels tall. The popup MUST have the address bar displayed, and MUST be in a standalone browser window. The contents of the popup MUST NOT be framed by the RP. </para>
+ /// <para>The RP SHOULD open the popup centered above the main browser window, and SHOULD dim the contents of the parent window while the popup is active. The RP SHOULD ensure that the user is not surprised by the appearance of the popup, and understands how to interact with it. </para>
+ /// <para>To keep the user popup user experience consistent, it is RECOMMENDED that the OP does not resize the popup window unless the OP requires additional space to show special features that are not usually displayed as part of the default popup user experience. </para>
+ /// <para>The OP MAY close the popup without returning a response to the RP. Closing the popup without sending a response should be interpreted as a negative assertion. </para>
+ /// <para>The response to an authentication request in a popup is unchanged from [OpenID 2.0] (OpenID 2.0 Workgroup, “OpenID 2.0,” .). Relying Parties detecting that the popup was closed without receiving an authentication response SHOULD interpret the close event to be a negative assertion. </para>
+ /// </remarks>
+ public const string Popup = "popup";
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/UI/UIRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/UI/UIRequest.cs
new file mode 100644
index 0000000..0902a00
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/UI/UIRequest.cs
@@ -0,0 +1,197 @@
+//-----------------------------------------------------------------------
+// <copyright file="UIRequest.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.UI {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.Linq;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+ using DotNetOpenAuth.Xrds;
+
+ /// <summary>
+ /// OpenID User Interface extension 1.0 request message.
+ /// </summary>
+ /// <remarks>
+ /// <para>Implements the extension described by: http://wiki.openid.net/f/openid_ui_extension_draft01.html </para>
+ /// <para>This extension only applies to checkid_setup requests, since checkid_immediate requests display
+ /// no UI to the user. </para>
+ /// <para>For rules about how the popup window should be displayed, please see the documentation of
+ /// <see cref="UIModes.Popup"/>. </para>
+ /// <para>An RP may determine whether an arbitrary OP supports this extension (and thereby determine
+ /// whether to use a standard full window redirect or a popup) via the
+ /// <see cref="IdentifierDiscoveryResult.IsExtensionSupported&lt;T&gt;()"/> method.</para>
+ /// </remarks>
+ [Serializable]
+ public class UIRequest : IOpenIdMessageExtension, IMessageWithEvents {
+ /// <summary>
+ /// The factory method that may be used in deserialization of this message.
+ /// </summary>
+ internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => {
+ if (typeUri == UIConstants.UITypeUri && isProviderRole) {
+ return new UIRequest();
+ }
+
+ return null;
+ };
+
+ /// <summary>
+ /// Additional type URIs that this extension is sometimes known by remote parties.
+ /// </summary>
+ private static readonly string[] additionalTypeUris = new string[] {
+ UIConstants.LangPrefSupported,
+ UIConstants.PopupSupported,
+ UIConstants.IconSupported,
+ };
+
+ /// <summary>
+ /// Backing store for <see cref="ExtraData"/>.
+ /// </summary>
+ private Dictionary<string, string> extraData = new Dictionary<string, string>();
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="UIRequest"/> class.
+ /// </summary>
+ public UIRequest() {
+ this.LanguagePreference = new[] { CultureInfo.CurrentUICulture };
+ this.Mode = UIModes.Popup;
+ }
+
+ /// <summary>
+ /// Gets or sets the list of user's preferred languages, sorted in decreasing preferred order.
+ /// </summary>
+ /// <value>The default is the <see cref="CultureInfo.CurrentUICulture"/> of the thread that created this instance.</value>
+ /// <remarks>
+ /// The user's preferred languages as a [BCP 47] language priority list, represented as a comma-separated list of BCP 47 basic language ranges in descending priority order. For instance, the value "fr-CA,fr-FR,en-CA" represents the preference for French spoken in Canada, French spoken in France, followed by English spoken in Canada.
+ /// </remarks>
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "By design.")]
+ [MessagePart("lang", AllowEmpty = false)]
+ public CultureInfo[] LanguagePreference { get; set; }
+
+ /// <summary>
+ /// Gets or sets the style of UI that the RP is hosting the OP's authentication page in.
+ /// </summary>
+ /// <value>Some value from the <see cref="UIModes"/> class. Defaults to <see cref="UIModes.Popup"/>.</value>
+ [MessagePart("mode", AllowEmpty = false, IsRequired = true)]
+ public string Mode { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the Relying Party has an icon
+ /// it would like the Provider to display to the user while asking them
+ /// whether they would like to log in.
+ /// </summary>
+ /// <value><c>true</c> if the Provider should display an icon; otherwise, <c>false</c>.</value>
+ /// <remarks>
+ /// By default, the Provider displays the relying party's favicon.ico.
+ /// </remarks>
+ [MessagePart("icon", AllowEmpty = false, IsRequired = false)]
+ public bool? Icon { get; set; }
+
+ #region IOpenIdMessageExtension Members
+
+ /// <summary>
+ /// Gets the TypeURI the extension uses in the OpenID protocol and in XRDS advertisements.
+ /// </summary>
+ /// <value></value>
+ public string TypeUri { get { return UIConstants.UITypeUri; } }
+
+ /// <summary>
+ /// Gets the additional TypeURIs that are supported by this extension, in preferred order.
+ /// May be empty if none other than <see cref="TypeUri"/> is supported, but
+ /// should not be null.
+ /// </summary>
+ /// <remarks>
+ /// Useful for reading in messages with an older version of an extension.
+ /// The value in the <see cref="TypeUri"/> property is always checked before
+ /// trying this list.
+ /// If you do support multiple versions of an extension using this method,
+ /// consider adding a CreateResponse method to your request extension class
+ /// so that the response can have the context it needs to remain compatible
+ /// given the version of the extension in the request message.
+ /// The <see cref="Extensions.SimpleRegistration.ClaimsRequest.CreateResponse"/> for an example.
+ /// </remarks>
+ public IEnumerable<string> AdditionalSupportedTypeUris { get { return additionalTypeUris; } }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this extension was
+ /// signed by the sender.
+ /// </summary>
+ /// <value>
+ /// <c>true</c> if this instance is signed by the sender; otherwise, <c>false</c>.
+ /// </value>
+ public bool IsSignedByRemoteParty { get; set; }
+
+ #endregion
+
+ #region IMessage Properties
+
+ /// <summary>
+ /// Gets the version of the protocol or extension this message is prepared to implement.
+ /// </summary>
+ /// <value>The value 1.0.</value>
+ /// <remarks>
+ /// Implementations of this interface should ensure that this property never returns null.
+ /// </remarks>
+ public Version Version {
+ get { return new Version(1, 0); }
+ }
+
+ /// <summary>
+ /// Gets the extra, non-standard Protocol parameters included in the message.
+ /// </summary>
+ /// <remarks>
+ /// Implementations of this interface should ensure that this property never returns null.
+ /// </remarks>
+ public IDictionary<string, string> ExtraData {
+ get { return this.extraData; }
+ }
+
+ #endregion
+
+ #region IMessage methods
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ /// <remarks>
+ /// <para>Some messages have required fields, or combinations of fields that must relate to each other
+ /// in specialized ways. After deserializing a message, this method checks the state of the
+ /// message to see if it conforms to the protocol.</para>
+ /// <para>Note that this property should <i>not</i> check signatures or perform any state checks
+ /// outside this scope of this particular message.</para>
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception>
+ public void EnsureValidMessage() {
+ }
+
+ #endregion
+
+ #region IMessageWithEvents Members
+
+ /// <summary>
+ /// Called when the message is about to be transmitted,
+ /// before it passes through the channel binding elements.
+ /// </summary>
+ public void OnSending() {
+ }
+
+ /// <summary>
+ /// Called when the message has been received,
+ /// after it passes through the channel binding elements.
+ /// </summary>
+ public void OnReceiving() {
+ if (this.LanguagePreference != null) {
+ // TODO: see if we can change the CultureInfo.CurrentUICulture somehow
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Extensions/UI/UIUtilities.cs b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/UI/UIUtilities.cs
new file mode 100644
index 0000000..19e333d
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Extensions/UI/UIUtilities.cs
@@ -0,0 +1,28 @@
+//-----------------------------------------------------------------------
+// <copyright file="UIUtilities.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Extensions.UI {
+ using System;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+
+ /// <summary>
+ /// Constants used in implementing support for the UI extension.
+ /// </summary>
+ public static class UIUtilities {
+ /// <summary>
+ /// The required width of the popup window the relying party creates for the provider.
+ /// </summary>
+ public const int PopupWidth = 500; // UI extension calls for 450px, but Yahoo needs 500px
+
+ /// <summary>
+ /// The required height of the popup window the relying party creates for the provider.
+ /// </summary>
+ public const int PopupHeight = 500;
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/HmacShaAssociation.cs b/src/DotNetOpenAuth.OpenId/OpenId/HmacShaAssociation.cs
new file mode 100644
index 0000000..bf0111d
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/HmacShaAssociation.cs
@@ -0,0 +1,285 @@
+//-----------------------------------------------------------------------
+// <copyright file="HmacShaAssociation.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.Linq;
+ using System.Security.Cryptography;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// An association that uses the HMAC-SHA family of algorithms for message signing.
+ /// </summary>
+ [ContractVerification(true)]
+ internal class HmacShaAssociation : Association {
+ /// <summary>
+ /// A list of HMAC-SHA algorithms in order of decreasing bit lengths.
+ /// </summary>
+ private static HmacSha[] hmacShaAssociationTypes = CreateAssociationTypes();
+
+ /// <summary>
+ /// The specific variety of HMAC-SHA this association is based on (whether it be HMAC-SHA1, HMAC-SHA256, etc.)
+ /// </summary>
+ private HmacSha typeIdentity;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="HmacShaAssociation"/> class.
+ /// </summary>
+ /// <param name="typeIdentity">The specific variety of HMAC-SHA this association is based on (whether it be HMAC-SHA1, HMAC-SHA256, etc.)</param>
+ /// <param name="handle">The association handle.</param>
+ /// <param name="secret">The association secret.</param>
+ /// <param name="totalLifeLength">The time duration the association will be good for.</param>
+ private HmacShaAssociation(HmacSha typeIdentity, string handle, byte[] secret, TimeSpan totalLifeLength)
+ : base(handle, secret, totalLifeLength, DateTime.UtcNow) {
+ Requires.NotNull(typeIdentity, "typeIdentity");
+ Requires.NotNullOrEmpty(handle, "handle");
+ Requires.NotNull(secret, "secret");
+ Requires.InRange(totalLifeLength > TimeSpan.Zero, "totalLifeLength");
+ Contract.Ensures(this.TotalLifeLength == totalLifeLength);
+ ErrorUtilities.VerifyProtocol(secret.Length == typeIdentity.SecretLength, OpenIdStrings.AssociationSecretAndTypeLengthMismatch, secret.Length, typeIdentity.GetAssociationType(Protocol.Default));
+
+ this.typeIdentity = typeIdentity;
+ }
+
+ /// <summary>
+ /// Gets the length (in bits) of the hash this association creates when signing.
+ /// </summary>
+ public override int HashBitLength {
+ get {
+ Protocol protocol = Protocol.Default;
+ return HmacShaAssociation.GetSecretLength(protocol, this.GetAssociationType(protocol)) * 8;
+ }
+ }
+
+ /// <summary>
+ /// Creates an HMAC-SHA association.
+ /// </summary>
+ /// <param name="protocol">The OpenID protocol version that the request for an association came in on.</param>
+ /// <param name="associationType">The value of the openid.assoc_type parameter.</param>
+ /// <param name="handle">The association handle.</param>
+ /// <param name="secret">The association secret.</param>
+ /// <param name="totalLifeLength">How long the association will be good for.</param>
+ /// <returns>The newly created association.</returns>
+ public static HmacShaAssociation Create(Protocol protocol, string associationType, string handle, byte[] secret, TimeSpan totalLifeLength) {
+ Requires.NotNull(protocol, "protocol");
+ Requires.NotNullOrEmpty(associationType, "associationType");
+ Requires.NotNull(secret, "secret");
+ Contract.Ensures(Contract.Result<HmacShaAssociation>() != null);
+ HmacSha match = hmacShaAssociationTypes.FirstOrDefault(sha => string.Equals(sha.GetAssociationType(protocol), associationType, StringComparison.Ordinal));
+ ErrorUtilities.VerifyProtocol(match != null, OpenIdStrings.NoAssociationTypeFoundByName, associationType);
+ return new HmacShaAssociation(match, handle, secret, totalLifeLength);
+ }
+
+ /// <summary>
+ /// Creates an association with the specified handle, secret, and lifetime.
+ /// </summary>
+ /// <param name="handle">The handle.</param>
+ /// <param name="secret">The secret.</param>
+ /// <param name="totalLifeLength">Total lifetime.</param>
+ /// <returns>The newly created association.</returns>
+ public static HmacShaAssociation Create(string handle, byte[] secret, TimeSpan totalLifeLength) {
+ Requires.NotNullOrEmpty(handle, "handle");
+ Requires.NotNull(secret, "secret");
+ Contract.Ensures(Contract.Result<HmacShaAssociation>() != null);
+
+ HmacSha shaType = hmacShaAssociationTypes.FirstOrDefault(sha => sha.SecretLength == secret.Length);
+ ErrorUtilities.VerifyProtocol(shaType != null, OpenIdStrings.NoAssociationTypeFoundByLength, secret.Length);
+ return new HmacShaAssociation(shaType, handle, secret, totalLifeLength);
+ }
+
+ /// <summary>
+ /// Returns the length of the shared secret (in bytes).
+ /// </summary>
+ /// <param name="protocol">The protocol version being used that will be used to lookup the text in <paramref name="associationType"/></param>
+ /// <param name="associationType">The value of the protocol argument specifying the type of association. For example: "HMAC-SHA1".</param>
+ /// <returns>The length (in bytes) of the association secret.</returns>
+ /// <exception cref="ProtocolException">Thrown if no association can be found by the given name.</exception>
+ public static int GetSecretLength(Protocol protocol, string associationType) {
+ HmacSha match = hmacShaAssociationTypes.FirstOrDefault(shaType => string.Equals(shaType.GetAssociationType(protocol), associationType, StringComparison.Ordinal));
+ ErrorUtilities.VerifyProtocol(match != null, OpenIdStrings.NoAssociationTypeFoundByName, associationType);
+ return match.SecretLength;
+ }
+
+ /// <summary>
+ /// Looks for the first association type in a preferred-order list that is
+ /// likely to be supported given a specific OpenID version and the security settings,
+ /// and perhaps a matching Diffie-Hellman session type.
+ /// </summary>
+ /// <param name="protocol">The OpenID version that dictates which associations are available.</param>
+ /// <param name="highSecurityIsBetter">A value indicating whether to consider higher strength security to be better. Use <c>true</c> for initial association requests from the Relying Party; use <c>false</c> from Providers when the Relying Party asks for an unrecognized association in order to pick a suggested alternative that is likely to be supported on both sides.</param>
+ /// <param name="securityRequirements">The set of requirements the selected association type must comply to.</param>
+ /// <param name="requireMatchingDHSessionType">Use <c>true</c> for HTTP associations, <c>false</c> for HTTPS associations.</param>
+ /// <param name="associationType">The resulting association type's well known protocol name. (i.e. HMAC-SHA256)</param>
+ /// <param name="sessionType">The resulting session type's well known protocol name, if a matching one is available. (i.e. DH-SHA256)</param>
+ /// <returns>
+ /// True if a qualifying association could be found; false otherwise.
+ /// </returns>
+ internal static bool TryFindBestAssociation(Protocol protocol, bool highSecurityIsBetter, SecuritySettings securityRequirements, bool requireMatchingDHSessionType, out string associationType, out string sessionType) {
+ Requires.NotNull(protocol, "protocol");
+ Requires.NotNull(securityRequirements, "securityRequirements");
+
+ associationType = null;
+ sessionType = null;
+
+ // We use AsEnumerable() to avoid VerificationException (http://stackoverflow.com/questions/478422/why-does-simple-array-and-linq-generate-verificationexception-operation-could-de)
+ IEnumerable<HmacSha> preferredOrder = highSecurityIsBetter ?
+ hmacShaAssociationTypes.AsEnumerable() : hmacShaAssociationTypes.Reverse();
+
+ foreach (HmacSha sha in preferredOrder) {
+ int hashSizeInBits = sha.SecretLength * 8;
+ if (hashSizeInBits > securityRequirements.MaximumHashBitLength ||
+ hashSizeInBits < securityRequirements.MinimumHashBitLength) {
+ continue;
+ }
+
+ if (OpenIdUtilities.IsDiffieHellmanPresent) {
+ sessionType = DiffieHellmanUtilities.GetNameForSize(protocol, hashSizeInBits);
+ } else {
+ sessionType = requireMatchingDHSessionType ? null : protocol.Args.SessionType.NoEncryption;
+ }
+
+ if (requireMatchingDHSessionType && sessionType == null) {
+ continue;
+ }
+ associationType = sha.GetAssociationType(protocol);
+ if (associationType == null) {
+ continue;
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Determines whether a named Diffie-Hellman session type and association type can be used together.
+ /// </summary>
+ /// <param name="protocol">The protocol carrying the names of the session and association types.</param>
+ /// <param name="associationType">The value of the openid.assoc_type parameter.</param>
+ /// <param name="sessionType">The value of the openid.session_type parameter.</param>
+ /// <returns>
+ /// <c>true</c> if the named association and session types are compatible; otherwise, <c>false</c>.
+ /// </returns>
+ internal static bool IsDHSessionCompatible(Protocol protocol, string associationType, string sessionType) {
+ Requires.NotNull(protocol, "protocol");
+ Requires.NotNullOrEmpty(associationType, "associationType");
+ Requires.NotNull(sessionType, "sessionType");
+
+ // All association types can work when no DH session is used at all.
+ if (string.Equals(sessionType, protocol.Args.SessionType.NoEncryption, StringComparison.Ordinal)) {
+ return true;
+ }
+
+ if (OpenIdUtilities.IsDiffieHellmanPresent) {
+ // When there _is_ a DH session, it must match in hash length with the association type.
+ int associationSecretLengthInBytes = GetSecretLength(protocol, associationType);
+ int sessionHashLengthInBytes = DiffieHellmanUtilities.Lookup(protocol, sessionType).HashSize / 8;
+ return associationSecretLengthInBytes == sessionHashLengthInBytes;
+ } else {
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Gets the string to pass as the assoc_type value in the OpenID protocol.
+ /// </summary>
+ /// <param name="protocol">The protocol version of the message that the assoc_type value will be included in.</param>
+ /// <returns>
+ /// The value that should be used for the openid.assoc_type parameter.
+ /// </returns>
+ [Pure]
+ internal override string GetAssociationType(Protocol protocol) {
+ return this.typeIdentity.GetAssociationType(protocol);
+ }
+
+ /// <summary>
+ /// Returns the specific hash algorithm used for message signing.
+ /// </summary>
+ /// <returns>
+ /// The hash algorithm used for message signing.
+ /// </returns>
+ [Pure]
+ protected override HashAlgorithm CreateHasher() {
+ var result = this.typeIdentity.CreateHasher(SecretKey);
+ Contract.Assume(result != null);
+ return result;
+ }
+
+ /// <summary>
+ /// Returns the value used to initialize the static field storing association types.
+ /// </summary>
+ /// <returns>A non-null, non-empty array.</returns>
+ /// <remarks>>
+ /// This is a method rather than being inlined to the field initializer to try to avoid
+ /// the CLR bug that crops up sometimes if we initialize arrays using object initializer syntax.
+ /// </remarks>
+ private static HmacSha[] CreateAssociationTypes() {
+ return new[] {
+ new HmacSha {
+ HmacAlgorithmName = HmacAlgorithms.HmacSha384,
+ GetAssociationType = protocol => protocol.Args.SignatureAlgorithm.HMAC_SHA512,
+ BaseHashAlgorithm = SHA512.Create(),
+ },
+ new HmacSha {
+ HmacAlgorithmName = HmacAlgorithms.HmacSha384,
+ GetAssociationType = protocol => protocol.Args.SignatureAlgorithm.HMAC_SHA384,
+ BaseHashAlgorithm = SHA384.Create(),
+ },
+ new HmacSha {
+ HmacAlgorithmName = HmacAlgorithms.HmacSha256,
+ GetAssociationType = protocol => protocol.Args.SignatureAlgorithm.HMAC_SHA256,
+ BaseHashAlgorithm = SHA256.Create(),
+ },
+ new HmacSha {
+ HmacAlgorithmName = HmacAlgorithms.HmacSha1,
+ GetAssociationType = protocol => protocol.Args.SignatureAlgorithm.HMAC_SHA1,
+ BaseHashAlgorithm = SHA1.Create(),
+ },
+ };
+ }
+
+ /// <summary>
+ /// Provides information about some HMAC-SHA hashing algorithm that OpenID supports.
+ /// </summary>
+ private class HmacSha {
+ /// <summary>
+ /// Gets or sets the function that takes a particular OpenID version and returns the value of the openid.assoc_type parameter in that protocol.
+ /// </summary>
+ internal Func<Protocol, string> GetAssociationType { get; set; }
+
+ /// <summary>
+ /// Gets or sets the name of the HMAC-SHA algorithm. (e.g. "HMAC-SHA256")
+ /// </summary>
+ internal string HmacAlgorithmName { get; set; }
+
+ /// <summary>
+ /// Gets or sets the base hash algorithm.
+ /// </summary>
+ internal HashAlgorithm BaseHashAlgorithm { get; set; }
+
+ /// <summary>
+ /// Gets the size of the hash (in bytes).
+ /// </summary>
+ internal int SecretLength { get { return this.BaseHashAlgorithm.HashSize / 8; } }
+
+ /// <summary>
+ /// Creates the <see cref="HashAlgorithm"/> using a given shared secret for the mac.
+ /// </summary>
+ /// <param name="secret">The HMAC secret.</param>
+ /// <returns>The algorithm.</returns>
+ internal HashAlgorithm CreateHasher(byte[] secret) {
+ return HmacAlgorithms.Create(this.HmacAlgorithmName, secret);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/IIdentifierDiscoveryService.cs b/src/DotNetOpenAuth.OpenId/OpenId/IIdentifierDiscoveryService.cs
new file mode 100644
index 0000000..63c3895
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/IIdentifierDiscoveryService.cs
@@ -0,0 +1,67 @@
+//-----------------------------------------------------------------------
+// <copyright file="IIdentifierDiscoveryService.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+
+ /// <summary>
+ /// A module that provides discovery services for OpenID identifiers.
+ /// </summary>
+ [ContractClass(typeof(IIdentifierDiscoveryServiceContract))]
+ public interface IIdentifierDiscoveryService {
+ /// <summary>
+ /// Performs discovery on the specified identifier.
+ /// </summary>
+ /// <param name="identifier">The identifier to perform discovery on.</param>
+ /// <param name="requestHandler">The means to place outgoing HTTP requests.</param>
+ /// <param name="abortDiscoveryChain">if set to <c>true</c>, no further discovery services will be called for this identifier.</param>
+ /// <returns>
+ /// A sequence of service endpoints yielded by discovery. Must not be null, but may be empty.
+ /// </returns>
+ [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "By design")]
+ [Pure]
+ IEnumerable<IdentifierDiscoveryResult> Discover(Identifier identifier, IDirectWebRequestHandler requestHandler, out bool abortDiscoveryChain);
+ }
+
+ /// <summary>
+ /// Code contract for the <see cref="IIdentifierDiscoveryService"/> interface.
+ /// </summary>
+ [ContractClassFor(typeof(IIdentifierDiscoveryService))]
+ internal abstract class IIdentifierDiscoveryServiceContract : IIdentifierDiscoveryService {
+ /// <summary>
+ /// Prevents a default instance of the <see cref="IIdentifierDiscoveryServiceContract"/> class from being created.
+ /// </summary>
+ private IIdentifierDiscoveryServiceContract() {
+ }
+
+ #region IDiscoveryService Members
+
+ /// <summary>
+ /// Performs discovery on the specified identifier.
+ /// </summary>
+ /// <param name="identifier">The identifier to perform discovery on.</param>
+ /// <param name="requestHandler">The means to place outgoing HTTP requests.</param>
+ /// <param name="abortDiscoveryChain">if set to <c>true</c>, no further discovery services will be called for this identifier.</param>
+ /// <returns>
+ /// A sequence of service endpoints yielded by discovery. Must not be null, but may be empty.
+ /// </returns>
+ IEnumerable<IdentifierDiscoveryResult> IIdentifierDiscoveryService.Discover(Identifier identifier, IDirectWebRequestHandler requestHandler, out bool abortDiscoveryChain) {
+ Requires.NotNull(identifier, "identifier");
+ Requires.NotNull(requestHandler, "requestHandler");
+ Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null);
+ throw new NotImplementedException();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/IOpenIdApplicationStore.cs b/src/DotNetOpenAuth.OpenId/OpenId/IOpenIdApplicationStore.cs
new file mode 100644
index 0000000..b60f2b8
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/IOpenIdApplicationStore.cs
@@ -0,0 +1,16 @@
+//-----------------------------------------------------------------------
+// <copyright file="IOpenIdApplicationStore.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId {
+ using DotNetOpenAuth.Messaging.Bindings;
+
+ /// <summary>
+ /// A hybrid of the store interfaces that an OpenID Provider must implement, and
+ /// an OpenID Relying Party may implement to operate in stateful (smart) mode.
+ /// </summary>
+ public interface IOpenIdApplicationStore : ICryptoKeyStore, INonceStore {
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/IOpenIdHost.cs b/src/DotNetOpenAuth.OpenId/OpenId/IOpenIdHost.cs
new file mode 100644
index 0000000..419cc84
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/IOpenIdHost.cs
@@ -0,0 +1,28 @@
+//-----------------------------------------------------------------------
+// <copyright file="IOpenIdHost.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// An interface implemented by both providers and relying parties.
+ /// </summary>
+ internal interface IOpenIdHost {
+ /// <summary>
+ /// Gets the security settings.
+ /// </summary>
+ SecuritySettings SecuritySettings { get; }
+
+ /// <summary>
+ /// Gets the web request handler.
+ /// </summary>
+ IDirectWebRequestHandler WebRequestHandler { get; }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/IProviderEndpoint.cs b/src/DotNetOpenAuth.OpenId/OpenId/IProviderEndpoint.cs
new file mode 100644
index 0000000..b2b231a
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/IProviderEndpoint.cs
@@ -0,0 +1,143 @@
+//-----------------------------------------------------------------------
+// <copyright file="IProviderEndpoint.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.RelyingParty {
+ using System;
+ using System.Collections.ObjectModel;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// Information published about an OpenId Provider by the
+ /// OpenId discovery documents found at a user's Claimed Identifier.
+ /// </summary>
+ /// <remarks>
+ /// Because information provided by this interface is suppplied by a
+ /// user's individually published documents, it may be incomplete or inaccurate.
+ /// </remarks>
+ [ContractClass(typeof(IProviderEndpointContract))]
+ public interface IProviderEndpoint {
+ /// <summary>
+ /// Gets the detected version of OpenID implemented by the Provider.
+ /// </summary>
+ Version Version { get; }
+
+ /// <summary>
+ /// Gets the URL that the OpenID Provider receives authentication requests at.
+ /// </summary>
+ /// <value>
+ /// This value MUST be an absolute HTTP or HTTPS URL.
+ /// </value>
+ Uri Uri { get; }
+
+ /// <summary>
+ /// Checks whether the OpenId Identifier claims support for a given extension.
+ /// </summary>
+ /// <typeparam name="T">The extension whose support is being queried.</typeparam>
+ /// <returns>True if support for the extension is advertised. False otherwise.</returns>
+ /// <remarks>
+ /// Note that a true or false return value is no guarantee of a Provider's
+ /// support for or lack of support for an extension. The return value is
+ /// determined by how the authenticating user filled out his/her XRDS document only.
+ /// The only way to be sure of support for a given extension is to include
+ /// the extension in the request and see if a response comes back for that extension.
+ /// </remarks>
+ [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "No parameter at all.")]
+ [Obsolete("Use IAuthenticationRequest.DiscoveryResult.IsExtensionSupported instead.")]
+ bool IsExtensionSupported<T>() where T : IOpenIdMessageExtension, new();
+
+ /// <summary>
+ /// Checks whether the OpenId Identifier claims support for a given extension.
+ /// </summary>
+ /// <param name="extensionType">The extension whose support is being queried.</param>
+ /// <returns>True if support for the extension is advertised. False otherwise.</returns>
+ /// <remarks>
+ /// Note that a true or false return value is no guarantee of a Provider's
+ /// support for or lack of support for an extension. The return value is
+ /// determined by how the authenticating user filled out his/her XRDS document only.
+ /// The only way to be sure of support for a given extension is to include
+ /// the extension in the request and see if a response comes back for that extension.
+ /// </remarks>
+ [Obsolete("Use IAuthenticationRequest.DiscoveryResult.IsExtensionSupported instead.")]
+ bool IsExtensionSupported(Type extensionType);
+ }
+
+ /// <summary>
+ /// Code contract for the <see cref="IProviderEndpoint"/> type.
+ /// </summary>
+ [ContractClassFor(typeof(IProviderEndpoint))]
+ internal abstract class IProviderEndpointContract : IProviderEndpoint {
+ /// <summary>
+ /// Prevents a default instance of the <see cref="IProviderEndpointContract"/> class from being created.
+ /// </summary>
+ private IProviderEndpointContract() {
+ }
+
+ #region IProviderEndpoint Members
+
+ /// <summary>
+ /// Gets the detected version of OpenID implemented by the Provider.
+ /// </summary>
+ Version IProviderEndpoint.Version {
+ get {
+ Contract.Ensures(Contract.Result<Version>() != null);
+ throw new System.NotImplementedException();
+ }
+ }
+
+ /// <summary>
+ /// Gets the URL that the OpenID Provider receives authentication requests at.
+ /// </summary>
+ Uri IProviderEndpoint.Uri {
+ get {
+ Contract.Ensures(Contract.Result<Uri>() != null);
+ throw new System.NotImplementedException();
+ }
+ }
+
+ /// <summary>
+ /// Checks whether the OpenId Identifier claims support for a given extension.
+ /// </summary>
+ /// <typeparam name="T">The extension whose support is being queried.</typeparam>
+ /// <returns>
+ /// True if support for the extension is advertised. False otherwise.
+ /// </returns>
+ /// <remarks>
+ /// Note that a true or false return value is no guarantee of a Provider's
+ /// support for or lack of support for an extension. The return value is
+ /// determined by how the authenticating user filled out his/her XRDS document only.
+ /// The only way to be sure of support for a given extension is to include
+ /// the extension in the request and see if a response comes back for that extension.
+ /// </remarks>
+ bool IProviderEndpoint.IsExtensionSupported<T>() {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Checks whether the OpenId Identifier claims support for a given extension.
+ /// </summary>
+ /// <param name="extensionType">The extension whose support is being queried.</param>
+ /// <returns>
+ /// True if support for the extension is advertised. False otherwise.
+ /// </returns>
+ /// <remarks>
+ /// Note that a true or false return value is no guarantee of a Provider's
+ /// support for or lack of support for an extension. The return value is
+ /// determined by how the authenticating user filled out his/her XRDS document only.
+ /// The only way to be sure of support for a given extension is to include
+ /// the extension in the request and see if a response comes back for that extension.
+ /// </remarks>
+ bool IProviderEndpoint.IsExtensionSupported(Type extensionType) {
+ Requires.NotNullSubtype<IOpenIdMessageExtension>(extensionType, "extensionType");
+ throw new NotImplementedException();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Identifier.cs b/src/DotNetOpenAuth.OpenId/OpenId/Identifier.cs
new file mode 100644
index 0000000..3cdd0f3
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Identifier.cs
@@ -0,0 +1,336 @@
+//-----------------------------------------------------------------------
+// <copyright file="Identifier.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Reflection;
+
+ /// <summary>
+ /// An Identifier is either a "http" or "https" URI, or an XRI.
+ /// </summary>
+ [Serializable]
+ [ContractVerification(true)]
+ [Pure]
+ [ContractClass(typeof(IdentifierContract))]
+ [DefaultEncoder(typeof(IdentifierEncoder))]
+ public abstract class Identifier {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Identifier"/> class.
+ /// </summary>
+ /// <param name="originalString">The original string before any normalization.</param>
+ /// <param name="isDiscoverySecureEndToEnd">Whether the derived class is prepared to guarantee end-to-end discovery
+ /// and initial redirect for authentication is performed using SSL.</param>
+ [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "string", Justification = "Emphasis on string instead of the strong-typed Identifier.")]
+ protected Identifier(string originalString, bool isDiscoverySecureEndToEnd) {
+ this.OriginalString = originalString;
+ this.IsDiscoverySecureEndToEnd = isDiscoverySecureEndToEnd;
+ }
+
+ /// <summary>
+ /// Gets the original string that was normalized to create this Identifier.
+ /// </summary>
+ internal string OriginalString { get; private set; }
+
+ /// <summary>
+ /// Gets the Identifier in the form in which it should be serialized.
+ /// </summary>
+ /// <value>
+ /// For Identifiers that were originally deserialized, this is the exact same
+ /// string that was deserialized. For Identifiers instantiated in some other way, this is
+ /// the normalized form of the string used to instantiate the identifier.
+ /// </value>
+ internal virtual string SerializedString {
+ get { return this.IsDeserializedInstance ? this.OriginalString : this.ToString(); }
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether <see cref="Identifier"/> instances are considered equal
+ /// based solely on their string reprsentations.
+ /// </summary>
+ /// <remarks>
+ /// This property serves as a test hook, so that MockIdentifier instances can be considered "equal"
+ /// to UriIdentifier instances.
+ /// </remarks>
+ protected internal static bool EqualityOnStrings { get; set; }
+
+ /// <summary>
+ /// Gets a value indicating whether this Identifier will ensure SSL is
+ /// used throughout the discovery phase and initial redirect of authentication.
+ /// </summary>
+ /// <remarks>
+ /// If this is <c>false</c>, a value of <c>true</c> may be obtained by calling
+ /// <see cref="TryRequireSsl"/>.
+ /// </remarks>
+ protected internal bool IsDiscoverySecureEndToEnd { get; private set; }
+
+ /// <summary>
+ /// Gets a value indicating whether this instance was initialized from
+ /// deserializing a message.
+ /// </summary>
+ /// <remarks>
+ /// This is interesting because when an Identifier comes from the network,
+ /// we can't normalize it and then expect signatures to still verify.
+ /// But if the Identifier is initialized locally, we can and should normalize it
+ /// before serializing it.
+ /// </remarks>
+ protected bool IsDeserializedInstance { get; private set; }
+
+ /// <summary>
+ /// Converts the string representation of an Identifier to its strong type.
+ /// </summary>
+ /// <param name="identifier">The identifier.</param>
+ /// <returns>The particular Identifier instance to represent the value given.</returns>
+ [SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", Justification = "Not all identifiers are URIs.")]
+ [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Our named alternate is Parse.")]
+ [DebuggerStepThrough]
+ public static implicit operator Identifier(string identifier) {
+ Requires.True(identifier == null || identifier.Length > 0, "identifier");
+ Contract.Ensures((identifier == null) == (Contract.Result<Identifier>() == null));
+
+ if (identifier == null) {
+ return null;
+ }
+ return Parse(identifier);
+ }
+
+ /// <summary>
+ /// Converts a given Uri to a strongly-typed Identifier.
+ /// </summary>
+ /// <param name="identifier">The identifier to convert.</param>
+ /// <returns>The result of the conversion.</returns>
+ [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "We have a Parse function.")]
+ [DebuggerStepThrough]
+ public static implicit operator Identifier(Uri identifier) {
+ Contract.Ensures((identifier == null) == (Contract.Result<Identifier>() == null));
+ if (identifier == null) {
+ return null;
+ }
+
+ return new UriIdentifier(identifier);
+ }
+
+ /// <summary>
+ /// Converts an Identifier to its string representation.
+ /// </summary>
+ /// <param name="identifier">The identifier to convert to a string.</param>
+ /// <returns>The result of the conversion.</returns>
+ [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "We have a Parse function.")]
+ [DebuggerStepThrough]
+ public static implicit operator string(Identifier identifier) {
+ Contract.Ensures((identifier == null) == (Contract.Result<string>() == null));
+ if (identifier == null) {
+ return null;
+ }
+ return identifier.ToString();
+ }
+
+ /// <summary>
+ /// Parses an identifier string and automatically determines
+ /// whether it is an XRI or URI.
+ /// </summary>
+ /// <param name="identifier">Either a URI or XRI identifier.</param>
+ /// <returns>An <see cref="Identifier"/> instance for the given value.</returns>
+ [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Some of these identifiers are not properly formatted to be Uris at this stage.")]
+ public static Identifier Parse(string identifier) {
+ Requires.NotNullOrEmpty(identifier, "identifier");
+ Contract.Ensures(Contract.Result<Identifier>() != null);
+
+ return Parse(identifier, false);
+ }
+
+ /// <summary>
+ /// Parses an identifier string and automatically determines
+ /// whether it is an XRI or URI.
+ /// </summary>
+ /// <param name="identifier">Either a URI or XRI identifier.</param>
+ /// <param name="serializeExactValue">if set to <c>true</c> this Identifier will serialize exactly as given rather than in its normalized form.</param>
+ /// <returns>
+ /// An <see cref="Identifier"/> instance for the given value.
+ /// </returns>
+ [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Some of these identifiers are not properly formatted to be Uris at this stage.")]
+ public static Identifier Parse(string identifier, bool serializeExactValue) {
+ Requires.NotNullOrEmpty(identifier, "identifier");
+ Contract.Ensures(Contract.Result<Identifier>() != null);
+
+ Identifier id;
+ if (XriIdentifier.IsValidXri(identifier)) {
+ id = new XriIdentifier(identifier);
+ } else {
+ id = new UriIdentifier(identifier);
+ }
+
+ id.IsDeserializedInstance = serializeExactValue;
+ return id;
+ }
+
+ /// <summary>
+ /// Attempts to parse a string for an OpenId Identifier.
+ /// </summary>
+ /// <param name="value">The string to be parsed.</param>
+ /// <param name="result">The parsed Identifier form.</param>
+ /// <returns>
+ /// True if the operation was successful. False if the string was not a valid OpenId Identifier.
+ /// </returns>
+ public static bool TryParse(string value, out Identifier result) {
+ if (string.IsNullOrEmpty(value)) {
+ result = null;
+ return false;
+ }
+
+ if (IsValid(value)) {
+ result = Parse(value);
+ return true;
+ } else {
+ result = null;
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Checks the validity of a given string representation of some Identifier.
+ /// </summary>
+ /// <param name="identifier">The identifier.</param>
+ /// <returns>
+ /// <c>true</c> if the specified identifier is valid; otherwise, <c>false</c>.
+ /// </returns>
+ [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Some of these identifiers are not properly formatted to be Uris at this stage.")]
+ public static bool IsValid(string identifier) {
+ Requires.NotNullOrEmpty(identifier, "identifier");
+ return XriIdentifier.IsValidXri(identifier) || UriIdentifier.IsValidUri(identifier);
+ }
+
+ /// <summary>
+ /// Tests equality between two <see cref="Identifier"/>s.
+ /// </summary>
+ /// <param name="id1">The first Identifier.</param>
+ /// <param name="id2">The second Identifier.</param>
+ /// <returns>
+ /// <c>true</c> if the two instances should be considered equal; <c>false</c> otherwise.
+ /// </returns>
+ public static bool operator ==(Identifier id1, Identifier id2) {
+ return id1.EqualsNullSafe(id2);
+ }
+
+ /// <summary>
+ /// Tests inequality between two <see cref="Identifier"/>s.
+ /// </summary>
+ /// <param name="id1">The first Identifier.</param>
+ /// <param name="id2">The second Identifier.</param>
+ /// <returns>
+ /// <c>true</c> if the two instances should be considered unequal; <c>false</c> if they are equal.
+ /// </returns>
+ public static bool operator !=(Identifier id1, Identifier id2) {
+ return !id1.EqualsNullSafe(id2);
+ }
+
+ /// <summary>
+ /// Tests equality between two <see cref="Identifier"/>s.
+ /// </summary>
+ /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
+ /// <returns>
+ /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
+ /// </returns>
+ /// <exception cref="T:System.NullReferenceException">
+ /// The <paramref name="obj"/> parameter is null.
+ /// </exception>
+ public override bool Equals(object obj) {
+ Debug.Fail("This should be overridden in every derived class.");
+ return base.Equals(obj);
+ }
+
+ /// <summary>
+ /// Gets the hash code for an <see cref="Identifier"/> for storage in a hashtable.
+ /// </summary>
+ /// <returns>
+ /// A hash code for the current <see cref="T:System.Object"/>.
+ /// </returns>
+ public override int GetHashCode() {
+ Debug.Fail("This should be overridden in every derived class.");
+ return base.GetHashCode();
+ }
+
+ /// <summary>
+ /// Reparses the specified identifier in order to be assured that the concrete type that
+ /// implements the identifier is one of the well-known ones.
+ /// </summary>
+ /// <param name="identifier">The identifier.</param>
+ /// <returns>Either <see cref="XriIdentifier"/> or <see cref="UriIdentifier"/>.</returns>
+ internal static Identifier Reparse(Identifier identifier) {
+ Requires.NotNull(identifier, "identifier");
+ Contract.Ensures(Contract.Result<Identifier>() != null);
+
+ return Parse(identifier, identifier.IsDeserializedInstance);
+ }
+
+ /// <summary>
+ /// Returns an <see cref="Identifier"/> that has no URI fragment.
+ /// Quietly returns the original <see cref="Identifier"/> if it is not
+ /// a <see cref="UriIdentifier"/> or no fragment exists.
+ /// </summary>
+ /// <returns>A new <see cref="Identifier"/> instance if there was a
+ /// fragment to remove, otherwise this same instance..</returns>
+ [Pure]
+ internal abstract Identifier TrimFragment();
+
+ /// <summary>
+ /// Converts a given identifier to its secure equivalent.
+ /// UriIdentifiers originally created with an implied HTTP scheme change to HTTPS.
+ /// Discovery is made to require SSL for the entire resolution process.
+ /// </summary>
+ /// <param name="secureIdentifier">
+ /// The newly created secure identifier.
+ /// If the conversion fails, <paramref name="secureIdentifier"/> retains
+ /// <i>this</i> identifiers identity, but will never discover any endpoints.
+ /// </param>
+ /// <returns>
+ /// True if the secure conversion was successful.
+ /// False if the Identifier was originally created with an explicit HTTP scheme.
+ /// </returns>
+ internal abstract bool TryRequireSsl(out Identifier secureIdentifier);
+
+ /// <summary>
+ /// Provides conversions to and from strings for messages that include members of this type.
+ /// </summary>
+ private class IdentifierEncoder : IMessagePartOriginalEncoder {
+ /// <summary>
+ /// Encodes the specified value as the original value that was formerly decoded.
+ /// </summary>
+ /// <param name="value">The value. Guaranteed to never be null.</param>
+ /// <returns>The <paramref name="value"/> in string form, ready for message transport.</returns>
+ public string EncodeAsOriginalString(object value) {
+ Requires.NotNull(value, "value");
+ return ((Identifier)value).OriginalString;
+ }
+
+ /// <summary>
+ /// Encodes the specified value.
+ /// </summary>
+ /// <param name="value">The value. Guaranteed to never be null.</param>
+ /// <returns>The <paramref name="value"/> in string form, ready for message transport.</returns>
+ public string Encode(object value) {
+ Requires.NotNull(value, "value");
+ return ((Identifier)value).SerializedString;
+ }
+
+ /// <summary>
+ /// Decodes the specified value.
+ /// </summary>
+ /// <param name="value">The string value carried by the transport. Guaranteed to never be null, although it may be empty.</param>
+ /// <returns>The deserialized form of the given string.</returns>
+ /// <exception cref="FormatException">Thrown when the string value given cannot be decoded into the required object type.</exception>
+ public object Decode(string value) {
+ Requires.NotNull(value, "value");
+ ErrorUtilities.VerifyFormat(value.Length > 0, MessagingStrings.NonEmptyStringExpected);
+ return Identifier.Parse(value, true);
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/IdentifierContract.cs b/src/DotNetOpenAuth.OpenId/OpenId/IdentifierContract.cs
new file mode 100644
index 0000000..0156d12
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/IdentifierContract.cs
@@ -0,0 +1,56 @@
+//-----------------------------------------------------------------------
+// <copyright file="IdentifierContract.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// Code Contract for the <see cref="Identifier"/> class.
+ /// </summary>
+ [ContractClassFor(typeof(Identifier))]
+ internal abstract class IdentifierContract : Identifier {
+ /// <summary>
+ /// Prevents a default instance of the IdentifierContract class from being created.
+ /// </summary>
+ private IdentifierContract()
+ : base(null, false) {
+ }
+
+ /// <summary>
+ /// Returns an <see cref="Identifier"/> that has no URI fragment.
+ /// Quietly returns the original <see cref="Identifier"/> if it is not
+ /// a <see cref="UriIdentifier"/> or no fragment exists.
+ /// </summary>
+ /// <returns>
+ /// A new <see cref="Identifier"/> instance if there was a
+ /// fragment to remove, otherwise this same instance..
+ /// </returns>
+ internal override Identifier TrimFragment() {
+ Contract.Ensures(Contract.Result<Identifier>() != null);
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Converts a given identifier to its secure equivalent.
+ /// UriIdentifiers originally created with an implied HTTP scheme change to HTTPS.
+ /// Discovery is made to require SSL for the entire resolution process.
+ /// </summary>
+ /// <param name="secureIdentifier">The newly created secure identifier.
+ /// If the conversion fails, <paramref name="secureIdentifier"/> retains
+ /// <i>this</i> identifiers identity, but will never discover any endpoints.</param>
+ /// <returns>
+ /// True if the secure conversion was successful.
+ /// False if the Identifier was originally created with an explicit HTTP scheme.
+ /// </returns>
+ internal override bool TryRequireSsl(out Identifier secureIdentifier) {
+ Contract.Ensures(Contract.ValueAtReturn(out secureIdentifier) != null);
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/IdentifierDiscoveryResult.cs b/src/DotNetOpenAuth.OpenId/OpenId/IdentifierDiscoveryResult.cs
new file mode 100644
index 0000000..ab69bf6
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/IdentifierDiscoveryResult.cs
@@ -0,0 +1,497 @@
+//-----------------------------------------------------------------------
+// <copyright file="IdentifierDiscoveryResult.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId {
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.ObjectModel;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.IO;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+
+ /// <summary>
+ /// Represents a single OP endpoint from discovery on some OpenID Identifier.
+ /// </summary>
+ [DebuggerDisplay("ClaimedIdentifier: {ClaimedIdentifier}, ProviderEndpoint: {ProviderEndpoint}, OpenId: {Protocol.Version}")]
+ public sealed class IdentifierDiscoveryResult : IProviderEndpoint {
+ /// <summary>
+ /// Backing field for the <see cref="Protocol"/> property.
+ /// </summary>
+ private Protocol protocol;
+
+ /// <summary>
+ /// Backing field for the <see cref="ClaimedIdentifier"/> property.
+ /// </summary>
+ private Identifier claimedIdentifier;
+
+ /// <summary>
+ /// Backing field for the <see cref="FriendlyIdentifierForDisplay"/> property.
+ /// </summary>
+ private string friendlyIdentifierForDisplay;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="IdentifierDiscoveryResult"/> class.
+ /// </summary>
+ /// <param name="providerEndpoint">The provider endpoint.</param>
+ /// <param name="claimedIdentifier">The Claimed Identifier.</param>
+ /// <param name="userSuppliedIdentifier">The User-supplied Identifier.</param>
+ /// <param name="providerLocalIdentifier">The Provider Local Identifier.</param>
+ /// <param name="servicePriority">The service priority.</param>
+ /// <param name="uriPriority">The URI priority.</param>
+ private IdentifierDiscoveryResult(ProviderEndpointDescription providerEndpoint, Identifier claimedIdentifier, Identifier userSuppliedIdentifier, Identifier providerLocalIdentifier, int? servicePriority, int? uriPriority) {
+ Requires.NotNull(providerEndpoint, "providerEndpoint");
+ Requires.NotNull(claimedIdentifier, "claimedIdentifier");
+ this.ProviderEndpoint = providerEndpoint.Uri;
+ this.Capabilities = new ReadOnlyCollection<string>(providerEndpoint.Capabilities);
+ this.Version = providerEndpoint.Version;
+ this.ClaimedIdentifier = claimedIdentifier;
+ this.ProviderLocalIdentifier = providerLocalIdentifier ?? claimedIdentifier;
+ this.UserSuppliedIdentifier = userSuppliedIdentifier;
+ this.ServicePriority = servicePriority;
+ this.ProviderEndpointPriority = uriPriority;
+ }
+
+ /// <summary>
+ /// Gets the detected version of OpenID implemented by the Provider.
+ /// </summary>
+ public Version Version { get; private set; }
+
+ /// <summary>
+ /// Gets the Identifier that was presented by the end user to the Relying Party,
+ /// or selected by the user at the OpenID Provider.
+ /// During the initiation phase of the protocol, an end user may enter
+ /// either their own Identifier or an OP Identifier. If an OP Identifier
+ /// is used, the OP may then assist the end user in selecting an Identifier
+ /// to share with the Relying Party.
+ /// </summary>
+ public Identifier UserSuppliedIdentifier { get; private set; }
+
+ /// <summary>
+ /// Gets the Identifier that the end user claims to control.
+ /// </summary>
+ public Identifier ClaimedIdentifier {
+ get {
+ return this.claimedIdentifier;
+ }
+
+ internal set {
+ // Take care to reparse the incoming identifier to make sure it's
+ // not a derived type that will override expected behavior.
+ // Elsewhere in this class, we count on the fact that this property
+ // is either UriIdentifier or XriIdentifier. MockIdentifier messes it up.
+ this.claimedIdentifier = value != null ? Identifier.Reparse(value) : null;
+ }
+ }
+
+ /// <summary>
+ /// Gets an alternate Identifier for an end user that is local to a
+ /// particular OP and thus not necessarily under the end user's
+ /// control.
+ /// </summary>
+ public Identifier ProviderLocalIdentifier { get; private set; }
+
+ /// <summary>
+ /// Gets a more user-friendly (but NON-secure!) string to display to the user as his identifier.
+ /// </summary>
+ /// <returns>A human-readable, abbreviated (but not secure) identifier the user MAY recognize as his own.</returns>
+ public string FriendlyIdentifierForDisplay {
+ get {
+ if (this.friendlyIdentifierForDisplay == null) {
+ XriIdentifier xri = this.ClaimedIdentifier as XriIdentifier;
+ UriIdentifier uri = this.ClaimedIdentifier as UriIdentifier;
+ if (xri != null) {
+ if (this.UserSuppliedIdentifier == null || string.Equals(this.UserSuppliedIdentifier, this.ClaimedIdentifier, StringComparison.OrdinalIgnoreCase)) {
+ this.friendlyIdentifierForDisplay = this.ClaimedIdentifier;
+ } else {
+ this.friendlyIdentifierForDisplay = this.UserSuppliedIdentifier;
+ }
+ } else if (uri != null) {
+ if (uri != this.Protocol.ClaimedIdentifierForOPIdentifier) {
+ string displayUri = uri.Uri.Host;
+
+ // We typically want to display the path, because that will often have the username in it.
+ // As Google Apps for Domains and the like become more popular, a standard /openid path
+ // will often appear, which is not helpful to identifying the user so we'll avoid including
+ // that path if it's present.
+ if (!string.Equals(uri.Uri.AbsolutePath, "/openid", StringComparison.OrdinalIgnoreCase)) {
+ displayUri += uri.Uri.AbsolutePath.TrimEnd('/');
+ }
+
+ // Multi-byte unicode characters get encoded by the Uri class for transit.
+ // Since this is for display purposes, we want to reverse this and display a readable
+ // representation of these foreign characters.
+ this.friendlyIdentifierForDisplay = Uri.UnescapeDataString(displayUri);
+ }
+ } else {
+ ErrorUtilities.ThrowInternal("ServiceEndpoint.ClaimedIdentifier neither XRI nor URI.");
+ this.friendlyIdentifierForDisplay = this.ClaimedIdentifier;
+ }
+ }
+
+ return this.friendlyIdentifierForDisplay;
+ }
+ }
+
+ /// <summary>
+ /// Gets the provider endpoint.
+ /// </summary>
+ public Uri ProviderEndpoint { get; private set; }
+
+ /// <summary>
+ /// Gets the @priority given in the XRDS document for this specific OP endpoint.
+ /// </summary>
+ public int? ProviderEndpointPriority { get; private set; }
+
+ /// <summary>
+ /// Gets the @priority given in the XRDS document for this service
+ /// (which may consist of several endpoints).
+ /// </summary>
+ public int? ServicePriority { get; private set; }
+
+ /// <summary>
+ /// Gets the collection of service type URIs found in the XRDS document describing this Provider.
+ /// </summary>
+ /// <value>Should never be null, but may be empty.</value>
+ public ReadOnlyCollection<string> Capabilities { get; private set; }
+
+ #region IProviderEndpoint Members
+
+ /// <summary>
+ /// Gets the URL that the OpenID Provider receives authentication requests at.
+ /// </summary>
+ /// <value>This value MUST be an absolute HTTP or HTTPS URL.</value>
+ Uri IProviderEndpoint.Uri {
+ get { return this.ProviderEndpoint; }
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Gets an XRDS sorting routine that uses the XRDS Service/@Priority
+ /// attribute to determine order.
+ /// </summary>
+ /// <remarks>
+ /// Endpoints lacking any priority value are sorted to the end of the list.
+ /// </remarks>
+ internal static Comparison<IdentifierDiscoveryResult> EndpointOrder {
+ get {
+ // Sort first by service type (OpenID 2.0, 1.1, 1.0),
+ // then by Service/@priority, then by Service/Uri/@priority
+ return (se1, se2) => {
+ int result = GetEndpointPrecedenceOrderByServiceType(se1).CompareTo(GetEndpointPrecedenceOrderByServiceType(se2));
+ if (result != 0) {
+ return result;
+ }
+ if (se1.ServicePriority.HasValue && se2.ServicePriority.HasValue) {
+ result = se1.ServicePriority.Value.CompareTo(se2.ServicePriority.Value);
+ if (result != 0) {
+ return result;
+ }
+ if (se1.ProviderEndpointPriority.HasValue && se2.ProviderEndpointPriority.HasValue) {
+ return se1.ProviderEndpointPriority.Value.CompareTo(se2.ProviderEndpointPriority.Value);
+ } else if (se1.ProviderEndpointPriority.HasValue) {
+ return -1;
+ } else if (se2.ProviderEndpointPriority.HasValue) {
+ return 1;
+ } else {
+ return 0;
+ }
+ } else {
+ if (se1.ServicePriority.HasValue) {
+ return -1;
+ } else if (se2.ServicePriority.HasValue) {
+ return 1;
+ } else {
+ // neither service defines a priority, so base ordering by uri priority.
+ if (se1.ProviderEndpointPriority.HasValue && se2.ProviderEndpointPriority.HasValue) {
+ return se1.ProviderEndpointPriority.Value.CompareTo(se2.ProviderEndpointPriority.Value);
+ } else if (se1.ProviderEndpointPriority.HasValue) {
+ return -1;
+ } else if (se2.ProviderEndpointPriority.HasValue) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ }
+ };
+ }
+ }
+
+ /// <summary>
+ /// Gets the protocol used by the OpenID Provider.
+ /// </summary>
+ internal Protocol Protocol {
+ get {
+ if (this.protocol == null) {
+ this.protocol = Protocol.Lookup(this.Version);
+ }
+
+ return this.protocol;
+ }
+ }
+
+ /// <summary>
+ /// Implements the operator ==.
+ /// </summary>
+ /// <param name="se1">The first service endpoint.</param>
+ /// <param name="se2">The second service endpoint.</param>
+ /// <returns>The result of the operator.</returns>
+ public static bool operator ==(IdentifierDiscoveryResult se1, IdentifierDiscoveryResult se2) {
+ return se1.EqualsNullSafe(se2);
+ }
+
+ /// <summary>
+ /// Implements the operator !=.
+ /// </summary>
+ /// <param name="se1">The first service endpoint.</param>
+ /// <param name="se2">The second service endpoint.</param>
+ /// <returns>The result of the operator.</returns>
+ public static bool operator !=(IdentifierDiscoveryResult se1, IdentifierDiscoveryResult se2) {
+ return !(se1 == se2);
+ }
+
+ /// <summary>
+ /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
+ /// </summary>
+ /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
+ /// <returns>
+ /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
+ /// </returns>
+ /// <exception cref="T:System.NullReferenceException">
+ /// The <paramref name="obj"/> parameter is null.
+ /// </exception>
+ public override bool Equals(object obj) {
+ var other = obj as IdentifierDiscoveryResult;
+ if (other == null) {
+ return false;
+ }
+
+ // We specifically do not check our ProviderSupportedServiceTypeUris array
+ // or the priority field
+ // as that is not persisted in our tokens, and it is not part of the
+ // important assertion validation that is part of the spec.
+ return
+ this.ClaimedIdentifier == other.ClaimedIdentifier &&
+ this.ProviderEndpoint == other.ProviderEndpoint &&
+ this.ProviderLocalIdentifier == other.ProviderLocalIdentifier &&
+ this.Protocol.EqualsPractically(other.Protocol);
+ }
+
+ /// <summary>
+ /// Serves as a hash function for a particular type.
+ /// </summary>
+ /// <returns>
+ /// A hash code for the current <see cref="T:System.Object"/>.
+ /// </returns>
+ public override int GetHashCode() {
+ return this.ClaimedIdentifier.GetHashCode();
+ }
+
+ /// <summary>
+ /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
+ /// </summary>
+ /// <returns>
+ /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
+ /// </returns>
+ public override string ToString() {
+ StringBuilder builder = new StringBuilder();
+ builder.AppendLine("ClaimedIdentifier: " + this.ClaimedIdentifier);
+ builder.AppendLine("ProviderLocalIdentifier: " + this.ProviderLocalIdentifier);
+ builder.AppendLine("ProviderEndpoint: " + this.ProviderEndpoint);
+ builder.AppendLine("OpenID version: " + this.Version);
+ builder.AppendLine("Service Type URIs:");
+ foreach (string serviceTypeUri in this.Capabilities) {
+ builder.Append("\t");
+ builder.AppendLine(serviceTypeUri);
+ }
+ builder.Length -= Environment.NewLine.Length; // trim last newline
+ return builder.ToString();
+ }
+
+ /// <summary>
+ /// Checks whether the OpenId Identifier claims support for a given extension.
+ /// </summary>
+ /// <typeparam name="T">The extension whose support is being queried.</typeparam>
+ /// <returns>
+ /// True if support for the extension is advertised. False otherwise.
+ /// </returns>
+ /// <remarks>
+ /// Note that a true or false return value is no guarantee of a Provider's
+ /// support for or lack of support for an extension. The return value is
+ /// determined by how the authenticating user filled out his/her XRDS document only.
+ /// The only way to be sure of support for a given extension is to include
+ /// the extension in the request and see if a response comes back for that extension.
+ /// </remarks>
+ [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "No parameter at all.")]
+ public bool IsExtensionSupported<T>() where T : IOpenIdMessageExtension, new() {
+ T extension = new T();
+ return this.IsExtensionSupported(extension);
+ }
+
+ /// <summary>
+ /// Checks whether the OpenId Identifier claims support for a given extension.
+ /// </summary>
+ /// <param name="extensionType">The extension whose support is being queried.</param>
+ /// <returns>
+ /// True if support for the extension is advertised. False otherwise.
+ /// </returns>
+ /// <remarks>
+ /// Note that a true or false return value is no guarantee of a Provider's
+ /// support for or lack of support for an extension. The return value is
+ /// determined by how the authenticating user filled out his/her XRDS document only.
+ /// The only way to be sure of support for a given extension is to include
+ /// the extension in the request and see if a response comes back for that extension.
+ /// </remarks>
+ public bool IsExtensionSupported(Type extensionType) {
+ var extension = (IOpenIdMessageExtension)Activator.CreateInstance(extensionType);
+ return this.IsExtensionSupported(extension);
+ }
+
+ /// <summary>
+ /// Determines whether a given extension is supported by this endpoint.
+ /// </summary>
+ /// <param name="extension">An instance of the extension to check support for.</param>
+ /// <returns>
+ /// <c>true</c> if the extension is supported by this endpoint; otherwise, <c>false</c>.
+ /// </returns>
+ public bool IsExtensionSupported(IOpenIdMessageExtension extension) {
+ Requires.NotNull(extension, "extension");
+
+ // Consider the primary case.
+ if (this.IsTypeUriPresent(extension.TypeUri)) {
+ return true;
+ }
+
+ // Consider the secondary cases.
+ if (extension.AdditionalSupportedTypeUris != null) {
+ if (extension.AdditionalSupportedTypeUris.Any(typeUri => this.IsTypeUriPresent(typeUri))) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Creates a <see cref="IdentifierDiscoveryResult"/> instance to represent some OP Identifier.
+ /// </summary>
+ /// <param name="providerIdentifier">The provider identifier (actually the user-supplied identifier).</param>
+ /// <param name="providerEndpoint">The provider endpoint.</param>
+ /// <param name="servicePriority">The service priority.</param>
+ /// <param name="uriPriority">The URI priority.</param>
+ /// <returns>The created <see cref="IdentifierDiscoveryResult"/> instance</returns>
+ internal static IdentifierDiscoveryResult CreateForProviderIdentifier(Identifier providerIdentifier, ProviderEndpointDescription providerEndpoint, int? servicePriority, int? uriPriority) {
+ Requires.NotNull(providerEndpoint, "providerEndpoint");
+
+ Protocol protocol = Protocol.Lookup(providerEndpoint.Version);
+
+ return new IdentifierDiscoveryResult(
+ providerEndpoint,
+ protocol.ClaimedIdentifierForOPIdentifier,
+ providerIdentifier,
+ protocol.ClaimedIdentifierForOPIdentifier,
+ servicePriority,
+ uriPriority);
+ }
+
+ /// <summary>
+ /// Creates a <see cref="IdentifierDiscoveryResult"/> instance to represent some Claimed Identifier.
+ /// </summary>
+ /// <param name="claimedIdentifier">The claimed identifier.</param>
+ /// <param name="providerLocalIdentifier">The provider local identifier.</param>
+ /// <param name="providerEndpoint">The provider endpoint.</param>
+ /// <param name="servicePriority">The service priority.</param>
+ /// <param name="uriPriority">The URI priority.</param>
+ /// <returns>The created <see cref="IdentifierDiscoveryResult"/> instance</returns>
+ internal static IdentifierDiscoveryResult CreateForClaimedIdentifier(Identifier claimedIdentifier, Identifier providerLocalIdentifier, ProviderEndpointDescription providerEndpoint, int? servicePriority, int? uriPriority) {
+ return CreateForClaimedIdentifier(claimedIdentifier, null, providerLocalIdentifier, providerEndpoint, servicePriority, uriPriority);
+ }
+
+ /// <summary>
+ /// Creates a <see cref="IdentifierDiscoveryResult"/> instance to represent some Claimed Identifier.
+ /// </summary>
+ /// <param name="claimedIdentifier">The claimed identifier.</param>
+ /// <param name="userSuppliedIdentifier">The user supplied identifier.</param>
+ /// <param name="providerLocalIdentifier">The provider local identifier.</param>
+ /// <param name="providerEndpoint">The provider endpoint.</param>
+ /// <param name="servicePriority">The service priority.</param>
+ /// <param name="uriPriority">The URI priority.</param>
+ /// <returns>The created <see cref="IdentifierDiscoveryResult"/> instance</returns>
+ internal static IdentifierDiscoveryResult CreateForClaimedIdentifier(Identifier claimedIdentifier, Identifier userSuppliedIdentifier, Identifier providerLocalIdentifier, ProviderEndpointDescription providerEndpoint, int? servicePriority, int? uriPriority) {
+ return new IdentifierDiscoveryResult(providerEndpoint, claimedIdentifier, userSuppliedIdentifier, providerLocalIdentifier, servicePriority, uriPriority);
+ }
+
+ /// <summary>
+ /// Determines whether a given type URI is present on the specified provider endpoint.
+ /// </summary>
+ /// <param name="typeUri">The type URI.</param>
+ /// <returns>
+ /// <c>true</c> if the type URI is present on the specified provider endpoint; otherwise, <c>false</c>.
+ /// </returns>
+ internal bool IsTypeUriPresent(string typeUri) {
+ Requires.NotNullOrEmpty(typeUri, "typeUri");
+ return this.Capabilities.Contains(typeUri);
+ }
+
+ /// <summary>
+ /// Sets the Capabilities property (this method is a test hook.)
+ /// </summary>
+ /// <param name="value">The value.</param>
+ /// <remarks>The publicize.exe tool should work for the unit tests, but for some reason it fails on the build server.</remarks>
+ internal void SetCapabilitiesForTestHook(ReadOnlyCollection<string> value) {
+ this.Capabilities = value;
+ }
+
+ /// <summary>
+ /// Gets the priority rating for a given type of endpoint, allowing a
+ /// priority sorting of endpoints.
+ /// </summary>
+ /// <param name="endpoint">The endpoint to prioritize.</param>
+ /// <returns>An arbitary integer, which may be used for sorting against other returned values from this method.</returns>
+ private static double GetEndpointPrecedenceOrderByServiceType(IdentifierDiscoveryResult endpoint) {
+ // The numbers returned from this method only need to compare against other numbers
+ // from this method, which makes them arbitrary but relational to only others here.
+ if (endpoint.Capabilities.Contains(Protocol.V20.OPIdentifierServiceTypeURI)) {
+ return 0;
+ }
+ if (endpoint.Capabilities.Contains(Protocol.V20.ClaimedIdentifierServiceTypeURI)) {
+ return 1;
+ }
+ if (endpoint.Capabilities.Contains(Protocol.V11.ClaimedIdentifierServiceTypeURI)) {
+ return 2;
+ }
+ if (endpoint.Capabilities.Contains(Protocol.V10.ClaimedIdentifierServiceTypeURI)) {
+ return 3;
+ }
+ return 10;
+ }
+
+#if CONTRACTS_FULL
+ /// <summary>
+ /// Verifies conditions that should be true for any valid state of this object.
+ /// </summary>
+ [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")]
+ [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")]
+ [ContractInvariantMethod]
+ private void ObjectInvariant() {
+ Contract.Invariant(this.ProviderEndpoint != null);
+ Contract.Invariant(this.ClaimedIdentifier != null);
+ Contract.Invariant(this.ProviderLocalIdentifier != null);
+ Contract.Invariant(this.Capabilities != null);
+ Contract.Invariant(this.Version != null);
+ Contract.Invariant(this.Protocol != null);
+ }
+#endif
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/IdentifierDiscoveryServices.cs b/src/DotNetOpenAuth.OpenId/OpenId/IdentifierDiscoveryServices.cs
new file mode 100644
index 0000000..6a3cfaa
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/IdentifierDiscoveryServices.cs
@@ -0,0 +1,82 @@
+//-----------------------------------------------------------------------
+// <copyright file="IdentifierDiscoveryServices.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId {
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using DotNetOpenAuth.Configuration;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// A service that can perform discovery on OpenID identifiers.
+ /// </summary>
+ internal class IdentifierDiscoveryServices {
+ /// <summary>
+ /// The RP or OP that is hosting these services.
+ /// </summary>
+ private readonly IOpenIdHost host;
+
+ /// <summary>
+ /// Backing field for the <see cref="DiscoveryServices"/> property.
+ /// </summary>
+ private readonly IList<IIdentifierDiscoveryService> discoveryServices = new List<IIdentifierDiscoveryService>(2);
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="IdentifierDiscoveryServices"/> class.
+ /// </summary>
+ /// <param name="host">The RP or OP that creates this instance.</param>
+ internal IdentifierDiscoveryServices(IOpenIdHost host) {
+ Requires.NotNull(host, "host");
+
+ this.host = host;
+ this.discoveryServices.AddRange(OpenIdElement.Configuration.RelyingParty.DiscoveryServices.CreateInstances(true));
+ }
+
+ /// <summary>
+ /// Gets the list of services that can perform discovery on identifiers given.
+ /// </summary>
+ public IList<IIdentifierDiscoveryService> DiscoveryServices {
+ get { return this.discoveryServices; }
+ }
+
+ /// <summary>
+ /// Performs discovery on the specified identifier.
+ /// </summary>
+ /// <param name="identifier">The identifier to discover services for.</param>
+ /// <returns>A non-null sequence of services discovered for the identifier.</returns>
+ public IEnumerable<IdentifierDiscoveryResult> Discover(Identifier identifier) {
+ Requires.NotNull(identifier, "identifier");
+ Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null);
+
+ IEnumerable<IdentifierDiscoveryResult> results = Enumerable.Empty<IdentifierDiscoveryResult>();
+ foreach (var discoverer in this.DiscoveryServices) {
+ bool abortDiscoveryChain;
+ var discoveryResults = discoverer.Discover(identifier, this.host.WebRequestHandler, out abortDiscoveryChain).CacheGeneratedResults();
+ results = results.Concat(discoveryResults);
+ if (abortDiscoveryChain) {
+ Logger.OpenId.InfoFormat("Further discovery on '{0}' was stopped by the {1} discovery service.", identifier, discoverer.GetType().Name);
+ break;
+ }
+ }
+
+ // If any OP Identifier service elements were found, we must not proceed
+ // to use any Claimed Identifier services, per OpenID 2.0 sections 7.3.2.2 and 11.2.
+ // For a discussion on this topic, see
+ // http://groups.google.com/group/dotnetopenid/browse_thread/thread/4b5a8c6b2210f387/5e25910e4d2252c8
+ // Sometimes the IIdentifierDiscoveryService will automatically filter this for us, but
+ // just to be sure, we'll do it here as well.
+ if (!this.host.SecuritySettings.AllowDualPurposeIdentifiers) {
+ results = results.CacheGeneratedResults(); // avoid performing discovery repeatedly
+ var opIdentifiers = results.Where(result => result.ClaimedIdentifier == result.Protocol.ClaimedIdentifierForOPIdentifier);
+ var claimedIdentifiers = results.Where(result => result.ClaimedIdentifier != result.Protocol.ClaimedIdentifierForOPIdentifier);
+ results = opIdentifiers.Any() ? opIdentifiers : claimedIdentifiers;
+ }
+
+ return results;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateDiffieHellmanRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateDiffieHellmanRequest.cs
new file mode 100644
index 0000000..ed96ce7
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateDiffieHellmanRequest.cs
@@ -0,0 +1,100 @@
+//-----------------------------------------------------------------------
+// <copyright file="AssociateDiffieHellmanRequest.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Globalization;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Reflection;
+ using Org.Mentalis.Security.Cryptography;
+
+ /// <summary>
+ /// An OpenID direct request from Relying Party to Provider to initiate an association that uses Diffie-Hellman encryption.
+ /// </summary>
+ internal class AssociateDiffieHellmanRequest : AssociateRequest {
+ /// <summary>
+ /// The (only) value we use for the X variable in the Diffie-Hellman algorithm.
+ /// </summary>
+ internal static readonly int DefaultX = 1024;
+
+ /// <summary>
+ /// The default gen value for the Diffie-Hellman algorithm.
+ /// </summary>
+ internal static readonly byte[] DefaultGen = { 2 };
+
+ /// <summary>
+ /// The default modulus value for the Diffie-Hellman algorithm.
+ /// </summary>
+ internal static readonly byte[] DefaultMod = {
+ 0, 220, 249, 58, 11, 136, 57, 114, 236, 14, 25, 152, 154, 197, 162,
+ 206, 49, 14, 29, 55, 113, 126, 141, 149, 113, 187, 118, 35, 115, 24,
+ 102, 230, 30, 247, 90, 46, 39, 137, 139, 5, 127, 152, 145, 194, 226,
+ 122, 99, 156, 63, 41, 182, 8, 20, 88, 28, 211, 178, 202, 57, 134, 210,
+ 104, 55, 5, 87, 125, 69, 194, 231, 229, 45, 200, 28, 122, 23, 24, 118,
+ 229, 206, 167, 75, 20, 72, 191, 223, 175, 24, 130, 142, 253, 37, 25,
+ 241, 78, 69, 227, 130, 102, 52, 175, 25, 73, 229, 181, 53, 204, 130,
+ 154, 72, 59, 138, 118, 34, 62, 93, 73, 10, 37, 127, 5, 189, 255, 22,
+ 242, 251, 34, 197, 131, 171 };
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AssociateDiffieHellmanRequest"/> class.
+ /// </summary>
+ /// <param name="version">The OpenID version this message must comply with.</param>
+ /// <param name="providerEndpoint">The OpenID Provider endpoint.</param>
+ internal AssociateDiffieHellmanRequest(Version version, Uri providerEndpoint)
+ : base(version, providerEndpoint) {
+ this.DiffieHellmanModulus = DefaultMod;
+ this.DiffieHellmanGen = DefaultGen;
+ }
+
+ /// <summary>
+ /// Gets or sets the openid.dh_modulus value.
+ /// </summary>
+ /// <value>May be null if the default value given in the OpenID spec is to be used.</value>
+ [MessagePart("openid.dh_modulus", IsRequired = false, AllowEmpty = false)]
+ internal byte[] DiffieHellmanModulus { get; set; }
+
+ /// <summary>
+ /// Gets or sets the openid.dh_gen value.
+ /// </summary>
+ /// <value>May be null if the default value given in the OpenID spec is to be used.</value>
+ [MessagePart("openid.dh_gen", IsRequired = false, AllowEmpty = false)]
+ internal byte[] DiffieHellmanGen { get; set; }
+
+ /// <summary>
+ /// Gets or sets the openid.dh_consumer_public value.
+ /// </summary>
+ /// <remarks>
+ /// This property is initialized with a call to <see cref="InitializeRequest"/>.
+ /// </remarks>
+ [MessagePart("openid.dh_consumer_public", IsRequired = true, AllowEmpty = false)]
+ internal byte[] DiffieHellmanConsumerPublic { get; set; }
+
+ /// <summary>
+ /// Gets the Diffie-Hellman algorithm.
+ /// </summary>
+ /// <remarks>
+ /// This property is initialized with a call to <see cref="InitializeRequest"/>.
+ /// </remarks>
+ internal DiffieHellman Algorithm { get; private set; }
+
+ /// <summary>
+ /// Called by the Relying Party to initialize the Diffie-Hellman algorithm and consumer public key properties.
+ /// </summary>
+ internal void InitializeRequest() {
+ if (this.DiffieHellmanModulus == null || this.DiffieHellmanGen == null) {
+ throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, OpenIdStrings.DiffieHellmanRequiredPropertiesNotSet, string.Join(", ", new string[] { "DiffieHellmanModulus", "DiffieHellmanGen" })));
+ }
+
+ this.Algorithm = new DiffieHellmanManaged(this.DiffieHellmanModulus ?? DefaultMod, this.DiffieHellmanGen ?? DefaultGen, DefaultX);
+ byte[] consumerPublicKeyExchange = this.Algorithm.CreateKeyExchange();
+ this.DiffieHellmanConsumerPublic = DiffieHellmanUtilities.EnsurePositive(consumerPublicKeyExchange);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateDiffieHellmanResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateDiffieHellmanResponse.cs
new file mode 100644
index 0000000..3860565
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateDiffieHellmanResponse.cs
@@ -0,0 +1,45 @@
+//-----------------------------------------------------------------------
+// <copyright file="AssociateDiffieHellmanResponse.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Messages {
+ using System;
+ using System.Diagnostics.Contracts;
+ using System.Security.Cryptography;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Reflection;
+ using Org.Mentalis.Security.Cryptography;
+
+ /// <summary>
+ /// The successful Diffie-Hellman association response message.
+ /// </summary>
+ /// <remarks>
+ /// Association response messages are described in OpenID 2.0 section 8.2. This type covers section 8.2.3.
+ /// </remarks>
+ internal abstract class AssociateDiffieHellmanResponse : AssociateSuccessfulResponse {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AssociateDiffieHellmanResponse"/> class.
+ /// </summary>
+ /// <param name="responseVersion">The OpenID version of the response message.</param>
+ /// <param name="originatingRequest">The originating request.</param>
+ internal AssociateDiffieHellmanResponse(Version responseVersion, AssociateDiffieHellmanRequest originatingRequest)
+ : base(responseVersion, originatingRequest) {
+ }
+
+ /// <summary>
+ /// Gets or sets the Provider's Diffie-Hellman public key.
+ /// </summary>
+ /// <value>btwoc(g ^ xb mod p)</value>
+ [MessagePart("dh_server_public", IsRequired = true, AllowEmpty = false)]
+ internal byte[] DiffieHellmanServerPublic { get; set; }
+
+ /// <summary>
+ /// Gets or sets the MAC key (shared secret), encrypted with the secret Diffie-Hellman value.
+ /// </summary>
+ /// <value>H(btwoc(g ^ (xa * xb) mod p)) XOR MAC key. H is either "SHA1" or "SHA256" depending on the session type. </value>
+ [MessagePart("enc_mac_key", IsRequired = true, AllowEmpty = false)]
+ internal byte[] EncodedMacKey { get; set; }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateRequest.cs
new file mode 100644
index 0000000..1e716e2
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateRequest.cs
@@ -0,0 +1,67 @@
+//-----------------------------------------------------------------------
+// <copyright file="AssociateRequest.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// An OpenID direct request from Relying Party to Provider to initiate an association.
+ /// </summary>
+ [DebuggerDisplay("OpenID {Version} {Mode} {AssociationType} {SessionType}")]
+ internal abstract class AssociateRequest : RequestBase {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AssociateRequest"/> class.
+ /// </summary>
+ /// <param name="version">The OpenID version this message must comply with.</param>
+ /// <param name="providerEndpoint">The OpenID Provider endpoint.</param>
+ protected AssociateRequest(Version version, Uri providerEndpoint)
+ : base(version, providerEndpoint, GetProtocolConstant(version, p => p.Args.Mode.associate), MessageTransport.Direct) {
+ }
+
+ /// <summary>
+ /// Gets or sets the preferred association type. The association type defines the algorithm to be used to sign subsequent messages.
+ /// </summary>
+ /// <value>Value: A valid association type from Section 8.3.</value>
+ [MessagePart("openid.assoc_type", IsRequired = true, AllowEmpty = false)]
+ internal string AssociationType { get; set; }
+
+ /// <summary>
+ /// Gets or sets the preferred association session type. This defines the method used to encrypt the association's MAC key in transit.
+ /// </summary>
+ /// <value>Value: A valid association session type from Section 8.4 (Association Session Types). </value>
+ /// <remarks>Note: Unless using transport layer encryption, "no-encryption" MUST NOT be used. </remarks>
+ [MessagePart("openid.session_type", IsRequired = false, AllowEmpty = true)]
+ [MessagePart("openid.session_type", IsRequired = true, AllowEmpty = false, MinVersion = "2.0")]
+ internal string SessionType { get; set; }
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ /// <remarks>
+ /// <para>Some messages have required fields, or combinations of fields that must relate to each other
+ /// in specialized ways. After deserializing a message, this method checks the state of the
+ /// message to see if it conforms to the protocol.</para>
+ /// <para>Note that this property should <i>not</i> check signatures or perform any state checks
+ /// outside this scope of this particular message.</para>
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception>
+ public override void EnsureValidMessage() {
+ base.EnsureValidMessage();
+
+ ErrorUtilities.VerifyProtocol(
+ !string.Equals(this.SessionType, Protocol.Args.SessionType.NoEncryption, StringComparison.Ordinal) || this.Recipient.IsTransportSecure(),
+ OpenIdStrings.NoEncryptionSessionRequiresHttps,
+ this);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateSuccessfulResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateSuccessfulResponse.cs
new file mode 100644
index 0000000..6605530
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateSuccessfulResponse.cs
@@ -0,0 +1,92 @@
+//-----------------------------------------------------------------------
+// <copyright file="AssociateSuccessfulResponse.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// The base class that all successful association response messages derive from.
+ /// </summary>
+ /// <remarks>
+ /// Association response messages are described in OpenID 2.0 section 8.2. This type covers section 8.2.1.
+ /// </remarks>
+ [DebuggerDisplay("OpenID {Version} associate response {AssociationHandle} {AssociationType} {SessionType}")]
+ [ContractClass(typeof(AssociateSuccessfulResponseContract))]
+ internal abstract class AssociateSuccessfulResponse : DirectResponseBase {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AssociateSuccessfulResponse"/> class.
+ /// </summary>
+ /// <param name="responseVersion">The OpenID version of the response message.</param>
+ /// <param name="originatingRequest">The originating request.</param>
+ internal AssociateSuccessfulResponse(Version responseVersion, AssociateRequest originatingRequest)
+ : base(responseVersion, originatingRequest) {
+ }
+
+ /// <summary>
+ /// Gets or sets the association handle is used as a key to refer to this association in subsequent messages.
+ /// </summary>
+ /// <value>A string 255 characters or less in length. It MUST consist only of ASCII characters in the range 33-126 inclusive (printable non-whitespace characters). </value>
+ [MessagePart("assoc_handle", IsRequired = true, AllowEmpty = false)]
+ internal string AssociationHandle { get; set; }
+
+ /// <summary>
+ /// Gets or sets the preferred association type. The association type defines the algorithm to be used to sign subsequent messages.
+ /// </summary>
+ /// <value>Value: A valid association type from Section 8.3.</value>
+ [MessagePart("assoc_type", IsRequired = true, AllowEmpty = false)]
+ internal string AssociationType { get; set; }
+
+ /// <summary>
+ /// Gets or sets the value of the "openid.session_type" parameter from the request.
+ /// If the OP is unwilling or unable to support this association type, it MUST return an
+ /// unsuccessful response (Unsuccessful Response Parameters).
+ /// </summary>
+ /// <value>Value: A valid association session type from Section 8.4 (Association Session Types). </value>
+ /// <remarks>Note: Unless using transport layer encryption, "no-encryption" MUST NOT be used. </remarks>
+ [MessagePart("session_type", IsRequired = false, AllowEmpty = true)]
+ [MessagePart("session_type", IsRequired = true, AllowEmpty = false, MinVersion = "2.0")]
+ internal string SessionType { get; set; }
+
+ /// <summary>
+ /// Gets or sets the lifetime, in seconds, of this association. The Relying Party MUST NOT use the association after this time has passed.
+ /// </summary>
+ /// <value>An integer, represented in base 10 ASCII. </value>
+ [MessagePart("expires_in", IsRequired = true)]
+ internal long ExpiresIn { get; set; }
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ /// <remarks>
+ /// <para>Some messages have required fields, or combinations of fields that must relate to each other
+ /// in specialized ways. After deserializing a message, this method checks the state of the
+ /// message to see if it conforms to the protocol.</para>
+ /// <para>Note that this property should <i>not</i> check signatures or perform any state checks
+ /// outside this scope of this particular message.</para>
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception>
+ public override void EnsureValidMessage() {
+ base.EnsureValidMessage();
+
+ if (this.Version.Major < 2) {
+ ErrorUtilities.VerifyProtocol(
+ string.IsNullOrEmpty(this.SessionType) || string.Equals(this.SessionType, this.Protocol.Args.SessionType.DH_SHA1, StringComparison.Ordinal),
+ MessagingStrings.UnexpectedMessagePartValueForConstant,
+ GetType().Name,
+ Protocol.openid.session_type,
+ this.Protocol.Args.SessionType.DH_SHA1,
+ this.SessionType);
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateSuccessfulResponseContract.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateSuccessfulResponseContract.cs
new file mode 100644
index 0000000..39a79a4
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateSuccessfulResponseContract.cs
@@ -0,0 +1,17 @@
+// <auto-generated />
+
+namespace DotNetOpenAuth.OpenId.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ [ContractClassFor(typeof(AssociateSuccessfulResponse))]
+ internal abstract class AssociateSuccessfulResponseContract : AssociateSuccessfulResponse {
+ protected AssociateSuccessfulResponseContract() : base(null, null) {
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateUnencryptedRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateUnencryptedRequest.cs
new file mode 100644
index 0000000..b6fe5d9
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateUnencryptedRequest.cs
@@ -0,0 +1,50 @@
+//-----------------------------------------------------------------------
+// <copyright file="AssociateUnencryptedRequest.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Messages {
+ using System;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Reflection;
+
+ /// <summary>
+ /// Represents an association request that is sent using HTTPS and otherwise communicates the shared secret in plain text.
+ /// </summary>
+ internal class AssociateUnencryptedRequest : AssociateRequest {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AssociateUnencryptedRequest"/> class.
+ /// </summary>
+ /// <param name="version">The OpenID version this message must comply with.</param>
+ /// <param name="providerEndpoint">The OpenID Provider endpoint.</param>
+ internal AssociateUnencryptedRequest(Version version, Uri providerEndpoint)
+ : base(version, providerEndpoint) {
+ SessionType = Protocol.Args.SessionType.NoEncryption;
+ }
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ /// <remarks>
+ /// <para>Some messages have required fields, or combinations of fields that must relate to each other
+ /// in specialized ways. After deserializing a message, this method checks the state of the
+ /// message to see if it conforms to the protocol.</para>
+ /// <para>Note that this property should <i>not</i> check signatures or perform any state checks
+ /// outside this scope of this particular message.</para>
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception>
+ public override void EnsureValidMessage() {
+ base.EnsureValidMessage();
+
+ ErrorUtilities.VerifyProtocol(
+ string.Equals(this.SessionType, Protocol.Args.SessionType.NoEncryption, StringComparison.Ordinal),
+ MessagingStrings.UnexpectedMessagePartValueForConstant,
+ GetType().Name,
+ Protocol.openid.session_type,
+ Protocol.Args.SessionType.NoEncryption,
+ SessionType);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateUnencryptedResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateUnencryptedResponse.cs
new file mode 100644
index 0000000..1fc9464
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateUnencryptedResponse.cs
@@ -0,0 +1,35 @@
+//-----------------------------------------------------------------------
+// <copyright file="AssociateUnencryptedResponse.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Messages {
+ using System;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Reflection;
+
+ /// <summary>
+ /// The successful unencrypted association response message.
+ /// </summary>
+ /// <remarks>
+ /// Association response messages are described in OpenID 2.0 section 8.2. This type covers section 8.2.2.
+ /// </remarks>
+ internal class AssociateUnencryptedResponse : AssociateSuccessfulResponse {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AssociateUnencryptedResponse"/> class.
+ /// </summary>
+ /// <param name="responseVersion">The OpenID version of the response message.</param>
+ /// <param name="originatingRequest">The originating request.</param>
+ internal AssociateUnencryptedResponse(Version responseVersion, AssociateUnencryptedRequest originatingRequest)
+ : base(responseVersion, originatingRequest) {
+ SessionType = Protocol.Args.SessionType.NoEncryption;
+ }
+
+ /// <summary>
+ /// Gets or sets the MAC key (shared secret) for this association, Base 64 (Josefsson, S., “The Base16, Base32, and Base64 Data Encodings,” .) [RFC3548] encoded.
+ /// </summary>
+ [MessagePart("mac_key", IsRequired = true, AllowEmpty = false)]
+ internal byte[] MacKey { get; set; }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateUnsuccessfulResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateUnsuccessfulResponse.cs
new file mode 100644
index 0000000..88d7781
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/AssociateUnsuccessfulResponse.cs
@@ -0,0 +1,54 @@
+//-----------------------------------------------------------------------
+// <copyright file="AssociateUnsuccessfulResponse.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Messages {
+ using System;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Globalization;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// The Provider's response to a Relying Party that requested an association that the Provider does not support.
+ /// </summary>
+ /// <remarks>
+ /// This message type described in OpenID 2.0 section 8.2.4.
+ /// </remarks>
+ [DebuggerDisplay("OpenID {Version} associate (failed) response")]
+ internal class AssociateUnsuccessfulResponse : DirectErrorResponse {
+ /// <summary>
+ /// A hard-coded string indicating an error occurred.
+ /// </summary>
+ /// <value>"unsupported-type" </value>
+ [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Read by reflection")]
+ [MessagePart("error_code", IsRequired = true, AllowEmpty = false)]
+#pragma warning disable 0414 // read by reflection
+ private readonly string Error = "unsupported-type";
+#pragma warning restore 0414
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AssociateUnsuccessfulResponse"/> class.
+ /// </summary>
+ /// <param name="responseVersion">The OpenID version of the response message.</param>
+ /// <param name="originatingRequest">The originating request.</param>
+ internal AssociateUnsuccessfulResponse(Version responseVersion, AssociateRequest originatingRequest)
+ : base(responseVersion, originatingRequest) {
+ this.ErrorMessage = string.Format(CultureInfo.CurrentCulture, OpenIdStrings.AssociationOrSessionTypeUnrecognizedOrNotSupported, originatingRequest.AssociationType, originatingRequest.SessionType);
+ }
+
+ /// <summary>
+ /// Gets or sets an association type supported by the OP from Section 8.3 (Association Types).
+ /// </summary>
+ [MessagePart("assoc_type", IsRequired = false, AllowEmpty = false)]
+ internal string AssociationType { get; set; }
+
+ /// <summary>
+ /// Gets or sets a valid association session type from Section 8.4 (Association Session Types) that the OP supports.
+ /// </summary>
+ [MessagePart("session_type", IsRequired = false, AllowEmpty = false)]
+ internal string SessionType { get; set; }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/CheckAuthenticationRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/CheckAuthenticationRequest.cs
new file mode 100644
index 0000000..0c46411
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/CheckAuthenticationRequest.cs
@@ -0,0 +1,79 @@
+//-----------------------------------------------------------------------
+// <copyright file="CheckAuthenticationRequest.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Reflection;
+ using DotNetOpenAuth.OpenId.ChannelElements;
+
+ /// <summary>
+ /// A message a Relying Party sends to a Provider to confirm the validity
+ /// of a positive assertion that was signed by a Provider-only secret.
+ /// </summary>
+ /// <remarks>
+ /// The significant payload of this message depends entirely upon the
+ /// assertion message, and therefore is all in the
+ /// <see cref="DotNetOpenAuth.Messaging.IMessage.ExtraData"/> property bag.
+ /// </remarks>
+ internal class CheckAuthenticationRequest : RequestBase {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CheckAuthenticationRequest"/> class.
+ /// </summary>
+ /// <param name="version">The OpenID version this message must comply with.</param>
+ /// <param name="providerEndpoint">The OpenID Provider endpoint.</param>
+ internal CheckAuthenticationRequest(Version version, Uri providerEndpoint)
+ : base(version, providerEndpoint, GetProtocolConstant(version, p => p.Args.Mode.check_authentication), MessageTransport.Direct) {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CheckAuthenticationRequest"/> class
+ /// based on the contents of some signed message whose signature must be verified.
+ /// </summary>
+ /// <param name="message">The message whose signature should be verified.</param>
+ /// <param name="channel">The channel. This is used only within the constructor and is not stored in a field.</param>
+ internal CheckAuthenticationRequest(IndirectSignedResponse message, Channel channel)
+ : base(message.Version, message.ProviderEndpoint, GetProtocolConstant(message.Version, p => p.Args.Mode.check_authentication), MessageTransport.Direct) {
+ Requires.NotNull(channel, "channel");
+
+ // Copy all message parts from the id_res message into this one,
+ // except for the openid.mode parameter.
+ MessageDictionary checkPayload = channel.MessageDescriptions.GetAccessor(message, true);
+ MessageDictionary thisPayload = channel.MessageDescriptions.GetAccessor(this);
+ foreach (var pair in checkPayload) {
+ if (!string.Equals(pair.Key, this.Protocol.openid.mode)) {
+ thisPayload[pair.Key] = pair.Value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the signature being verified by this request
+ /// is in fact valid.
+ /// </summary>
+ /// <value><c>true</c> if the signature is valid; otherwise, <c>false</c>.</value>
+ /// <remarks>
+ /// This property is automatically set as the message is received by the channel's
+ /// signing binding element.
+ /// </remarks>
+ internal bool IsValid { get; set; }
+
+ /// <summary>
+ /// Gets or sets the ReturnTo that existed in the original signed message.
+ /// </summary>
+ /// <remarks>
+ /// This exists strictly for convenience in recreating the <see cref="IndirectSignedResponse"/>
+ /// message.
+ /// </remarks>
+ [MessagePart("openid.return_to", IsRequired = true, AllowEmpty = false, Encoder = typeof(OriginalStringUriEncoder))]
+ [MessagePart("openid.return_to", IsRequired = false, AllowEmpty = false, MinVersion = "2.0", Encoder = typeof(OriginalStringUriEncoder))]
+ internal Uri ReturnTo { get; set; }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/CheckAuthenticationResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/CheckAuthenticationResponse.cs
new file mode 100644
index 0000000..9c31ea5
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/CheckAuthenticationResponse.cs
@@ -0,0 +1,55 @@
+//-----------------------------------------------------------------------
+// <copyright file="CheckAuthenticationResponse.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.ChannelElements;
+
+ /// <summary>
+ /// The message sent from the Provider to the Relying Party to confirm/deny
+ /// the validity of an assertion that was signed by a private Provider secret.
+ /// </summary>
+ internal class CheckAuthenticationResponse : DirectResponseBase {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CheckAuthenticationResponse"/> class
+ /// for use by the Relying Party.
+ /// </summary>
+ /// <param name="responseVersion">The OpenID version of the response message.</param>
+ /// <param name="request">The request that this message is responding to.</param>
+ internal CheckAuthenticationResponse(Version responseVersion, CheckAuthenticationRequest request)
+ : base(responseVersion, request) {
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the signature of the verification request is valid.
+ /// </summary>
+ [MessagePart("is_valid", IsRequired = true)]
+ internal bool IsValid { get; set; }
+
+ /// <summary>
+ /// Gets or sets the handle the relying party should invalidate if <see cref="IsValid"/> is true.
+ /// </summary>
+ /// <value>The "invalidate_handle" value sent in the verification request, if the OP confirms it is invalid.</value>
+ /// <remarks>
+ /// <para>If present in a verification response with "is_valid" set to "true",
+ /// the Relying Party SHOULD remove the corresponding association from
+ /// its store and SHOULD NOT send further authentication requests with
+ /// this handle.</para>
+ /// <para>This two-step process for invalidating associations is necessary
+ /// to prevent an attacker from invalidating an association at will by
+ /// adding "invalidate_handle" parameters to an authentication response.</para>
+ /// <para>For OpenID 1.1, we allow this to be present but empty to put up with poor implementations such as Blogger.</para>
+ /// </remarks>
+ [MessagePart("invalidate_handle", IsRequired = false, AllowEmpty = true, MaxVersion = "1.1")]
+ [MessagePart("invalidate_handle", IsRequired = false, AllowEmpty = false, MinVersion = "2.0")]
+ internal string InvalidateHandle { get; set; }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/CheckIdRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/CheckIdRequest.cs
new file mode 100644
index 0000000..1d6d43e
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/CheckIdRequest.cs
@@ -0,0 +1,93 @@
+//-----------------------------------------------------------------------
+// <copyright file="CheckIdRequest.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// An authentication request from a Relying Party to a Provider.
+ /// </summary>
+ /// <remarks>
+ /// This message type satisfies OpenID 2.0 section 9.1.
+ /// </remarks>
+ [DebuggerDisplay("OpenID {Version} {Mode} {ClaimedIdentifier}")]
+ [Serializable]
+ internal class CheckIdRequest : SignedResponseRequest {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CheckIdRequest"/> class.
+ /// </summary>
+ /// <param name="version">The OpenID version to use.</param>
+ /// <param name="providerEndpoint">The Provider endpoint that receives this message.</param>
+ /// <param name="mode">
+ /// <see cref="AuthenticationRequestMode.Immediate"/> for asynchronous javascript clients;
+ /// <see cref="AuthenticationRequestMode.Setup"/> to allow the Provider to interact with the user in order to complete authentication.
+ /// </param>
+ internal CheckIdRequest(Version version, Uri providerEndpoint, AuthenticationRequestMode mode) :
+ base(version, providerEndpoint, mode) {
+ }
+
+ /// <summary>
+ /// Gets or sets the Claimed Identifier.
+ /// </summary>
+ /// <remarks>
+ /// <para>"openid.claimed_id" and "openid.identity" SHALL be either both present or both absent.
+ /// If neither value is present, the assertion is not about an identifier,
+ /// and will contain other information in its payload, using extensions (Extensions). </para>
+ /// <para>It is RECOMMENDED that OPs accept XRI identifiers with or without the "xri://" prefix, as specified in the Normalization (Normalization) section. </para>
+ /// </remarks>
+ [MessagePart("openid.claimed_id", IsRequired = true, AllowEmpty = false, MinVersion = "2.0")]
+ internal Identifier ClaimedIdentifier { get; set; }
+
+ /// <summary>
+ /// Gets or sets the OP Local Identifier.
+ /// </summary>
+ /// <value>The OP-Local Identifier. </value>
+ /// <remarks>
+ /// <para>If a different OP-Local Identifier is not specified, the claimed
+ /// identifier MUST be used as the value for openid.identity.</para>
+ /// <para>Note: If this is set to the special value
+ /// "http://specs.openid.net/auth/2.0/identifier_select" then the OP SHOULD
+ /// choose an Identifier that belongs to the end user. This parameter MAY
+ /// be omitted if the request is not about an identifier (for instance if
+ /// an extension is in use that makes the request meaningful without it;
+ /// see openid.claimed_id above). </para>
+ /// </remarks>
+ [MessagePart("openid.identity", IsRequired = true, AllowEmpty = false)]
+ internal Identifier LocalIdentifier { get; set; }
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ /// <remarks>
+ /// <para>Some messages have required fields, or combinations of fields that must relate to each other
+ /// in specialized ways. After deserializing a message, this method checks the state of the
+ /// message to see if it conforms to the protocol.</para>
+ /// <para>Note that this property should <i>not</i> check signatures or perform any state checks
+ /// outside this scope of this particular message.</para>
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception>
+ public override void EnsureValidMessage() {
+ base.EnsureValidMessage();
+
+ if (this.Protocol.ClaimedIdentifierForOPIdentifier != null) {
+ // Ensure that the claimed_id and identity parameters are either both the
+ // special identifier_select value or both NOT that value.
+ ErrorUtilities.VerifyProtocol(
+ (this.LocalIdentifier == this.Protocol.ClaimedIdentifierForOPIdentifier) == (this.ClaimedIdentifier == this.Protocol.ClaimedIdentifierForOPIdentifier),
+ OpenIdStrings.MatchingArgumentsExpected,
+ Protocol.openid.claimed_id,
+ Protocol.openid.identity,
+ Protocol.ClaimedIdentifierForOPIdentifier);
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/DirectErrorResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/DirectErrorResponse.cs
new file mode 100644
index 0000000..ec3691a
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/DirectErrorResponse.cs
@@ -0,0 +1,68 @@
+//-----------------------------------------------------------------------
+// <copyright file="DirectErrorResponse.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Messages {
+ using System;
+ using System.Net;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// A message sent from a Provider to a Relying Party in response to a direct message request that resulted in an error.
+ /// </summary>
+ /// <remarks>
+ /// This message must be sent with an HTTP status code of 400.
+ /// This class satisfies OpenID 2.0 section 5.1.2.2.
+ /// </remarks>
+ internal class DirectErrorResponse : DirectResponseBase, IErrorMessage, IHttpDirectResponse {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="DirectErrorResponse"/> class.
+ /// </summary>
+ /// <param name="responseVersion">The OpenID version of the response message.</param>
+ /// <param name="originatingRequest">The originating request.</param>
+ internal DirectErrorResponse(Version responseVersion, IDirectedProtocolMessage originatingRequest)
+ : base(responseVersion, originatingRequest) {
+ }
+
+ #region IHttpDirectResponse Members
+
+ /// <summary>
+ /// Gets the HTTP status code that the direct respones should be sent with.
+ /// </summary>
+ /// <value><see cref="HttpStatusCode.BadRequest"/></value>
+ HttpStatusCode IHttpDirectResponse.HttpStatusCode {
+ get { return HttpStatusCode.BadRequest; }
+ }
+
+ /// <summary>
+ /// Gets the HTTP headers to add to the response.
+ /// </summary>
+ /// <value>May be an empty collection, but must not be <c>null</c>.</value>
+ WebHeaderCollection IHttpDirectResponse.Headers {
+ get { return new WebHeaderCollection(); }
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Gets or sets a human-readable message indicating why the request failed.
+ /// </summary>
+ [MessagePart("error", IsRequired = true, AllowEmpty = true)]
+ public string ErrorMessage { get; set; }
+
+ /// <summary>
+ /// Gets or sets the contact address for the administrator of the server.
+ /// </summary>
+ /// <value>The contact address may take any form, as it is intended to be displayed to a person. </value>
+ [MessagePart("contact", IsRequired = false, AllowEmpty = true)]
+ public string Contact { get; set; }
+
+ /// <summary>
+ /// Gets or sets a reference token, such as a support ticket number or a URL to a news blog, etc.
+ /// </summary>
+ [MessagePart("reference", IsRequired = false, AllowEmpty = true)]
+ public string Reference { get; set; }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/DirectResponseBase.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/DirectResponseBase.cs
new file mode 100644
index 0000000..787c920
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/DirectResponseBase.cs
@@ -0,0 +1,157 @@
+//-----------------------------------------------------------------------
+// <copyright file="DirectResponseBase.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// A common base class for OpenID direct message responses.
+ /// </summary>
+ [DebuggerDisplay("OpenID {Version} response")]
+ internal class DirectResponseBase : IDirectResponseProtocolMessage {
+ /// <summary>
+ /// The openid.ns parameter in the message.
+ /// </summary>
+ /// <value>"http://specs.openid.net/auth/2.0" </value>
+ /// <remarks>
+ /// OpenID 2.0 Section 5.1.2:
+ /// This particular value MUST be present for the response to be a valid OpenID 2.0 response.
+ /// Future versions of the specification may define different values in order to allow message
+ /// recipients to properly interpret the request.
+ /// </remarks>
+ [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Read by reflection.")]
+ [MessagePart("ns", IsRequired = true, AllowEmpty = false, MinVersion = "2.0")]
+#pragma warning disable 0414 // read by reflection
+ private readonly string OpenIdNamespace = Protocol.OpenId2Namespace;
+#pragma warning restore 0414
+
+ /// <summary>
+ /// Backing store for the <see cref="OriginatingRequest"/> properties.
+ /// </summary>
+ private IDirectedProtocolMessage originatingRequest;
+
+ /// <summary>
+ /// Backing store for the <see cref="Incoming"/> properties.
+ /// </summary>
+ private bool incoming;
+
+ /// <summary>
+ /// The dictionary of parameters that are not part of the OpenID specification.
+ /// </summary>
+ private Dictionary<string, string> extraData = new Dictionary<string, string>();
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="DirectResponseBase"/> class.
+ /// </summary>
+ /// <param name="responseVersion">The OpenID version of the response message.</param>
+ /// <param name="originatingRequest">The originating request. May be null in case the request is unrecognizable and this is an error response.</param>
+ protected DirectResponseBase(Version responseVersion, IDirectedProtocolMessage originatingRequest) {
+ Requires.NotNull(responseVersion, "responseVersion");
+
+ this.Version = responseVersion;
+ this.originatingRequest = originatingRequest;
+ }
+
+ #region IProtocolMessage Properties
+
+ /// <summary>
+ /// Gets the version of the protocol this message is prepared to implement.
+ /// </summary>
+ /// <value>Version 2.0</value>
+ public Version Version { get; private set; }
+
+ /// <summary>
+ /// Gets the level of protection this message requires.
+ /// </summary>
+ /// <value><see cref="MessageProtections.None"/></value>
+ public MessageProtections RequiredProtection {
+ get { return MessageProtections.None; }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this is a direct or indirect message.
+ /// </summary>
+ /// <value><see cref="MessageTransport.Direct"/></value>
+ public MessageTransport Transport {
+ get { return MessageTransport.Direct; }
+ }
+
+ /// <summary>
+ /// Gets the extra, non-OAuth parameters included in the message.
+ /// </summary>
+ public IDictionary<string, string> ExtraData {
+ get { return this.extraData; }
+ }
+
+ #endregion
+
+ #region IDirectResponseProtocolMessage Members
+
+ /// <summary>
+ /// Gets the originating request message that caused this response to be formed.
+ /// </summary>
+ /// <remarks>
+ /// This property may be null if the request message was undecipherable.
+ /// </remarks>
+ IDirectedProtocolMessage IDirectResponseProtocolMessage.OriginatingRequest {
+ get { return this.originatingRequest; }
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Gets a value indicating whether this message was deserialized as an incoming message.
+ /// </summary>
+ protected internal bool Incoming {
+ get { return this.incoming; }
+ }
+
+ /// <summary>
+ /// Gets the protocol used by this message.
+ /// </summary>
+ protected Protocol Protocol {
+ get { return Protocol.Lookup(this.Version); }
+ }
+
+ /// <summary>
+ /// Gets the originating request message that caused this response to be formed.
+ /// </summary>
+ protected IDirectedProtocolMessage OriginatingRequest {
+ get { return this.originatingRequest; }
+ }
+
+ #region IProtocolMessage methods
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ /// <remarks>
+ /// <para>Some messages have required fields, or combinations of fields that must relate to each other
+ /// in specialized ways. After deserializing a message, this method checks the state of the
+ /// message to see if it conforms to the protocol.</para>
+ /// <para>Note that this property should <i>not</i> check signatures or perform any state checks
+ /// outside this scope of this particular message.</para>
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception>
+ public virtual void EnsureValidMessage() {
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Sets a flag indicating that this message is received (as opposed to sent).
+ /// </summary>
+ internal void SetAsIncoming() {
+ this.incoming = true;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/IErrorMessage.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/IErrorMessage.cs
new file mode 100644
index 0000000..a730957
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/IErrorMessage.cs
@@ -0,0 +1,32 @@
+//-----------------------------------------------------------------------
+// <copyright file="IErrorMessage.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Messages {
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// Members found on error response messages sent from a Provider
+ /// to a Relying Party in response to direct and indirect message
+ /// requests that result in an error.
+ /// </summary>
+ internal interface IErrorMessage : IProtocolMessage {
+ /// <summary>
+ /// Gets or sets a human-readable message indicating why the request failed.
+ /// </summary>
+ string ErrorMessage { get; set; }
+
+ /// <summary>
+ /// Gets or sets the contact address for the administrator of the server.
+ /// </summary>
+ /// <value>The contact address may take any form, as it is intended to be displayed to a person. </value>
+ string Contact { get; set; }
+
+ /// <summary>
+ /// Gets or sets a reference token, such as a support ticket number or a URL to a news blog, etc.
+ /// </summary>
+ string Reference { get; set; }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/IOpenIdMessageExtension.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/IOpenIdMessageExtension.cs
new file mode 100644
index 0000000..dabb752
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/IOpenIdMessageExtension.cs
@@ -0,0 +1,160 @@
+//-----------------------------------------------------------------------
+// <copyright file="IOpenIdMessageExtension.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// The contract any OpenID extension for DotNetOpenAuth must implement.
+ /// </summary>
+ /// <remarks>
+ /// Classes that implement this interface should be marked as
+ /// [<see cref="SerializableAttribute"/>] to allow serializing state servers
+ /// to cache messages, particularly responses.
+ /// </remarks>
+ [ContractClass(typeof(IOpenIdMessageExtensionContract))]
+ public interface IOpenIdMessageExtension : IExtensionMessage {
+ /// <summary>
+ /// Gets the TypeURI the extension uses in the OpenID protocol and in XRDS advertisements.
+ /// </summary>
+ string TypeUri { get; }
+
+ /// <summary>
+ /// Gets the additional TypeURIs that are supported by this extension, in preferred order.
+ /// May be empty if none other than <see cref="TypeUri"/> is supported, but
+ /// should not be null.
+ /// </summary>
+ /// <remarks>
+ /// Useful for reading in messages with an older version of an extension.
+ /// The value in the <see cref="TypeUri"/> property is always checked before
+ /// trying this list.
+ /// If you do support multiple versions of an extension using this method,
+ /// consider adding a CreateResponse method to your request extension class
+ /// so that the response can have the context it needs to remain compatible
+ /// given the version of the extension in the request message.
+ /// The <see cref="Extensions.SimpleRegistration.ClaimsRequest.CreateResponse"/> for an example.
+ /// </remarks>
+ IEnumerable<string> AdditionalSupportedTypeUris { get; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this extension was
+ /// signed by the sender.
+ /// </summary>
+ /// <value>
+ /// <c>true</c> if this instance is signed by the sender; otherwise, <c>false</c>.
+ /// </value>
+ bool IsSignedByRemoteParty { get; set; }
+ }
+
+ /// <summary>
+ /// Code contract class for the IOpenIdMessageExtension interface.
+ /// </summary>
+ [ContractClassFor(typeof(IOpenIdMessageExtension))]
+ internal abstract class IOpenIdMessageExtensionContract : IOpenIdMessageExtension {
+ /// <summary>
+ /// Prevents a default instance of the <see cref="IOpenIdMessageExtensionContract"/> class from being created.
+ /// </summary>
+ private IOpenIdMessageExtensionContract() {
+ }
+
+ #region IOpenIdMessageExtension Members
+
+ /// <summary>
+ /// Gets the TypeURI the extension uses in the OpenID protocol and in XRDS advertisements.
+ /// </summary>
+ string IOpenIdMessageExtension.TypeUri {
+ get {
+ Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>()));
+ throw new NotImplementedException();
+ }
+ }
+
+ /// <summary>
+ /// Gets the additional TypeURIs that are supported by this extension, in preferred order.
+ /// May be empty if none other than <see cref="IOpenIdMessageExtension.TypeUri"/> is supported, but
+ /// should not be null.
+ /// </summary>
+ /// <remarks>
+ /// Useful for reading in messages with an older version of an extension.
+ /// The value in the <see cref="IOpenIdMessageExtension.TypeUri"/> property is always checked before
+ /// trying this list.
+ /// If you do support multiple versions of an extension using this method,
+ /// consider adding a CreateResponse method to your request extension class
+ /// so that the response can have the context it needs to remain compatible
+ /// given the version of the extension in the request message.
+ /// The <see cref="Extensions.SimpleRegistration.ClaimsRequest.CreateResponse"/> for an example.
+ /// </remarks>
+ IEnumerable<string> IOpenIdMessageExtension.AdditionalSupportedTypeUris {
+ get {
+ Contract.Ensures(Contract.Result<IEnumerable<string>>() != null);
+ throw new NotImplementedException();
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this extension was
+ /// signed by the sender.
+ /// </summary>
+ /// <value>
+ /// <c>true</c> if this instance is signed by the sender; otherwise, <c>false</c>.
+ /// </value>
+ bool IOpenIdMessageExtension.IsSignedByRemoteParty {
+ get { throw new NotImplementedException(); }
+ set { throw new NotImplementedException(); }
+ }
+
+ #endregion
+
+ #region IMessage Members
+
+ /// <summary>
+ /// Gets the version of the protocol or extension this message is prepared to implement.
+ /// </summary>
+ /// <remarks>
+ /// Implementations of this interface should ensure that this property never returns null.
+ /// </remarks>
+ Version IMessage.Version {
+ get {
+ throw new NotImplementedException();
+ }
+ }
+
+ /// <summary>
+ /// Gets the extra, non-standard Protocol parameters included in the message.
+ /// </summary>
+ /// <remarks>
+ /// Implementations of this interface should ensure that this property never returns null.
+ /// </remarks>
+ IDictionary<string, string> IMessage.ExtraData {
+ get {
+ throw new NotImplementedException();
+ }
+ }
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ /// <remarks>
+ /// <para>Some messages have required fields, or combinations of fields that must relate to each other
+ /// in specialized ways. After deserializing a message, this method checks the state of the
+ /// message to see if it conforms to the protocol.</para>
+ /// <para>Note that this property should <i>not</i> check signatures or perform any state checks
+ /// outside this scope of this particular message.</para>
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception>
+ void IMessage.EnsureValidMessage() {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/IndirectErrorResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/IndirectErrorResponse.cs
new file mode 100644
index 0000000..b614485
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/IndirectErrorResponse.cs
@@ -0,0 +1,57 @@
+//-----------------------------------------------------------------------
+// <copyright file="IndirectErrorResponse.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// A message sent from a Provider to a Relying Party in response to an indirect message request that resulted in an error.
+ /// </summary>
+ /// <remarks>
+ /// This class satisfies OpenID 2.0 section 5.2.3.
+ /// </remarks>
+ internal class IndirectErrorResponse : IndirectResponseBase, IErrorMessage {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="IndirectErrorResponse"/> class.
+ /// </summary>
+ /// <param name="request">The request that resulted in this error on the Provider.</param>
+ internal IndirectErrorResponse(SignedResponseRequest request)
+ : base(request, Protocol.Lookup(GetVersion(request)).openidnp.error) {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="IndirectErrorResponse"/> class.
+ /// </summary>
+ /// <param name="version">The OpenID version this message should comply with.</param>
+ /// <param name="recipient">The recipient of this message.</param>
+ internal IndirectErrorResponse(Version version, Uri recipient)
+ : base(version, recipient, Protocol.Lookup(version).openidnp.error) {
+ }
+
+ /// <summary>
+ /// Gets or sets a human-readable message indicating why the request failed.
+ /// </summary>
+ [MessagePart("openid.error", IsRequired = true, AllowEmpty = true)]
+ public string ErrorMessage { get; set; }
+
+ /// <summary>
+ /// Gets or sets the contact address for the administrator of the server.
+ /// </summary>
+ /// <value>The contact address may take any form, as it is intended to be displayed to a person. </value>
+ [MessagePart("openid.contact", IsRequired = false, AllowEmpty = true)]
+ public string Contact { get; set; }
+
+ /// <summary>
+ /// Gets or sets a reference token, such as a support ticket number or a URL to a news blog, etc.
+ /// </summary>
+ [MessagePart("openid.reference", IsRequired = false, AllowEmpty = true)]
+ public string Reference { get; set; }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/IndirectResponseBase.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/IndirectResponseBase.cs
new file mode 100644
index 0000000..ae0217d
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/IndirectResponseBase.cs
@@ -0,0 +1,113 @@
+//-----------------------------------------------------------------------
+// <copyright file="IndirectResponseBase.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// A common base class from which indirect response messages should derive.
+ /// </summary>
+ [Serializable]
+ internal class IndirectResponseBase : RequestBase, IProtocolMessageWithExtensions {
+ /// <summary>
+ /// Backing store for the <see cref="Extensions"/> property.
+ /// </summary>
+ private IList<IExtensionMessage> extensions = new List<IExtensionMessage>();
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="IndirectResponseBase"/> class.
+ /// </summary>
+ /// <param name="request">The request that caused this response message to be constructed.</param>
+ /// <param name="mode">The value of the openid.mode parameter.</param>
+ protected IndirectResponseBase(SignedResponseRequest request, string mode)
+ : base(GetVersion(request), GetReturnTo(request), mode, MessageTransport.Indirect) {
+ Requires.NotNull(request, "request");
+
+ this.OriginatingRequest = request;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="IndirectResponseBase"/> class
+ /// for unsolicited assertion scenarios.
+ /// </summary>
+ /// <param name="version">The OpenID version supported at the Relying Party.</param>
+ /// <param name="relyingPartyReturnTo">
+ /// The URI at which the Relying Party receives OpenID indirect messages.
+ /// </param>
+ /// <param name="mode">The value to use for the openid.mode parameter.</param>
+ protected IndirectResponseBase(Version version, Uri relyingPartyReturnTo, string mode)
+ : base(version, relyingPartyReturnTo, mode, MessageTransport.Indirect) {
+ }
+
+ #region IProtocolMessageWithExtensions Members
+
+ /// <summary>
+ /// Gets the list of extensions that are included with this message.
+ /// </summary>
+ /// <value></value>
+ /// <remarks>
+ /// Implementations of this interface should ensure that this property never returns null.
+ /// </remarks>
+ public IList<IExtensionMessage> Extensions {
+ get { return this.extensions; }
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Gets the signed extensions on this message.
+ /// </summary>
+ internal IEnumerable<IOpenIdMessageExtension> SignedExtensions {
+ get { return this.extensions.OfType<IOpenIdMessageExtension>().Where(ext => ext.IsSignedByRemoteParty); }
+ }
+
+ /// <summary>
+ /// Gets the unsigned extensions on this message.
+ /// </summary>
+ internal IEnumerable<IOpenIdMessageExtension> UnsignedExtensions {
+ get { return this.extensions.OfType<IOpenIdMessageExtension>().Where(ext => !ext.IsSignedByRemoteParty); }
+ }
+
+ /// <summary>
+ /// Gets the originating request message, if applicable.
+ /// </summary>
+ protected SignedResponseRequest OriginatingRequest { get; private set; }
+
+ /// <summary>
+ /// Gets the <see cref="IMessage.Version"/> property of a message.
+ /// </summary>
+ /// <param name="message">The message to fetch the protocol version from.</param>
+ /// <returns>The value of the <see cref="IMessage.Version"/> property.</returns>
+ /// <remarks>
+ /// This method can be used by a constructor to throw an <see cref="ArgumentNullException"/>
+ /// instead of a <see cref="NullReferenceException"/>.
+ /// </remarks>
+ internal static Version GetVersion(IProtocolMessage message) {
+ Requires.NotNull(message, "message");
+ return message.Version;
+ }
+
+ /// <summary>
+ /// Gets the <see cref="SignedResponseRequest.ReturnTo"/> property of a message.
+ /// </summary>
+ /// <param name="message">The message to fetch the ReturnTo from.</param>
+ /// <returns>The value of the <see cref="SignedResponseRequest.ReturnTo"/> property.</returns>
+ /// <remarks>
+ /// This method can be used by a constructor to throw an <see cref="ArgumentNullException"/>
+ /// instead of a <see cref="NullReferenceException"/>.
+ /// </remarks>
+ private static Uri GetReturnTo(SignedResponseRequest message) {
+ Requires.NotNull(message, "message");
+ ErrorUtilities.VerifyProtocol(message.ReturnTo != null, OpenIdStrings.ReturnToRequiredForResponse);
+ return message.ReturnTo;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/IndirectSignedResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/IndirectSignedResponse.cs
new file mode 100644
index 0000000..8bceb68
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/IndirectSignedResponse.cs
@@ -0,0 +1,411 @@
+//-----------------------------------------------------------------------
+// <copyright file="IndirectSignedResponse.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.Specialized;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.Linq;
+ using System.Net.Security;
+ using System.Web;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
+ using DotNetOpenAuth.Messaging.Reflection;
+ using DotNetOpenAuth.OpenId.ChannelElements;
+
+ /// <summary>
+ /// An indirect message from a Provider to a Relying Party where at least part of the
+ /// payload is signed so the Relying Party can verify it has not been tampered with.
+ /// </summary>
+ [DebuggerDisplay("OpenID {Version} {Mode} (no id assertion)")]
+ [Serializable]
+ [SuppressMessage("Microsoft.StyleCop.CSharp.DocumentationRules", "SA1630:DocumentationTextMustContainWhitespace", Justification = "The samples are string literals.")]
+ [SuppressMessage("Microsoft.StyleCop.CSharp.DocumentationRules", "SA1631:DocumentationMustMeetCharacterPercentage", Justification = "The samples are string literals.")]
+ internal class IndirectSignedResponse : IndirectResponseBase, ITamperResistantOpenIdMessage {
+ /// <summary>
+ /// The allowed date/time formats for the response_nonce parameter.
+ /// </summary>
+ /// <remarks>
+ /// This array of formats is not yet a complete list.
+ /// </remarks>
+ private static readonly string[] PermissibleDateTimeFormats = { "yyyy-MM-ddTHH:mm:ssZ" };
+
+ /// <summary>
+ /// Backing field for the <see cref="IExpiringProtocolMessage.UtcCreationDate"/> property.
+ /// </summary>
+ /// <remarks>
+ /// The field initializer being DateTime.UtcNow allows for OpenID 1.x messages
+ /// to pass through the StandardExpirationBindingElement.
+ /// </remarks>
+ private DateTime creationDateUtc = DateTime.UtcNow;
+
+ /// <summary>
+ /// Backing store for the <see cref="ReturnToParameters"/> property.
+ /// </summary>
+ private IDictionary<string, string> returnToParameters;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="IndirectSignedResponse"/> class.
+ /// </summary>
+ /// <param name="request">
+ /// The authentication request that caused this assertion to be generated.
+ /// </param>
+ internal IndirectSignedResponse(SignedResponseRequest request)
+ : base(request, Protocol.Lookup(GetVersion(request)).Args.Mode.id_res) {
+ Requires.NotNull(request, "request");
+
+ this.ReturnTo = request.ReturnTo;
+ this.ProviderEndpoint = request.Recipient.StripQueryArgumentsWithPrefix(Protocol.openid.Prefix);
+ ((ITamperResistantOpenIdMessage)this).AssociationHandle = request.AssociationHandle;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="IndirectSignedResponse"/> class
+ /// in order to perform signature verification at the Provider.
+ /// </summary>
+ /// <param name="previouslySignedMessage">The previously signed message.</param>
+ /// <param name="channel">The channel. This is used only within the constructor and is not stored in a field.</param>
+ internal IndirectSignedResponse(CheckAuthenticationRequest previouslySignedMessage, Channel channel)
+ : base(GetVersion(previouslySignedMessage), previouslySignedMessage.ReturnTo, Protocol.Lookup(GetVersion(previouslySignedMessage)).Args.Mode.id_res) {
+ Requires.NotNull(channel, "channel");
+
+ // Copy all message parts from the check_authentication message into this one,
+ // except for the openid.mode parameter.
+ MessageDictionary checkPayload = channel.MessageDescriptions.GetAccessor(previouslySignedMessage);
+ MessageDictionary thisPayload = channel.MessageDescriptions.GetAccessor(this);
+ foreach (var pair in checkPayload) {
+ if (!string.Equals(pair.Key, this.Protocol.openid.mode)) {
+ thisPayload[pair.Key] = pair.Value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="IndirectSignedResponse"/> class
+ /// for unsolicited assertions.
+ /// </summary>
+ /// <param name="version">The OpenID version to use.</param>
+ /// <param name="relyingPartyReturnTo">The return_to URL of the Relying Party.
+ /// This value will commonly be from <see cref="SignedResponseRequest.ReturnTo"/>,
+ /// but for unsolicited assertions may come from the Provider performing RP discovery
+ /// to find the appropriate return_to URL to use.</param>
+ internal IndirectSignedResponse(Version version, Uri relyingPartyReturnTo)
+ : base(version, relyingPartyReturnTo, Protocol.Lookup(version).Args.Mode.id_res) {
+ this.ReturnTo = relyingPartyReturnTo;
+ }
+
+ /// <summary>
+ /// Gets the level of protection this message requires.
+ /// </summary>
+ /// <value>
+ /// <see cref="MessageProtections.All"/> for OpenID 2.0 messages.
+ /// <see cref="MessageProtections.TamperProtection"/> for OpenID 1.x messages.
+ /// </value>
+ /// <remarks>
+ /// Although the required protection is reduced for OpenID 1.x,
+ /// this library will provide Relying Party hosts with all protections
+ /// by adding its own specially-crafted nonce to the authentication request
+ /// messages except for stateless RPs in OpenID 1.x messages.
+ /// </remarks>
+ public override MessageProtections RequiredProtection {
+ // We actually manage to provide All protections regardless of OpenID version
+ // on both the Provider and Relying Party side, except for stateless RPs for OpenID 1.x.
+ get { return this.Version.Major < 2 ? MessageProtections.TamperProtection : MessageProtections.All; }
+ }
+
+ /// <summary>
+ /// Gets or sets the message signature.
+ /// </summary>
+ /// <value>Base 64 encoded signature calculated as specified in Section 6 (Generating Signatures).</value>
+ [MessagePart("openid.sig", IsRequired = true, AllowEmpty = false)]
+ string ITamperResistantProtocolMessage.Signature { get; set; }
+
+ /// <summary>
+ /// Gets or sets the signed parameter order.
+ /// </summary>
+ /// <value>Comma-separated list of signed fields.</value>
+ /// <example>"op_endpoint,identity,claimed_id,return_to,assoc_handle,response_nonce"</example>
+ /// <remarks>
+ /// This entry consists of the fields without the "openid." prefix that the signature covers.
+ /// This list MUST contain at least "op_endpoint", "return_to" "response_nonce" and "assoc_handle",
+ /// and if present in the response, "claimed_id" and "identity".
+ /// Additional keys MAY be signed as part of the message. See Generating Signatures.
+ /// </remarks>
+ [MessagePart("openid.signed", IsRequired = true, AllowEmpty = false)]
+ string ITamperResistantOpenIdMessage.SignedParameterOrder { get; set; }
+
+ /// <summary>
+ /// Gets or sets the association handle used to sign the message.
+ /// </summary>
+ /// <value>The handle for the association that was used to sign this assertion. </value>
+ [MessagePart("openid.assoc_handle", IsRequired = true, AllowEmpty = false, RequiredProtection = ProtectionLevel.Sign, MinVersion = "2.0")]
+ [MessagePart("openid.assoc_handle", IsRequired = true, AllowEmpty = false, RequiredProtection = ProtectionLevel.None, MaxVersion = "1.1")]
+ string ITamperResistantOpenIdMessage.AssociationHandle { get; set; }
+
+ /// <summary>
+ /// Gets or sets the nonce that will protect the message from replay attacks.
+ /// </summary>
+ string IReplayProtectedProtocolMessage.Nonce { get; set; }
+
+ /// <summary>
+ /// Gets the context within which the nonce must be unique.
+ /// </summary>
+ string IReplayProtectedProtocolMessage.NonceContext {
+ get {
+ if (this.ProviderEndpoint != null) {
+ return this.ProviderEndpoint.AbsoluteUri;
+ } else {
+ // This is the Provider, on an OpenID 1.x check_authentication message.
+ // We don't need any special nonce context because the Provider
+ // generated and consumed the nonce.
+ return string.Empty;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the UTC date/time the message was originally sent onto the network.
+ /// </summary>
+ /// <remarks>
+ /// The property setter should ensure a UTC date/time,
+ /// and throw an exception if this is not possible.
+ /// </remarks>
+ /// <exception cref="ArgumentException">
+ /// Thrown when a DateTime that cannot be converted to UTC is set.
+ /// </exception>
+ DateTime IExpiringProtocolMessage.UtcCreationDate {
+ get { return this.creationDateUtc; }
+ set { this.creationDateUtc = value.ToUniversalTimeSafe(); }
+ }
+
+ /// <summary>
+ /// Gets or sets the association handle that the Provider wants the Relying Party to not use any more.
+ /// </summary>
+ /// <value>If the Relying Party sent an invalid association handle with the request, it SHOULD be included here.</value>
+ /// <remarks>
+ /// For OpenID 1.1, we allow this to be present but empty to put up with poor implementations such as Blogger.
+ /// </remarks>
+ [MessagePart("openid.invalidate_handle", IsRequired = false, AllowEmpty = true, MaxVersion = "1.1")]
+ [MessagePart("openid.invalidate_handle", IsRequired = false, AllowEmpty = false, MinVersion = "2.0")]
+ string ITamperResistantOpenIdMessage.InvalidateHandle { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Provider Endpoint URI.
+ /// </summary>
+ [MessagePart("openid.op_endpoint", IsRequired = true, AllowEmpty = false, RequiredProtection = ProtectionLevel.Sign, MinVersion = "2.0")]
+ internal Uri ProviderEndpoint { get; set; }
+
+ /// <summary>
+ /// Gets or sets the return_to parameter as the relying party provided
+ /// it in <see cref="SignedResponseRequest.ReturnTo"/>.
+ /// </summary>
+ /// <value>Verbatim copy of the return_to URL parameter sent in the
+ /// request, before the Provider modified it. </value>
+ [MessagePart("openid.return_to", IsRequired = true, AllowEmpty = false, RequiredProtection = ProtectionLevel.Sign, Encoder = typeof(OriginalStringUriEncoder))]
+ internal Uri ReturnTo { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the <see cref="ReturnTo"/>
+ /// URI's query string is unaltered between when the Relying Party
+ /// sent the original request and when the response was received.
+ /// </summary>
+ /// <remarks>
+ /// This property is not persisted in the transmitted message, and
+ /// has no effect on the Provider-side of the communication.
+ /// </remarks>
+ internal bool ReturnToParametersSignatureValidated { get; set; }
+
+ /// <summary>
+ /// Gets or sets the nonce that will protect the message from replay attacks.
+ /// </summary>
+ /// <value>
+ /// <para>A string 255 characters or less in length, that MUST be unique to
+ /// this particular successful authentication response. The nonce MUST start
+ /// with the current time on the server, and MAY contain additional ASCII
+ /// characters in the range 33-126 inclusive (printable non-whitespace characters),
+ /// as necessary to make each response unique. The date and time MUST be
+ /// formatted as specified in section 5.6 of [RFC3339]
+ /// (Klyne, G. and C. Newman, “Date and Time on the Internet: Timestamps,” .),
+ /// with the following restrictions:</para>
+ /// <list type="bullet">
+ /// <item>All times must be in the UTC timezone, indicated with a "Z".</item>
+ /// <item>No fractional seconds are allowed</item>
+ /// </list>
+ /// </value>
+ /// <example>2005-05-15T17:11:51ZUNIQUE</example>
+ internal string ResponseNonceTestHook {
+ get { return this.ResponseNonce; }
+ set { this.ResponseNonce = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the nonce that will protect the message from replay attacks.
+ /// </summary>
+ /// <value>
+ /// <para>A string 255 characters or less in length, that MUST be unique to
+ /// this particular successful authentication response. The nonce MUST start
+ /// with the current time on the server, and MAY contain additional ASCII
+ /// characters in the range 33-126 inclusive (printable non-whitespace characters),
+ /// as necessary to make each response unique. The date and time MUST be
+ /// formatted as specified in section 5.6 of [RFC3339]
+ /// (Klyne, G. and C. Newman, “Date and Time on the Internet: Timestamps,” .),
+ /// with the following restrictions:</para>
+ /// <list type="bullet">
+ /// <item>All times must be in the UTC timezone, indicated with a "Z".</item>
+ /// <item>No fractional seconds are allowed</item>
+ /// </list>
+ /// </value>
+ /// <example>2005-05-15T17:11:51ZUNIQUE</example>
+ [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by messaging framework via reflection.")]
+ [MessagePart("openid.response_nonce", IsRequired = true, AllowEmpty = false, RequiredProtection = ProtectionLevel.Sign, MinVersion = "2.0")]
+ [MessagePart("openid.response_nonce", IsRequired = false, AllowEmpty = false, RequiredProtection = ProtectionLevel.None, MaxVersion = "1.1")]
+ private string ResponseNonce {
+ get {
+ string uniqueFragment = ((IReplayProtectedProtocolMessage)this).Nonce;
+ return this.creationDateUtc.ToString(PermissibleDateTimeFormats[0], CultureInfo.InvariantCulture) + uniqueFragment;
+ }
+
+ set {
+ if (value == null) {
+ ((IReplayProtectedProtocolMessage)this).Nonce = null;
+ } else {
+ int indexOfZ = value.IndexOf("Z", StringComparison.Ordinal);
+ ErrorUtilities.VerifyProtocol(indexOfZ >= 0, MessagingStrings.UnexpectedMessagePartValue, Protocol.openid.response_nonce, value);
+ this.creationDateUtc = DateTime.Parse(value.Substring(0, indexOfZ + 1), CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal);
+ ((IReplayProtectedProtocolMessage)this).Nonce = value.Substring(indexOfZ + 1);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets the querystring key=value pairs in the return_to URL.
+ /// </summary>
+ private IDictionary<string, string> ReturnToParameters {
+ get {
+ if (this.returnToParameters == null) {
+ this.returnToParameters = HttpUtility.ParseQueryString(this.ReturnTo.Query).ToDictionary();
+ }
+
+ return this.returnToParameters;
+ }
+ }
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ /// <remarks>
+ /// <para>Some messages have required fields, or combinations of fields that must relate to each other
+ /// in specialized ways. After deserializing a message, this method checks the state of the
+ /// message to see if it conforms to the protocol.</para>
+ /// <para>Note that this property should <i>not</i> check signatures or perform any state checks
+ /// outside this scope of this particular message.</para>
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception>
+ public override void EnsureValidMessage() {
+ base.EnsureValidMessage();
+
+ this.VerifyReturnToMatchesRecipient();
+ }
+
+ /// <summary>
+ /// Gets the value of a named parameter in the return_to URL without signature protection.
+ /// </summary>
+ /// <param name="key">The full name of the parameter whose value is being sought.</param>
+ /// <returns>The value of the parameter if it is present and unaltered from when
+ /// the Relying Party signed it; <c>null</c> otherwise.</returns>
+ /// <remarks>
+ /// This method will always return null on the Provider-side, since Providers
+ /// cannot verify the private signature made by the relying party.
+ /// </remarks>
+ internal string GetReturnToArgument(string key) {
+ Requires.NotNullOrEmpty(key, "key");
+ ErrorUtilities.VerifyInternal(this.ReturnTo != null, "ReturnTo was expected to be required but is null.");
+
+ string value;
+ this.ReturnToParameters.TryGetValue(key, out value);
+ return value;
+ }
+
+ /// <summary>
+ /// Gets the names of the callback parameters added to the original authentication request
+ /// without signature protection.
+ /// </summary>
+ /// <returns>A sequence of the callback parameter names.</returns>
+ internal IEnumerable<string> GetReturnToParameterNames() {
+ return this.ReturnToParameters.Keys;
+ }
+
+ /// <summary>
+ /// Gets a dictionary of all the message part names and values
+ /// that are included in the message signature.
+ /// </summary>
+ /// <param name="channel">The channel.</param>
+ /// <returns>
+ /// A dictionary of the signed message parts.
+ /// </returns>
+ internal IDictionary<string, string> GetSignedMessageParts(Channel channel) {
+ Requires.NotNull(channel, "channel");
+
+ ITamperResistantOpenIdMessage signedSelf = this;
+ if (signedSelf.SignedParameterOrder == null) {
+ return EmptyDictionary<string, string>.Instance;
+ }
+
+ MessageDictionary messageDictionary = channel.MessageDescriptions.GetAccessor(this);
+ string[] signedPartNamesWithoutPrefix = signedSelf.SignedParameterOrder.Split(',');
+ Dictionary<string, string> signedParts = new Dictionary<string, string>(signedPartNamesWithoutPrefix.Length);
+
+ var signedPartNames = signedPartNamesWithoutPrefix.Select(part => Protocol.openid.Prefix + part);
+ foreach (string partName in signedPartNames) {
+ signedParts[partName] = messageDictionary[partName];
+ }
+
+ return signedParts;
+ }
+
+ /// <summary>
+ /// Determines whether one querystring contains every key=value pair that
+ /// another querystring contains.
+ /// </summary>
+ /// <param name="superset">The querystring that should contain at least all the key=value pairs of the other.</param>
+ /// <param name="subset">The querystring containing the set of key=value pairs to test for in the other.</param>
+ /// <returns>
+ /// <c>true</c> if <paramref name="superset"/> contains all the query parameters that <paramref name="subset"/> does; <c>false</c> otherwise.
+ /// </returns>
+ private static bool IsQuerySubsetOf(string superset, string subset) {
+ NameValueCollection subsetArgs = HttpUtility.ParseQueryString(subset);
+ NameValueCollection supersetArgs = HttpUtility.ParseQueryString(superset);
+ return subsetArgs.Keys.Cast<string>().All(key => string.Equals(subsetArgs[key], supersetArgs[key], StringComparison.Ordinal));
+ }
+
+ /// <summary>
+ /// Verifies that the openid.return_to field matches the URL of the actual HTTP request.
+ /// </summary>
+ /// <remarks>
+ /// From OpenId Authentication 2.0 section 11.1:
+ /// To verify that the "openid.return_to" URL matches the URL that is processing this assertion:
+ /// * The URL scheme, authority, and path MUST be the same between the two URLs.
+ /// * Any query parameters that are present in the "openid.return_to" URL MUST
+ /// also be present with the same values in the URL of the HTTP request the RP received.
+ /// </remarks>
+ private void VerifyReturnToMatchesRecipient() {
+ ErrorUtilities.VerifyProtocol(
+ string.Equals(this.Recipient.Scheme, this.ReturnTo.Scheme, StringComparison.OrdinalIgnoreCase) &&
+ string.Equals(this.Recipient.Authority, this.ReturnTo.Authority, StringComparison.OrdinalIgnoreCase) &&
+ string.Equals(this.Recipient.AbsolutePath, this.ReturnTo.AbsolutePath, StringComparison.Ordinal) &&
+ IsQuerySubsetOf(this.Recipient.Query, this.ReturnTo.Query),
+ OpenIdStrings.ReturnToParamDoesNotMatchRequestUrl,
+ Protocol.openid.return_to,
+ this.ReturnTo,
+ this.Recipient);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/NegativeAssertionResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/NegativeAssertionResponse.cs
new file mode 100644
index 0000000..43fd6f5
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/NegativeAssertionResponse.cs
@@ -0,0 +1,141 @@
+//-----------------------------------------------------------------------
+// <copyright file="NegativeAssertionResponse.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// The message OpenID Providers send back to Relying Parties to refuse
+ /// to assert the identity of a user.
+ /// </summary>
+ [Serializable]
+ internal class NegativeAssertionResponse : IndirectResponseBase {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="NegativeAssertionResponse"/> class.
+ /// </summary>
+ /// <param name="request">The request that the relying party sent.</param>
+ internal NegativeAssertionResponse(CheckIdRequest request)
+ : this(request, null) {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="NegativeAssertionResponse"/> class.
+ /// </summary>
+ /// <param name="request">The request that the relying party sent.</param>
+ /// <param name="channel">The channel to use to simulate construction of the user_setup_url, if applicable. May be null, but the user_setup_url will not be constructed.</param>
+ internal NegativeAssertionResponse(SignedResponseRequest request, Channel channel)
+ : base(request, GetMode(request)) {
+ // If appropriate, and when we're provided with a channel to do it,
+ // go ahead and construct the user_setup_url
+ if (this.Version.Major < 2 && request.Immediate && channel != null) {
+ // All requests are CheckIdRequests in OpenID 1.x, so this cast should be safe.
+ this.UserSetupUrl = ConstructUserSetupUrl((CheckIdRequest)request, channel);
+ }
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="NegativeAssertionResponse"/> class.
+ /// </summary>
+ /// <param name="version">The version.</param>
+ /// <param name="relyingPartyReturnTo">The relying party return to.</param>
+ /// <param name="mode">The value of the openid.mode parameter.</param>
+ internal NegativeAssertionResponse(Version version, Uri relyingPartyReturnTo, string mode)
+ : base(version, relyingPartyReturnTo, mode) {
+ }
+
+ /// <summary>
+ /// Gets or sets the URL the relying party can use to upgrade their authentication
+ /// request from an immediate to a setup message.
+ /// </summary>
+ /// <value>URL to redirect User-Agent to so the End User can do whatever's necessary to fulfill the assertion.</value>
+ /// <remarks>
+ /// This part is only included in OpenID 1.x responses.
+ /// </remarks>
+ [MessagePart("openid.user_setup_url", AllowEmpty = false, IsRequired = false, MaxVersion = "1.1")]
+ internal Uri UserSetupUrl { get; set; }
+
+ /// <summary>
+ /// Gets a value indicating whether this <see cref="NegativeAssertionResponse"/>
+ /// is in response to an authentication request made in immediate mode.
+ /// </summary>
+ /// <value><c>true</c> if the request was in immediate mode; otherwise, <c>false</c>.</value>
+ internal bool Immediate {
+ get {
+ if (this.OriginatingRequest != null) {
+ return this.OriginatingRequest.Immediate;
+ } else {
+ if (string.Equals(this.Mode, Protocol.Args.Mode.setup_needed, StringComparison.Ordinal)) {
+ return true;
+ } else if (string.Equals(this.Mode, Protocol.Args.Mode.cancel, StringComparison.Ordinal)) {
+ return false;
+ } else {
+ throw ErrorUtilities.ThrowProtocol(MessagingStrings.UnexpectedMessagePartValue, Protocol.openid.mode, this.Mode);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ /// <remarks>
+ /// <para>Some messages have required fields, or combinations of fields that must relate to each other
+ /// in specialized ways. After deserializing a message, this method checks the state of the
+ /// message to see if it conforms to the protocol.</para>
+ /// <para>Note that this property should <i>not</i> check signatures or perform any state checks
+ /// outside this scope of this particular message.</para>
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception>
+ public override void EnsureValidMessage() {
+ base.EnsureValidMessage();
+
+ // Since there are a couple of negative assertion modes, ensure that the mode given is one of the allowed ones.
+ ErrorUtilities.VerifyProtocol(string.Equals(this.Mode, Protocol.Args.Mode.setup_needed, StringComparison.Ordinal) || string.Equals(this.Mode, Protocol.Args.Mode.cancel, StringComparison.Ordinal), MessagingStrings.UnexpectedMessagePartValue, Protocol.openid.mode, this.Mode);
+
+ if (this.Immediate && Protocol.Version.Major < 2) {
+ ErrorUtilities.VerifyProtocol(this.UserSetupUrl != null, OpenIdStrings.UserSetupUrlRequiredInImmediateNegativeResponse);
+ }
+ }
+
+ /// <summary>
+ /// Constructs the value for the user_setup_url parameter to be sent back
+ /// in negative assertions in response to OpenID 1.x RP's checkid_immediate requests.
+ /// </summary>
+ /// <param name="immediateRequest">The immediate request.</param>
+ /// <param name="channel">The channel to use to simulate construction of the message.</param>
+ /// <returns>The value to use for the user_setup_url parameter.</returns>
+ private static Uri ConstructUserSetupUrl(CheckIdRequest immediateRequest, Channel channel) {
+ Requires.NotNull(immediateRequest, "immediateRequest");
+ Requires.NotNull(channel, "channel");
+ ErrorUtilities.VerifyInternal(immediateRequest.Immediate, "Only immediate requests should be sent here.");
+
+ var setupRequest = new CheckIdRequest(immediateRequest.Version, immediateRequest.Recipient, AuthenticationRequestMode.Setup);
+ setupRequest.LocalIdentifier = immediateRequest.LocalIdentifier;
+ setupRequest.ReturnTo = immediateRequest.ReturnTo;
+ setupRequest.Realm = immediateRequest.Realm;
+ setupRequest.AssociationHandle = immediateRequest.AssociationHandle;
+ return channel.PrepareResponse(setupRequest).GetDirectUriRequest(channel);
+ }
+
+ /// <summary>
+ /// Gets the value for the openid.mode that is appropriate for this response.
+ /// </summary>
+ /// <param name="request">The request that we're responding to.</param>
+ /// <returns>The value of the openid.mode parameter to use.</returns>
+ private static string GetMode(SignedResponseRequest request) {
+ Requires.NotNull(request, "request");
+
+ Protocol protocol = Protocol.Lookup(request.Version);
+ return request.Immediate ? protocol.Args.Mode.setup_needed : protocol.Args.Mode.cancel;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/PositiveAssertionResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/PositiveAssertionResponse.cs
new file mode 100644
index 0000000..16ee9ea
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/PositiveAssertionResponse.cs
@@ -0,0 +1,83 @@
+//-----------------------------------------------------------------------
+// <copyright file="PositiveAssertionResponse.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Globalization;
+ using System.Linq;
+ using System.Net.Security;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
+ using DotNetOpenAuth.OpenId.ChannelElements;
+
+ /// <summary>
+ /// An identity assertion from a Provider to a Relying Party, stating that the
+ /// user operating the user agent is in fact some specific user known to the Provider.
+ /// </summary>
+ [DebuggerDisplay("OpenID {Version} {Mode} {LocalIdentifier}")]
+ [Serializable]
+ internal class PositiveAssertionResponse : IndirectSignedResponse {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PositiveAssertionResponse"/> class.
+ /// </summary>
+ /// <param name="request">
+ /// The authentication request that caused this assertion to be generated.
+ /// </param>
+ internal PositiveAssertionResponse(CheckIdRequest request)
+ : base(request) {
+ this.ClaimedIdentifier = request.ClaimedIdentifier;
+ this.LocalIdentifier = request.LocalIdentifier;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PositiveAssertionResponse"/> class
+ /// for unsolicited assertions.
+ /// </summary>
+ /// <param name="version">The OpenID version to use.</param>
+ /// <param name="relyingPartyReturnTo">The return_to URL of the Relying Party.
+ /// This value will commonly be from <see cref="SignedResponseRequest.ReturnTo"/>,
+ /// but for unsolicited assertions may come from the Provider performing RP discovery
+ /// to find the appropriate return_to URL to use.</param>
+ internal PositiveAssertionResponse(Version version, Uri relyingPartyReturnTo)
+ : base(version, relyingPartyReturnTo) {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PositiveAssertionResponse"/> class.
+ /// </summary>
+ /// <param name="relyingParty">The relying party return_to endpoint that will receive this positive assertion.</param>
+ internal PositiveAssertionResponse(RelyingPartyEndpointDescription relyingParty)
+ : this(relyingParty.Protocol.Version, relyingParty.ReturnToEndpoint) {
+ }
+
+ /// <summary>
+ /// Gets or sets the Claimed Identifier.
+ /// </summary>
+ /// <remarks>
+ /// <para>"openid.claimed_id" and "openid.identity" SHALL be either both present or both absent.
+ /// If neither value is present, the assertion is not about an identifier,
+ /// and will contain other information in its payload, using extensions (Extensions). </para>
+ /// </remarks>
+ [MessagePart("openid.claimed_id", IsRequired = true, AllowEmpty = false, RequiredProtection = ProtectionLevel.Sign, MinVersion = "2.0")]
+ internal Identifier ClaimedIdentifier { get; set; }
+
+ /// <summary>
+ /// Gets or sets the OP Local Identifier.
+ /// </summary>
+ /// <value>The OP-Local Identifier. </value>
+ /// <remarks>
+ /// <para>OpenID Providers MAY assist the end user in selecting the Claimed
+ /// and OP-Local Identifiers about which the assertion is made.
+ /// The openid.identity field MAY be omitted if an extension is in use that
+ /// makes the response meaningful without it (see openid.claimed_id above). </para>
+ /// </remarks>
+ [MessagePart("openid.identity", IsRequired = true, AllowEmpty = false, RequiredProtection = ProtectionLevel.Sign)]
+ internal Identifier LocalIdentifier { get; set; }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/RequestBase.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/RequestBase.cs
new file mode 100644
index 0000000..678dcee
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/RequestBase.cs
@@ -0,0 +1,185 @@
+//-----------------------------------------------------------------------
+// <copyright file="RequestBase.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// A common base class for OpenID request messages and indirect responses (since they are ultimately requests).
+ /// </summary>
+ [DebuggerDisplay("OpenID {Version} {Mode}")]
+ [Serializable]
+ internal class RequestBase : IDirectedProtocolMessage {
+ /// <summary>
+ /// The openid.ns parameter in the message.
+ /// </summary>
+ /// <value>"http://specs.openid.net/auth/2.0" </value>
+ /// <remarks>
+ /// This particular value MUST be present for the request to be a valid OpenID Authentication 2.0 request. Future versions of the specification may define different values in order to allow message recipients to properly interpret the request.
+ /// </remarks>
+ [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Read by reflection.")]
+ [MessagePart("openid.ns", IsRequired = true, AllowEmpty = false, MinVersion = "2.0")]
+#pragma warning disable 0414 // read by reflection
+ private readonly string OpenIdNamespace = Protocol.OpenId2Namespace;
+#pragma warning restore 0414
+
+ /// <summary>
+ /// Backing store for the <see cref="ExtraData"/> property.
+ /// </summary>
+ private readonly Dictionary<string, string> extraData = new Dictionary<string, string>();
+
+ /// <summary>
+ /// Backing store for the <see cref="Incoming"/> property.
+ /// </summary>
+ private bool incoming;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RequestBase"/> class.
+ /// </summary>
+ /// <param name="version">The OpenID version this message must comply with.</param>
+ /// <param name="providerEndpoint">The OpenID Provider endpoint.</param>
+ /// <param name="mode">The value for the openid.mode parameter.</param>
+ /// <param name="transport">A value indicating whether the message will be transmitted directly or indirectly.</param>
+ protected RequestBase(Version version, Uri providerEndpoint, string mode, MessageTransport transport) {
+ Requires.NotNull(providerEndpoint, "providerEndpoint");
+ Requires.NotNullOrEmpty(mode, "mode");
+
+ this.Recipient = providerEndpoint;
+ this.Mode = mode;
+ this.Transport = transport;
+ this.Version = version;
+ }
+
+ /// <summary>
+ /// Gets the value of the openid.mode parameter.
+ /// </summary>
+ [MessagePart("openid.mode", IsRequired = true, AllowEmpty = false)]
+ public string Mode { get; private set; }
+
+ #region IDirectedProtocolMessage Members
+
+ /// <summary>
+ /// Gets the preferred method of transport for the message.
+ /// </summary>
+ /// <value>
+ /// For direct messages this is the OpenID mandated POST.
+ /// For indirect messages both GET and POST are allowed.
+ /// </value>
+ HttpDeliveryMethods IDirectedProtocolMessage.HttpMethods {
+ get {
+ // OpenID 2.0 section 5.1.1
+ HttpDeliveryMethods methods = HttpDeliveryMethods.PostRequest;
+ if (this.Transport == MessageTransport.Indirect) {
+ methods |= HttpDeliveryMethods.GetRequest;
+ }
+ return methods;
+ }
+ }
+
+ /// <summary>
+ /// Gets the recipient of the message.
+ /// </summary>
+ /// <value>The OP endpoint, or the RP return_to.</value>
+ public Uri Recipient {
+ get;
+ private set;
+ }
+
+ #endregion
+
+ #region IProtocolMessage Properties
+
+ /// <summary>
+ /// Gets the version of the protocol this message is prepared to implement.
+ /// </summary>
+ /// <value>Version 2.0</value>
+ public Version Version { get; private set; }
+
+ /// <summary>
+ /// Gets the level of protection this message requires.
+ /// </summary>
+ /// <value><see cref="MessageProtections.None"/></value>
+ public virtual MessageProtections RequiredProtection {
+ get { return MessageProtections.None; }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this is a direct or indirect message.
+ /// </summary>
+ /// <value><see cref="MessageTransport.Direct"/></value>
+ public MessageTransport Transport { get; private set; }
+
+ /// <summary>
+ /// Gets the extra parameters included in the message.
+ /// </summary>
+ /// <value>An empty dictionary.</value>
+ public IDictionary<string, string> ExtraData {
+ get { return this.extraData; }
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Gets a value indicating whether this message was deserialized as an incoming message.
+ /// </summary>
+ protected internal bool Incoming {
+ get { return this.incoming; }
+ }
+
+ /// <summary>
+ /// Gets the protocol used by this message.
+ /// </summary>
+ protected Protocol Protocol {
+ get { return Protocol.Lookup(this.Version); }
+ }
+
+ #region IProtocolMessage Methods
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ /// <remarks>
+ /// <para>Some messages have required fields, or combinations of fields that must relate to each other
+ /// in specialized ways. After deserializing a message, this method checks the state of the
+ /// message to see if it conforms to the protocol.</para>
+ /// <para>Note that this property should <i>not</i> check signatures or perform any state checks
+ /// outside this scope of this particular message.</para>
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception>
+ public virtual void EnsureValidMessage() {
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Sets a flag indicating that this message is received (as opposed to sent).
+ /// </summary>
+ internal void SetAsIncoming() {
+ this.incoming = true;
+ }
+
+ /// <summary>
+ /// Gets some string from a given version of the OpenID protocol.
+ /// </summary>
+ /// <param name="protocolVersion">The protocol version to use for lookup.</param>
+ /// <param name="mode">A function that can retrieve the desired protocol constant.</param>
+ /// <returns>The value of the constant.</returns>
+ /// <remarks>
+ /// This method can be used by a constructor to throw an <see cref="ArgumentNullException"/>
+ /// instead of a <see cref="NullReferenceException"/>.
+ /// </remarks>
+ protected static string GetProtocolConstant(Version protocolVersion, Func<Protocol, string> mode) {
+ Requires.NotNull(protocolVersion, "protocolVersion");
+ return mode(Protocol.Lookup(protocolVersion));
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Messages/SignedResponseRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Messages/SignedResponseRequest.cs
new file mode 100644
index 0000000..a2ebb64
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Messages/SignedResponseRequest.cs
@@ -0,0 +1,184 @@
+//-----------------------------------------------------------------------
+// <copyright file="SignedResponseRequest.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// An indirect request from a Relying Party to a Provider where the response
+ /// is expected to be signed.
+ /// </summary>
+ [Serializable]
+ internal class SignedResponseRequest : RequestBase, IProtocolMessageWithExtensions {
+ /// <summary>
+ /// Backing store for the <see cref="Extensions"/> property.
+ /// </summary>
+ private IList<IExtensionMessage> extensions = new List<IExtensionMessage>();
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SignedResponseRequest"/> class.
+ /// </summary>
+ /// <param name="version">The OpenID version to use.</param>
+ /// <param name="providerEndpoint">The Provider endpoint that receives this message.</param>
+ /// <param name="mode">
+ /// <see cref="AuthenticationRequestMode.Immediate"/> for asynchronous javascript clients;
+ /// <see cref="AuthenticationRequestMode.Setup"/> to allow the Provider to interact with the user in order to complete authentication.
+ /// </param>
+ internal SignedResponseRequest(Version version, Uri providerEndpoint, AuthenticationRequestMode mode) :
+ base(version, providerEndpoint, GetMode(version, mode), DotNetOpenAuth.Messaging.MessageTransport.Indirect) {
+ }
+
+ #region IProtocolMessageWithExtensions Members
+
+ /// <summary>
+ /// Gets the list of extensions that are included with this message.
+ /// </summary>
+ /// <value></value>
+ /// <remarks>
+ /// Implementations of this interface should ensure that this property never returns null.
+ /// </remarks>
+ public IList<IExtensionMessage> Extensions {
+ get { return this.extensions; }
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Gets a value indicating whether the Provider is allowed to interact with the user
+ /// as part of authentication.
+ /// </summary>
+ /// <value><c>true</c> if using OpenID immediate mode; otherwise, <c>false</c>.</value>
+ internal bool Immediate {
+ get { return string.Equals(this.Mode, Protocol.Args.Mode.checkid_immediate, StringComparison.Ordinal); }
+ }
+
+ /// <summary>
+ /// Gets or sets the handle of the association the RP would like the Provider
+ /// to use for signing a positive assertion in the response message.
+ /// </summary>
+ /// <value>A handle for an association between the Relying Party and the OP
+ /// that SHOULD be used to sign the response. </value>
+ /// <remarks>
+ /// If no association handle is sent, the transaction will take place in Stateless Mode
+ /// (Verifying Directly with the OpenID Provider).
+ /// </remarks>
+ [MessagePart("openid.assoc_handle", IsRequired = false, AllowEmpty = false)]
+ internal string AssociationHandle { get; set; }
+
+ /// <summary>
+ /// Gets or sets the URL the Provider should redirect the user agent to following
+ /// the authentication attempt.
+ /// </summary>
+ /// <value>URL to which the OP SHOULD return the User-Agent with the response
+ /// indicating the status of the request.</value>
+ /// <remarks>
+ /// <para>If this value is not sent in the request it signifies that the Relying Party
+ /// does not wish for the end user to be returned. </para>
+ /// <para>The return_to URL MAY be used as a mechanism for the Relying Party to attach
+ /// context about the authentication request to the authentication response.
+ /// This document does not define a mechanism by which the RP can ensure that query
+ /// parameters are not modified by outside parties; such a mechanism can be defined
+ /// by the RP itself. </para>
+ /// </remarks>
+ [MessagePart("openid.return_to", IsRequired = true, AllowEmpty = false)]
+ [MessagePart("openid.return_to", IsRequired = false, AllowEmpty = false, MinVersion = "2.0")]
+ internal Uri ReturnTo { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Relying Party discovery URL the Provider may use to verify the
+ /// source of the authentication request.
+ /// </summary>
+ /// <value>
+ /// URL pattern the OP SHOULD ask the end user to trust. See Section 9.2 (Realms).
+ /// This value MUST be sent if openid.return_to is omitted.
+ /// Default: The <see cref="ReturnTo"/> URL.
+ /// </value>
+ [MessagePart("openid.trust_root", IsRequired = false, AllowEmpty = false)]
+ [MessagePart("openid.realm", IsRequired = false, AllowEmpty = false, MinVersion = "2.0")]
+ internal Realm Realm { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the return_to value should be signed.
+ /// </summary>
+ internal bool SignReturnTo { get; set; }
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ /// <remarks>
+ /// <para>Some messages have required fields, or combinations of fields that must relate to each other
+ /// in specialized ways. After deserializing a message, this method checks the state of the
+ /// message to see if it conforms to the protocol.</para>
+ /// <para>Note that this property should <i>not</i> check signatures or perform any state checks
+ /// outside this scope of this particular message.</para>
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception>
+ public override void EnsureValidMessage() {
+ base.EnsureValidMessage();
+
+ if (this.Realm == null) {
+ // Set the default Realm per the spec if it is not explicitly given.
+ this.Realm = this.ReturnTo;
+ } else if (this.ReturnTo != null) {
+ // Verify that the realm and return_to agree.
+ ErrorUtilities.VerifyProtocol(this.Realm.Contains(this.ReturnTo), OpenIdStrings.ReturnToNotUnderRealm, this.ReturnTo, this.Realm);
+ }
+ }
+
+ /// <summary>
+ /// Adds parameters to the return_to querystring.
+ /// </summary>
+ /// <param name="keysValues">The keys=value pairs to add to the return_to query string.</param>
+ /// <remarks>
+ /// This method is useful if the Relying Party wants to recall some value
+ /// when and if a positive assertion comes back from the Provider.
+ /// </remarks>
+ internal void AddReturnToArguments(IEnumerable<KeyValuePair<string, string>> keysValues) {
+ Requires.NotNull(keysValues, "keysValues");
+ ErrorUtilities.VerifyOperation(this.ReturnTo != null, OpenIdStrings.ReturnToRequiredForOperation);
+ UriBuilder returnToBuilder = new UriBuilder(this.ReturnTo);
+ returnToBuilder.AppendAndReplaceQueryArgs(keysValues);
+ this.ReturnTo = returnToBuilder.Uri;
+ }
+
+ /// <summary>
+ /// Adds a parameter to the return_to querystring.
+ /// </summary>
+ /// <param name="key">The name of the parameter.</param>
+ /// <param name="value">The value of the argument.</param>
+ /// <remarks>
+ /// This method is useful if the Relying Party wants to recall some value
+ /// when and if a positive assertion comes back from the Provider.
+ /// </remarks>
+ internal void AddReturnToArguments(string key, string value) {
+ var pair = new KeyValuePair<string, string>(key, value);
+ this.AddReturnToArguments(new[] { pair });
+ }
+
+ /// <summary>
+ /// Gets the value of the openid.mode parameter based on the protocol version and immediate flag.
+ /// </summary>
+ /// <param name="version">The OpenID version to use.</param>
+ /// <param name="mode">
+ /// <see cref="AuthenticationRequestMode.Immediate"/> for asynchronous javascript clients;
+ /// <see cref="AuthenticationRequestMode.Setup"/> to allow the Provider to interact with the user in order to complete authentication.
+ /// </param>
+ /// <returns>checkid_immediate or checkid_setup</returns>
+ private static string GetMode(Version version, AuthenticationRequestMode mode) {
+ Requires.NotNull(version, "version");
+
+ Protocol protocol = Protocol.Lookup(version);
+ return mode == AuthenticationRequestMode.Immediate ? protocol.Args.Mode.checkid_immediate : protocol.Args.Mode.checkid_setup;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/NoDiscoveryIdentifier.cs b/src/DotNetOpenAuth.OpenId/OpenId/NoDiscoveryIdentifier.cs
new file mode 100644
index 0000000..c439f1f
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/NoDiscoveryIdentifier.cs
@@ -0,0 +1,100 @@
+//-----------------------------------------------------------------------
+// <copyright file="NoDiscoveryIdentifier.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// Wraps an existing Identifier and prevents it from performing discovery.
+ /// </summary>
+ [ContractVerification(true)]
+ [Pure]
+ internal class NoDiscoveryIdentifier : Identifier {
+ /// <summary>
+ /// The wrapped identifier.
+ /// </summary>
+ private readonly Identifier wrappedIdentifier;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="NoDiscoveryIdentifier"/> class.
+ /// </summary>
+ /// <param name="wrappedIdentifier">The ordinary Identifier whose discovery is being masked.</param>
+ /// <param name="claimSsl">Whether this Identifier should claim to be SSL-secure, although no discovery will never generate service endpoints anyway.</param>
+ internal NoDiscoveryIdentifier(Identifier wrappedIdentifier, bool claimSsl)
+ : base(wrappedIdentifier.OriginalString, claimSsl) {
+ Requires.NotNull(wrappedIdentifier, "wrappedIdentifier");
+
+ this.wrappedIdentifier = wrappedIdentifier;
+ }
+
+ /// <summary>
+ /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
+ /// </summary>
+ /// <returns>
+ /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
+ /// </returns>
+ public override string ToString() {
+ return this.wrappedIdentifier.ToString();
+ }
+
+ /// <summary>
+ /// Tests equality between two <see cref="Identifier"/>s.
+ /// </summary>
+ /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
+ /// <returns>
+ /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
+ /// </returns>
+ /// <exception cref="T:System.NullReferenceException">
+ /// The <paramref name="obj"/> parameter is null.
+ /// </exception>
+ public override bool Equals(object obj) {
+ return this.wrappedIdentifier.Equals(obj);
+ }
+
+ /// <summary>
+ /// Gets the hash code for an <see cref="Identifier"/> for storage in a hashtable.
+ /// </summary>
+ /// <returns>
+ /// A hash code for the current <see cref="T:System.Object"/>.
+ /// </returns>
+ public override int GetHashCode() {
+ return this.wrappedIdentifier.GetHashCode();
+ }
+
+ /// <summary>
+ /// Returns an <see cref="Identifier"/> that has no URI fragment.
+ /// Quietly returns the original <see cref="Identifier"/> if it is not
+ /// a <see cref="UriIdentifier"/> or no fragment exists.
+ /// </summary>
+ /// <returns>
+ /// A new <see cref="Identifier"/> instance if there was a
+ /// fragment to remove, otherwise this same instance..
+ /// </returns>
+ internal override Identifier TrimFragment() {
+ return new NoDiscoveryIdentifier(this.wrappedIdentifier.TrimFragment(), IsDiscoverySecureEndToEnd);
+ }
+
+ /// <summary>
+ /// Converts a given identifier to its secure equivalent.
+ /// UriIdentifiers originally created with an implied HTTP scheme change to HTTPS.
+ /// Discovery is made to require SSL for the entire resolution process.
+ /// </summary>
+ /// <param name="secureIdentifier">The newly created secure identifier.
+ /// If the conversion fails, <paramref name="secureIdentifier"/> retains
+ /// <i>this</i> identifiers identity, but will never discover any endpoints.</param>
+ /// <returns>
+ /// True if the secure conversion was successful.
+ /// False if the Identifier was originally created with an explicit HTTP scheme.
+ /// </returns>
+ internal override bool TryRequireSsl(out Identifier secureIdentifier) {
+ return this.wrappedIdentifier.TryRequireSsl(out secureIdentifier);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/OpenIdStrings.Designer.cs b/src/DotNetOpenAuth.OpenId/OpenId/OpenIdStrings.Designer.cs
new file mode 100644
index 0000000..f45af93
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/OpenIdStrings.Designer.cs
@@ -0,0 +1,823 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.225
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId {
+ using System;
+
+
+ /// <summary>
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ /// </summary>
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class OpenIdStrings {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal OpenIdStrings() {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DotNetOpenAuth.OpenId.OpenIdStrings", typeof(OpenIdStrings).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to An absolute URI is required for this value..
+ /// </summary>
+ internal static string AbsoluteUriRequired {
+ get {
+ return ResourceManager.GetString("AbsoluteUriRequired", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to This is already a PPID Identifier..
+ /// </summary>
+ internal static string ArgumentIsPpidIdentifier {
+ get {
+ return ResourceManager.GetString("ArgumentIsPpidIdentifier", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The requested association type &apos;{0}&apos; with session type &apos;{1}&apos; is unrecognized or not supported by this Provider due to security requirements..
+ /// </summary>
+ internal static string AssociationOrSessionTypeUnrecognizedOrNotSupported {
+ get {
+ return ResourceManager.GetString("AssociationOrSessionTypeUnrecognizedOrNotSupported", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The length of the shared secret ({0}) does not match the length required by the association type (&apos;{1}&apos;)..
+ /// </summary>
+ internal static string AssociationSecretAndTypeLengthMismatch {
+ get {
+ return ResourceManager.GetString("AssociationSecretAndTypeLengthMismatch", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The length of the encrypted shared secret ({0}) does not match the length of the hashing algorithm ({1})..
+ /// </summary>
+ internal static string AssociationSecretHashLengthMismatch {
+ get {
+ return ResourceManager.GetString("AssociationSecretHashLengthMismatch", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to No association store has been given but is required for the current configuration..
+ /// </summary>
+ internal static string AssociationStoreRequired {
+ get {
+ return ResourceManager.GetString("AssociationStoreRequired", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to If an association store is given, a nonce store must also be provided..
+ /// </summary>
+ internal static string AssociationStoreRequiresNonceStore {
+ get {
+ return ResourceManager.GetString("AssociationStoreRequiresNonceStore", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to An attribute with type URI &apos;{0}&apos; has already been added..
+ /// </summary>
+ internal static string AttributeAlreadyAdded {
+ get {
+ return ResourceManager.GetString("AttributeAlreadyAdded", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Only {0} values for attribute &apos;{1}&apos; were requested, but {2} were supplied..
+ /// </summary>
+ internal static string AttributeTooManyValues {
+ get {
+ return ResourceManager.GetString("AttributeTooManyValues", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The private data supplied does not meet the requirements of any known Association type. Its length may be too short, or it may have been corrupted..
+ /// </summary>
+ internal static string BadAssociationPrivateData {
+ get {
+ return ResourceManager.GetString("BadAssociationPrivateData", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The {0} extension failed to deserialize and will be skipped. {1}.
+ /// </summary>
+ internal static string BadExtension {
+ get {
+ return ResourceManager.GetString("BadExtension", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Callback arguments are only supported when a {0} is provided to the {1}..
+ /// </summary>
+ internal static string CallbackArgumentsRequireSecretStore {
+ get {
+ return ResourceManager.GetString("CallbackArgumentsRequireSecretStore", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A Simple Registration request can only generate a response on the receiving end..
+ /// </summary>
+ internal static string CallDeserializeBeforeCreateResponse {
+ get {
+ return ResourceManager.GetString("CallDeserializeBeforeCreateResponse", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The openid.claimed_id and openid.identity parameters must both be present or both be absent..
+ /// </summary>
+ internal static string ClaimedIdAndLocalIdMustBothPresentOrAbsent {
+ get {
+ return ResourceManager.GetString("ClaimedIdAndLocalIdMustBothPresentOrAbsent", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The ClaimedIdentifier property cannot be set when IsDelegatedIdentifier is true to avoid breaking OpenID URL delegation..
+ /// </summary>
+ internal static string ClaimedIdentifierCannotBeSetOnDelegatedAuthentication {
+ get {
+ return ResourceManager.GetString("ClaimedIdentifierCannotBeSetOnDelegatedAuthentication", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to This OpenID exploits features that this relying party cannot reliably verify. Please try logging in with a human-readable OpenID or from a different OpenID Provider..
+ /// </summary>
+ internal static string ClaimedIdentifierDefiesDotNetNormalization {
+ get {
+ return ResourceManager.GetString("ClaimedIdentifierDefiesDotNetNormalization", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The ClaimedIdentifier property must be set first..
+ /// </summary>
+ internal static string ClaimedIdentifierMustBeSetFirst {
+ get {
+ return ResourceManager.GetString("ClaimedIdentifierMustBeSetFirst", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to An extension with this property name (&apos;{0}&apos;) has already been registered..
+ /// </summary>
+ internal static string ClientScriptExtensionPropertyNameCollision {
+ get {
+ return ResourceManager.GetString("ClientScriptExtensionPropertyNameCollision", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The extension &apos;{0}&apos; has already been registered..
+ /// </summary>
+ internal static string ClientScriptExtensionTypeCollision {
+ get {
+ return ResourceManager.GetString("ClientScriptExtensionTypeCollision", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to An authentication request has already been created using CreateRequest()..
+ /// </summary>
+ internal static string CreateRequestAlreadyCalled {
+ get {
+ return ResourceManager.GetString("CreateRequestAlreadyCalled", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Only OpenIDs issued directly by their OpenID Provider are allowed here..
+ /// </summary>
+ internal static string DelegatingIdentifiersNotAllowed {
+ get {
+ return ResourceManager.GetString("DelegatingIdentifiersNotAllowed", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The associate request instance must be a Diffie-Hellman instance..
+ /// </summary>
+ internal static string DiffieHellmanAssociationRequired {
+ get {
+ return ResourceManager.GetString("DiffieHellmanAssociationRequired", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The following properties must be set before the Diffie-Hellman algorithm can generate a public key: {0}.
+ /// </summary>
+ internal static string DiffieHellmanRequiredPropertiesNotSet {
+ get {
+ return ResourceManager.GetString("DiffieHellmanRequiredPropertiesNotSet", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to URI is not SSL yet requireSslDiscovery is set to true..
+ /// </summary>
+ internal static string ExplicitHttpUriSuppliedWithSslRequirement {
+ get {
+ return ResourceManager.GetString("ExplicitHttpUriSuppliedWithSslRequirement", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to An extension sharing namespace &apos;{0}&apos; has already been added. Only one extension per namespace is allowed in a given request..
+ /// </summary>
+ internal static string ExtensionAlreadyAddedWithSameTypeURI {
+ get {
+ return ResourceManager.GetString("ExtensionAlreadyAddedWithSameTypeURI", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Cannot lookup extension support on a rehydrated ServiceEndpoint..
+ /// </summary>
+ internal static string ExtensionLookupSupportUnavailable {
+ get {
+ return ResourceManager.GetString("ExtensionLookupSupportUnavailable", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Fragment segments do not apply to XRI identifiers..
+ /// </summary>
+ internal static string FragmentNotAllowedOnXRIs {
+ get {
+ return ResourceManager.GetString("FragmentNotAllowedOnXRIs", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The HTML head tag must include runat=&quot;server&quot;..
+ /// </summary>
+ internal static string HeadTagMustIncludeRunatServer {
+ get {
+ return ResourceManager.GetString("HeadTagMustIncludeRunatServer", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to ClaimedIdentifier and LocalIdentifier must be the same when IsIdentifierSelect is true..
+ /// </summary>
+ internal static string IdentifierSelectRequiresMatchingIdentifiers {
+ get {
+ return ResourceManager.GetString("IdentifierSelectRequiresMatchingIdentifiers", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The openid.identity and openid.claimed_id parameters must either be both present or both absent from the message..
+ /// </summary>
+ internal static string IdentityAndClaimedIdentifierMustBeBothPresentOrAbsent {
+ get {
+ return ResourceManager.GetString("IdentityAndClaimedIdentifierMustBeBothPresentOrAbsent", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The Provider requested association type &apos;{0}&apos; and session type &apos;{1}&apos;, which are not compatible with each other..
+ /// </summary>
+ internal static string IncompatibleAssociationAndSessionTypes {
+ get {
+ return ResourceManager.GetString("IncompatibleAssociationAndSessionTypes", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to {0} (Contact: {1}, Reference: {2}).
+ /// </summary>
+ internal static string IndirectErrorFormattedMessage {
+ get {
+ return ResourceManager.GetString("IndirectErrorFormattedMessage", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Cannot encode &apos;{0}&apos; because it contains an illegal character for Key-Value Form encoding. (line {1}: &apos;{2}&apos;).
+ /// </summary>
+ internal static string InvalidCharacterInKeyValueFormInput {
+ get {
+ return ResourceManager.GetString("InvalidCharacterInKeyValueFormInput", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Invalid XmlDSig signature on XRDS document..
+ /// </summary>
+ internal static string InvalidDSig {
+ get {
+ return ResourceManager.GetString("InvalidDSig", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Cannot decode Key-Value Form because a line was found without a &apos;{0}&apos; character. (line {1}: &apos;{2}&apos;).
+ /// </summary>
+ internal static string InvalidKeyValueFormCharacterMissing {
+ get {
+ return ResourceManager.GetString("InvalidKeyValueFormCharacterMissing", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The scheme must be http or https but was &apos;{0}&apos;..
+ /// </summary>
+ internal static string InvalidScheme {
+ get {
+ return ResourceManager.GetString("InvalidScheme", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The value &apos;{0}&apos; is not a valid URI..
+ /// </summary>
+ internal static string InvalidUri {
+ get {
+ return ResourceManager.GetString("InvalidUri", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Not a recognized XRI format..
+ /// </summary>
+ internal static string InvalidXri {
+ get {
+ return ResourceManager.GetString("InvalidXri", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The OpenID Provider issued an assertion for an Identifier whose discovery information did not match.
+ ///Assertion endpoint info:
+ ///{0}
+ ///Discovered endpoint info:
+ ///{1}.
+ /// </summary>
+ internal static string IssuedAssertionFailsIdentifierDiscovery {
+ get {
+ return ResourceManager.GetString("IssuedAssertionFailsIdentifierDiscovery", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The list of keys do not match the provided dictionary..
+ /// </summary>
+ internal static string KeysListAndDictionaryDoNotMatch {
+ get {
+ return ResourceManager.GetString("KeysListAndDictionaryDoNotMatch", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The &apos;{0}&apos; and &apos;{1}&apos; parameters must both be or not be &apos;{2}&apos;..
+ /// </summary>
+ internal static string MatchingArgumentsExpected {
+ get {
+ return ResourceManager.GetString("MatchingArgumentsExpected", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The maximum time allowed to complete authentication has been exceeded. Please try again..
+ /// </summary>
+ internal static string MaximumAuthenticationTimeExpired {
+ get {
+ return ResourceManager.GetString("MaximumAuthenticationTimeExpired", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to X.509 signing certificate issued to {0}, but a certificate for {1} was expected..
+ /// </summary>
+ internal static string MisdirectedSigningCertificate {
+ get {
+ return ResourceManager.GetString("MisdirectedSigningCertificate", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Missing {0} element..
+ /// </summary>
+ internal static string MissingElement {
+ get {
+ return ResourceManager.GetString("MissingElement", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to No recognized association type matches the requested length of {0}..
+ /// </summary>
+ internal static string NoAssociationTypeFoundByLength {
+ get {
+ return ResourceManager.GetString("NoAssociationTypeFoundByLength", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to No recognized association type matches the requested name of &apos;{0}&apos;..
+ /// </summary>
+ internal static string NoAssociationTypeFoundByName {
+ get {
+ return ResourceManager.GetString("NoAssociationTypeFoundByName", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Unless using transport layer encryption, &quot;no-encryption&quot; MUST NOT be used..
+ /// </summary>
+ internal static string NoEncryptionSessionRequiresHttps {
+ get {
+ return ResourceManager.GetString("NoEncryptionSessionRequiresHttps", resourceCulture);
+ }
+ }
+
+ /// <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 {
+ get {
+ return ResourceManager.GetString("NoRelyingPartyEndpointDiscovered", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Diffie-Hellman session type &apos;{0}&apos; not found for OpenID {1}..
+ /// </summary>
+ internal static string NoSessionTypeFound {
+ get {
+ return ResourceManager.GetString("NoSessionTypeFound", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to This operation is not supported by serialized authentication responses. Try this operation from the LoggedIn event handler..
+ /// </summary>
+ internal static string NotSupportedByAuthenticationSnapshot {
+ get {
+ return ResourceManager.GetString("NotSupportedByAuthenticationSnapshot", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to No OpenID endpoint found..
+ /// </summary>
+ internal static string OpenIdEndpointNotFound {
+ get {
+ return ResourceManager.GetString("OpenIdEndpointNotFound", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to No OpenID url is provided..
+ /// </summary>
+ internal static string OpenIdTextBoxEmpty {
+ get {
+ return ResourceManager.GetString("OpenIdTextBoxEmpty", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to This operation is only allowed when IAuthenticationResponse.State == AuthenticationStatus.SetupRequired..
+ /// </summary>
+ internal static string OperationOnlyValidForSetupRequiredState {
+ get {
+ return ResourceManager.GetString("OperationOnlyValidForSetupRequiredState", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to OpenID popup window or iframe did not recognize an OpenID response in the request..
+ /// </summary>
+ internal static string PopupRedirectMissingResponse {
+ get {
+ return ResourceManager.GetString("PopupRedirectMissingResponse", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to An positive OpenID assertion was received from OP endpoint {0} and was rejected based on this site&apos;s security settings..
+ /// </summary>
+ internal static string PositiveAssertionFromNonQualifiedProvider {
+ get {
+ return ResourceManager.GetString("PositiveAssertionFromNonQualifiedProvider", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Unable to find the signing secret by the handle &apos;{0}&apos;..
+ /// </summary>
+ internal static string PrivateRPSecretNotFound {
+ get {
+ return ResourceManager.GetString("PrivateRPSecretNotFound", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The {0} property must be set first..
+ /// </summary>
+ internal static string PropertyNotSet {
+ get {
+ return ResourceManager.GetString("PropertyNotSet", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to This property value is not supported by this control..
+ /// </summary>
+ internal static string PropertyValueNotSupported {
+ get {
+ return ResourceManager.GetString("PropertyValueNotSupported", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Unable to determine the version of the OpenID protocol implemented by the Provider at endpoint &apos;{0}&apos;..
+ /// </summary>
+ internal static string ProviderVersionUnrecognized {
+ get {
+ return ResourceManager.GetString("ProviderVersionUnrecognized", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to An HTTP request to the realm URL ({0}) resulted in a redirect, which is not allowed during relying party discovery..
+ /// </summary>
+ internal static string RealmCausedRedirectUponDiscovery {
+ get {
+ return ResourceManager.GetString("RealmCausedRedirectUponDiscovery", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Sorry. This site only accepts OpenIDs that are HTTPS-secured, but {0} is not a secure Identifier..
+ /// </summary>
+ internal static string RequireSslNotSatisfiedByAssertedClaimedId {
+ get {
+ return ResourceManager.GetString("RequireSslNotSatisfiedByAssertedClaimedId", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The response is not ready. Use IsResponseReady to check whether a response is ready first..
+ /// </summary>
+ internal static string ResponseNotReady {
+ get {
+ return ResourceManager.GetString("ResponseNotReady", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to return_to &apos;{0}&apos; not under realm &apos;{1}&apos;..
+ /// </summary>
+ internal static string ReturnToNotUnderRealm {
+ get {
+ return ResourceManager.GetString("ReturnToNotUnderRealm", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The {0} parameter ({1}) does not match the actual URL ({2}) the request was made with..
+ /// </summary>
+ internal static string ReturnToParamDoesNotMatchRequestUrl {
+ get {
+ return ResourceManager.GetString("ReturnToParamDoesNotMatchRequestUrl", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The ReturnTo property must not be null to support this operation..
+ /// </summary>
+ internal static string ReturnToRequiredForOperation {
+ get {
+ return ResourceManager.GetString("ReturnToRequiredForOperation", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The openid.return_to parameter is required in the request message in order to construct a response, but that parameter was missing..
+ /// </summary>
+ internal static string ReturnToRequiredForResponse {
+ get {
+ return ResourceManager.GetString("ReturnToRequiredForResponse", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The following parameter(s) are not included in the signature but must be: {0}.
+ /// </summary>
+ internal static string SignatureDoesNotIncludeMandatoryParts {
+ get {
+ return ResourceManager.GetString("SignatureDoesNotIncludeMandatoryParts", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Invalid birthdate value. Must be in the form yyyy-MM-dd..
+ /// </summary>
+ internal static string SregInvalidBirthdate {
+ get {
+ return ResourceManager.GetString("SregInvalidBirthdate", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The type must implement {0}..
+ /// </summary>
+ internal static string TypeMustImplementX {
+ get {
+ return ResourceManager.GetString("TypeMustImplementX", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The property {0} had unexpected value {1}..
+ /// </summary>
+ internal static string UnexpectedEnumPropertyValue {
+ get {
+ return ResourceManager.GetString("UnexpectedEnumPropertyValue", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Unexpected HTTP status code {0} {1} received in direct response..
+ /// </summary>
+ internal static string UnexpectedHttpStatusCode {
+ get {
+ return ResourceManager.GetString("UnexpectedHttpStatusCode", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to An unsolicited assertion cannot be sent for the claimed identifier {0} because this is not an authorized Provider for that identifier..
+ /// </summary>
+ internal static string UnsolicitedAssertionForUnrelatedClaimedIdentifier {
+ get {
+ return ResourceManager.GetString("UnsolicitedAssertionForUnrelatedClaimedIdentifier", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Rejecting unsolicited assertions requires a nonce store and an association store..
+ /// </summary>
+ internal static string UnsolicitedAssertionRejectionRequiresNonceStore {
+ get {
+ return ResourceManager.GetString("UnsolicitedAssertionRejectionRequiresNonceStore", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Unsolicited assertions are not allowed at this relying party..
+ /// </summary>
+ internal static string UnsolicitedAssertionsNotAllowed {
+ get {
+ return ResourceManager.GetString("UnsolicitedAssertionsNotAllowed", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Unsolicited assertions are not allowed from 1.0 OpenID Providers..
+ /// </summary>
+ internal static string UnsolicitedAssertionsNotAllowedFrom1xOPs {
+ get {
+ return ResourceManager.GetString("UnsolicitedAssertionsNotAllowedFrom1xOPs", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Providing a DateTime whose Kind is Unspecified is not allowed..
+ /// </summary>
+ internal static string UnspecifiedDateTimeKindNotAllowed {
+ get {
+ return ResourceManager.GetString("UnspecifiedDateTimeKindNotAllowed", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Unrecognized or missing canonicalization method..
+ /// </summary>
+ internal static string UnsupportedCanonicalizationMethod {
+ get {
+ return ResourceManager.GetString("UnsupportedCanonicalizationMethod", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to This feature is unavailable due to an unrecognized channel configuration..
+ /// </summary>
+ internal static string UnsupportedChannelConfiguration {
+ get {
+ return ResourceManager.GetString("UnsupportedChannelConfiguration", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Unrecognized or missing signature method..
+ /// </summary>
+ internal static string UnsupportedSignatureMethod {
+ get {
+ return ResourceManager.GetString("UnsupportedSignatureMethod", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The openid.user_setup_url parameter is required when sending negative assertion messages in response to immediate mode requests..
+ /// </summary>
+ internal static string UserSetupUrlRequiredInImmediateNegativeResponse {
+ get {
+ return ResourceManager.GetString("UserSetupUrlRequiredInImmediateNegativeResponse", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The X.509 certificate used to sign this document is not trusted..
+ /// </summary>
+ internal static string X509CertificateNotTrusted {
+ get {
+ return ResourceManager.GetString("X509CertificateNotTrusted", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to XRI support has been disabled at this site..
+ /// </summary>
+ internal static string XriResolutionDisabled {
+ get {
+ return ResourceManager.GetString("XriResolutionDisabled", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to XRI resolution failed..
+ /// </summary>
+ internal static string XriResolutionFailed {
+ get {
+ return ResourceManager.GetString("XriResolutionFailed", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/OpenIdStrings.resx b/src/DotNetOpenAuth.OpenId/OpenId/OpenIdStrings.resx
new file mode 100644
index 0000000..b700d76
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/OpenIdStrings.resx
@@ -0,0 +1,376 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="AssociationSecretAndTypeLengthMismatch" xml:space="preserve">
+ <value>The length of the shared secret ({0}) does not match the length required by the association type ('{1}').</value>
+ </data>
+ <data name="AssociationSecretHashLengthMismatch" xml:space="preserve">
+ <value>The length of the encrypted shared secret ({0}) does not match the length of the hashing algorithm ({1}).</value>
+ </data>
+ <data name="AssociationStoreRequiresNonceStore" xml:space="preserve">
+ <value>If an association store is given, a nonce store must also be provided.</value>
+ </data>
+ <data name="BadAssociationPrivateData" xml:space="preserve">
+ <value>The private data supplied does not meet the requirements of any known Association type. Its length may be too short, or it may have been corrupted.</value>
+ </data>
+ <data name="CallbackArgumentsRequireSecretStore" xml:space="preserve">
+ <value>Callback arguments are only supported when a {0} is provided to the {1}.</value>
+ </data>
+ <data name="CallDeserializeBeforeCreateResponse" xml:space="preserve">
+ <value>A Simple Registration request can only generate a response on the receiving end.</value>
+ </data>
+ <data name="ClaimedIdAndLocalIdMustBothPresentOrAbsent" xml:space="preserve">
+ <value>The openid.claimed_id and openid.identity parameters must both be present or both be absent.</value>
+ </data>
+ <data name="ClaimedIdentifierCannotBeSetOnDelegatedAuthentication" xml:space="preserve">
+ <value>The ClaimedIdentifier property cannot be set when IsDelegatedIdentifier is true to avoid breaking OpenID URL delegation.</value>
+ </data>
+ <data name="ClaimedIdentifierMustBeSetFirst" xml:space="preserve">
+ <value>The ClaimedIdentifier property must be set first.</value>
+ </data>
+ <data name="DiffieHellmanRequiredPropertiesNotSet" xml:space="preserve">
+ <value>The following properties must be set before the Diffie-Hellman algorithm can generate a public key: {0}</value>
+ </data>
+ <data name="ExplicitHttpUriSuppliedWithSslRequirement" xml:space="preserve">
+ <value>URI is not SSL yet requireSslDiscovery is set to true.</value>
+ </data>
+ <data name="ExtensionAlreadyAddedWithSameTypeURI" xml:space="preserve">
+ <value>An extension sharing namespace '{0}' has already been added. Only one extension per namespace is allowed in a given request.</value>
+ </data>
+ <data name="ExtensionLookupSupportUnavailable" xml:space="preserve">
+ <value>Cannot lookup extension support on a rehydrated ServiceEndpoint.</value>
+ </data>
+ <data name="FragmentNotAllowedOnXRIs" xml:space="preserve">
+ <value>Fragment segments do not apply to XRI identifiers.</value>
+ </data>
+ <data name="IdentifierSelectRequiresMatchingIdentifiers" xml:space="preserve">
+ <value>ClaimedIdentifier and LocalIdentifier must be the same when IsIdentifierSelect is true.</value>
+ </data>
+ <data name="IndirectErrorFormattedMessage" xml:space="preserve">
+ <value>{0} (Contact: {1}, Reference: {2})</value>
+ </data>
+ <data name="InvalidCharacterInKeyValueFormInput" xml:space="preserve">
+ <value>Cannot encode '{0}' because it contains an illegal character for Key-Value Form encoding. (line {1}: '{2}')</value>
+ </data>
+ <data name="InvalidKeyValueFormCharacterMissing" xml:space="preserve">
+ <value>Cannot decode Key-Value Form because a line was found without a '{0}' character. (line {1}: '{2}')</value>
+ </data>
+ <data name="InvalidScheme" xml:space="preserve">
+ <value>The scheme must be http or https but was '{0}'.</value>
+ </data>
+ <data name="InvalidUri" xml:space="preserve">
+ <value>The value '{0}' is not a valid URI.</value>
+ </data>
+ <data name="InvalidXri" xml:space="preserve">
+ <value>Not a recognized XRI format.</value>
+ </data>
+ <data name="IssuedAssertionFailsIdentifierDiscovery" xml:space="preserve">
+ <value>The OpenID Provider issued an assertion for an Identifier whose discovery information did not match.
+Assertion endpoint info:
+{0}
+Discovered endpoint info:
+{1}</value>
+ </data>
+ <data name="KeysListAndDictionaryDoNotMatch" xml:space="preserve">
+ <value>The list of keys do not match the provided dictionary.</value>
+ </data>
+ <data name="MatchingArgumentsExpected" xml:space="preserve">
+ <value>The '{0}' and '{1}' parameters must both be or not be '{2}'.</value>
+ </data>
+ <data name="NoAssociationTypeFoundByLength" xml:space="preserve">
+ <value>No recognized association type matches the requested length of {0}.</value>
+ </data>
+ <data name="NoAssociationTypeFoundByName" xml:space="preserve">
+ <value>No recognized association type matches the requested name of '{0}'.</value>
+ </data>
+ <data name="NoEncryptionSessionRequiresHttps" xml:space="preserve">
+ <value>Unless using transport layer encryption, "no-encryption" MUST NOT be used.</value>
+ </data>
+ <data name="NoSessionTypeFound" xml:space="preserve">
+ <value>Diffie-Hellman session type '{0}' not found for OpenID {1}.</value>
+ </data>
+ <data name="OpenIdEndpointNotFound" xml:space="preserve">
+ <value>No OpenID endpoint found.</value>
+ </data>
+ <data name="OperationOnlyValidForSetupRequiredState" xml:space="preserve">
+ <value>This operation is only allowed when IAuthenticationResponse.State == AuthenticationStatus.SetupRequired.</value>
+ </data>
+ <data name="ProviderVersionUnrecognized" xml:space="preserve">
+ <value>Unable to determine the version of the OpenID protocol implemented by the Provider at endpoint '{0}'.</value>
+ </data>
+ <data name="RealmCausedRedirectUponDiscovery" xml:space="preserve">
+ <value>An HTTP request to the realm URL ({0}) resulted in a redirect, which is not allowed during relying party discovery.</value>
+ </data>
+ <data name="ReturnToNotUnderRealm" xml:space="preserve">
+ <value>return_to '{0}' not under realm '{1}'.</value>
+ </data>
+ <data name="ReturnToParamDoesNotMatchRequestUrl" xml:space="preserve">
+ <value>The {0} parameter ({1}) does not match the actual URL ({2}) the request was made with.</value>
+ </data>
+ <data name="ReturnToRequiredForResponse" xml:space="preserve">
+ <value>The openid.return_to parameter is required in the request message in order to construct a response, but that parameter was missing.</value>
+ </data>
+ <data name="SignatureDoesNotIncludeMandatoryParts" xml:space="preserve">
+ <value>The following parameter(s) are not included in the signature but must be: {0}</value>
+ </data>
+ <data name="SregInvalidBirthdate" xml:space="preserve">
+ <value>Invalid birthdate value. Must be in the form yyyy-MM-dd.</value>
+ </data>
+ <data name="TypeMustImplementX" xml:space="preserve">
+ <value>The type must implement {0}.</value>
+ </data>
+ <data name="UnsolicitedAssertionsNotAllowedFrom1xOPs" xml:space="preserve">
+ <value>Unsolicited assertions are not allowed from 1.0 OpenID Providers.</value>
+ </data>
+ <data name="UserSetupUrlRequiredInImmediateNegativeResponse" xml:space="preserve">
+ <value>The openid.user_setup_url parameter is required when sending negative assertion messages in response to immediate mode requests.</value>
+ </data>
+ <data name="XriResolutionFailed" xml:space="preserve">
+ <value>XRI resolution failed.</value>
+ </data>
+ <data name="AttributeAlreadyAdded" xml:space="preserve">
+ <value>An attribute with type URI '{0}' has already been added.</value>
+ </data>
+ <data name="AttributeTooManyValues" xml:space="preserve">
+ <value>Only {0} values for attribute '{1}' were requested, but {2} were supplied.</value>
+ </data>
+ <data name="UnspecifiedDateTimeKindNotAllowed" xml:space="preserve">
+ <value>Providing a DateTime whose Kind is Unspecified is not allowed.</value>
+ </data>
+ <data name="AssociationOrSessionTypeUnrecognizedOrNotSupported" xml:space="preserve">
+ <value>The requested association type '{0}' with session type '{1}' is unrecognized or not supported by this Provider due to security requirements.</value>
+ </data>
+ <data name="IncompatibleAssociationAndSessionTypes" xml:space="preserve">
+ <value>The Provider requested association type '{0}' and session type '{1}', which are not compatible with each other.</value>
+ </data>
+ <data name="CreateRequestAlreadyCalled" xml:space="preserve">
+ <value>An authentication request has already been created using CreateRequest().</value>
+ </data>
+ <data name="OpenIdTextBoxEmpty" xml:space="preserve">
+ <value>No OpenID url is provided.</value>
+ </data>
+ <data name="ClientScriptExtensionPropertyNameCollision" xml:space="preserve">
+ <value>An extension with this property name ('{0}') has already been registered.</value>
+ </data>
+ <data name="ClientScriptExtensionTypeCollision" xml:space="preserve">
+ <value>The extension '{0}' has already been registered.</value>
+ </data>
+ <data name="UnexpectedHttpStatusCode" xml:space="preserve">
+ <value>Unexpected HTTP status code {0} {1} received in direct response.</value>
+ </data>
+ <data name="NotSupportedByAuthenticationSnapshot" xml:space="preserve">
+ <value>This operation is not supported by serialized authentication responses. Try this operation from the LoggedIn event handler.</value>
+ </data>
+ <data name="NoRelyingPartyEndpointDiscovered" xml:space="preserve">
+ <value>No XRDS document containing OpenID relying party endpoint information could be found at {0}.</value>
+ </data>
+ <data name="AbsoluteUriRequired" xml:space="preserve">
+ <value>An absolute URI is required for this value.</value>
+ </data>
+ <data name="UnsolicitedAssertionForUnrelatedClaimedIdentifier" xml:space="preserve">
+ <value>An unsolicited assertion cannot be sent for the claimed identifier {0} because this is not an authorized Provider for that identifier.</value>
+ </data>
+ <data name="MaximumAuthenticationTimeExpired" xml:space="preserve">
+ <value>The maximum time allowed to complete authentication has been exceeded. Please try again.</value>
+ </data>
+ <data name="PrivateRPSecretNotFound" xml:space="preserve">
+ <value>Unable to find the signing secret by the handle '{0}'.</value>
+ </data>
+ <data name="ResponseNotReady" xml:space="preserve">
+ <value>The response is not ready. Use IsResponseReady to check whether a response is ready first.</value>
+ </data>
+ <data name="UnsupportedChannelConfiguration" xml:space="preserve">
+ <value>This feature is unavailable due to an unrecognized channel configuration.</value>
+ </data>
+ <data name="IdentityAndClaimedIdentifierMustBeBothPresentOrAbsent" xml:space="preserve">
+ <value>The openid.identity and openid.claimed_id parameters must either be both present or both absent from the message.</value>
+ </data>
+ <data name="ReturnToRequiredForOperation" xml:space="preserve">
+ <value>The ReturnTo property must not be null to support this operation.</value>
+ </data>
+ <data name="UnsolicitedAssertionRejectionRequiresNonceStore" xml:space="preserve">
+ <value>Rejecting unsolicited assertions requires a nonce store and an association store.</value>
+ </data>
+ <data name="UnsolicitedAssertionsNotAllowed" xml:space="preserve">
+ <value>Unsolicited assertions are not allowed at this relying party.</value>
+ </data>
+ <data name="DelegatingIdentifiersNotAllowed" xml:space="preserve">
+ <value>Only OpenIDs issued directly by their OpenID Provider are allowed here.</value>
+ </data>
+ <data name="XriResolutionDisabled" xml:space="preserve">
+ <value>XRI support has been disabled at this site.</value>
+ </data>
+ <data name="AssociationStoreRequired" xml:space="preserve">
+ <value>No association store has been given but is required for the current configuration.</value>
+ </data>
+ <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>
+ <data name="PropertyValueNotSupported" xml:space="preserve">
+ <value>This property value is not supported by this control.</value>
+ </data>
+ <data name="ArgumentIsPpidIdentifier" xml:space="preserve">
+ <value>This is already a PPID Identifier.</value>
+ </data>
+ <data name="RequireSslNotSatisfiedByAssertedClaimedId" xml:space="preserve">
+ <value>Sorry. This site only accepts OpenIDs that are HTTPS-secured, but {0} is not a secure Identifier.</value>
+ </data>
+ <data name="BadExtension" xml:space="preserve">
+ <value>The {0} extension failed to deserialize and will be skipped. {1}</value>
+ </data>
+ <data name="PositiveAssertionFromNonQualifiedProvider" xml:space="preserve">
+ <value>An positive OpenID assertion was received from OP endpoint {0} and was rejected based on this site's security settings.</value>
+ </data>
+ <data name="HeadTagMustIncludeRunatServer" xml:space="preserve">
+ <value>The HTML head tag must include runat="server".</value>
+ </data>
+ <data name="PropertyNotSet" xml:space="preserve">
+ <value>The {0} property must be set first.</value>
+ </data>
+ <data name="X509CertificateNotTrusted" xml:space="preserve">
+ <value>The X.509 certificate used to sign this document is not trusted.</value>
+ </data>
+ <data name="ClaimedIdentifierDefiesDotNetNormalization" xml:space="preserve">
+ <value>This OpenID exploits features that this relying party cannot reliably verify. Please try logging in with a human-readable OpenID or from a different OpenID Provider.</value>
+ </data>
+ <data name="MissingElement" xml:space="preserve">
+ <value>Missing {0} element.</value>
+ </data>
+ <data name="DiffieHellmanAssociationRequired" xml:space="preserve">
+ <value>The associate request instance must be a Diffie-Hellman instance.</value>
+ </data>
+ <data name="InvalidDSig" xml:space="preserve">
+ <value>Invalid XmlDSig signature on XRDS document.</value>
+ </data>
+ <data name="MisdirectedSigningCertificate" xml:space="preserve">
+ <value>X.509 signing certificate issued to {0}, but a certificate for {1} was expected.</value>
+ </data>
+ <data name="PopupRedirectMissingResponse" xml:space="preserve">
+ <value>OpenID popup window or iframe did not recognize an OpenID response in the request.</value>
+ </data>
+ <data name="UnsupportedCanonicalizationMethod" xml:space="preserve">
+ <value>Unrecognized or missing canonicalization method.</value>
+ </data>
+ <data name="UnsupportedSignatureMethod" xml:space="preserve">
+ <value>Unrecognized or missing signature method.</value>
+ </data>
+</root> \ No newline at end of file
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/OpenIdStrings.sr.resx b/src/DotNetOpenAuth.OpenId/OpenId/OpenIdStrings.sr.resx
new file mode 100644
index 0000000..0df62c0
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/OpenIdStrings.sr.resx
@@ -0,0 +1,340 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="AssociationSecretAndTypeLengthMismatch" xml:space="preserve">
+ <value>Dužina deljene tajne ({0}) ne slaže se sa dužinom zahtevanom od povezujućeg tipa ('{1}').</value>
+ </data>
+ <data name="AssociationSecretHashLengthMismatch" xml:space="preserve">
+ <value>Dužina šifrovane deljene tajne ({0}) ne slaže se sa dužinom hashing algoritma ({1}).</value>
+ </data>
+ <data name="AssociationStoreRequiresNonceStore" xml:space="preserve">
+ <value>Ako je dato povezujuće skladište, jedinstveni identifikator skladišta takođe mora biti prisutan.</value>
+ </data>
+ <data name="BadAssociationPrivateData" xml:space="preserve">
+ <value>Dostavljeni privatni podaci ne slažu se ni sa jednim poznatim Association tipom. Dužina im je možda prekratka ili su podaci neispravni.</value>
+ </data>
+ <data name="CallbackArgumentsRequireSecretStore" xml:space="preserve">
+ <value>Callback argumenti su podržani jedino kada je {0} dustupan za {1}.</value>
+ </data>
+ <data name="CallDeserializeBeforeCreateResponse" xml:space="preserve">
+ <value>Simple Registration zahtev može generisati odgovor jedino na prijemnoj strani.</value>
+ </data>
+ <data name="ClaimedIdAndLocalIdMustBothPresentOrAbsent" xml:space="preserve">
+ <value>openid.claimed_id i openid.identity parametri moraju istovremeno biti prisutni ili nedostajati.</value>
+ </data>
+ <data name="ClaimedIdentifierCannotBeSetOnDelegatedAuthentication" xml:space="preserve">
+ <value>ClaimedIdentifier svojstvo ne može se podesiti kada IsDelegatedIdentifier ima vrednost true da bi se izbeglo narušavanje OpenID URL delegiranja.</value>
+ </data>
+ <data name="ClaimedIdentifierMustBeSetFirst" xml:space="preserve">
+ <value>ClaimedIdentifier svojstvo se najpre mora podesiti.</value>
+ </data>
+ <data name="DiffieHellmanRequiredPropertiesNotSet" xml:space="preserve">
+ <value>Sledeća svojstva moraju biti podešena pre nego što Diffie-Hellmanov algoritam može generisati javni ključ: {0}</value>
+ </data>
+ <data name="ExplicitHttpUriSuppliedWithSslRequirement" xml:space="preserve">
+ <value>URI nije SSL a svojstvo requireSslDiscovery je podešeno na true.</value>
+ </data>
+ <data name="ExtensionAlreadyAddedWithSameTypeURI" xml:space="preserve">
+ <value>Prostor deljenja proširenja '{0}' je već dodat. Samo jedno proširenje po prostoru je dozvoljeno u datom zahtevu.</value>
+ </data>
+ <data name="ExtensionLookupSupportUnavailable" xml:space="preserve">
+ <value>Ne može se tražiti podrška za proširenja na rehidriranom ServiceEndpoint.</value>
+ </data>
+ <data name="FragmentNotAllowedOnXRIs" xml:space="preserve">
+ <value>Segmenti fragmenta se ne primenjuju na XRI identifikatore.</value>
+ </data>
+ <data name="IdentifierSelectRequiresMatchingIdentifiers" xml:space="preserve">
+ <value>ClaimedIdentifier i LocalIdentifier moraju biti isti kada IsIdentifierSelect ima vrednost true.</value>
+ </data>
+ <data name="IndirectErrorFormattedMessage" xml:space="preserve">
+ <value>{0} (Kontakt: {1}, Referenca: {2})</value>
+ </data>
+ <data name="InvalidCharacterInKeyValueFormInput" xml:space="preserve">
+ <value>Ne može se enkodovati '{0}' jer sadrži nevalidan znak za Key-Value Form enkodiranje. (linija {1}: '{2}')</value>
+ </data>
+ <data name="InvalidKeyValueFormCharacterMissing" xml:space="preserve">
+ <value>Ne može se dekodovati Key-Value Form jer je pronađena linija bez '{0}' znaka. (linija {1}: '{2}')</value>
+ </data>
+ <data name="InvalidScheme" xml:space="preserve">
+ <value>Šema mora biti http ili https a bila je '{0}'.</value>
+ </data>
+ <data name="InvalidUri" xml:space="preserve">
+ <value>Vrednost '{0}' nije validan URI.</value>
+ </data>
+ <data name="InvalidXri" xml:space="preserve">
+ <value>Nije prepoznat XRI format: '{0}'.</value>
+ </data>
+ <data name="IssuedAssertionFailsIdentifierDiscovery" xml:space="preserve">
+ <value>OpenID Provider dao je iskaz za Identifier čije se informacije o pronalaženju ne slažu.
+Informacije o krajnoj tački iskaza:
+{0}
+Informacije o otkrivenoj krajnjoj tački:
+{1}</value>
+ </data>
+ <data name="KeysListAndDictionaryDoNotMatch" xml:space="preserve">
+ <value>Lista ključeva se ne slaže sa ponuđenim rečnikom.</value>
+ </data>
+ <data name="MatchingArgumentsExpected" xml:space="preserve">
+ <value>Parametri '{0}' i '{1}' moraju oba biti ili ne smeju oba biti '{2}'.</value>
+ </data>
+ <data name="NoAssociationTypeFoundByLength" xml:space="preserve">
+ <value>Ni jedan prepoznati tip asociranja se ne uklapa sa zahtevanom dužinom {0}.</value>
+ </data>
+ <data name="NoAssociationTypeFoundByName" xml:space="preserve">
+ <value>Ni jedan prepoznati tip asociranja se ne uklapa sa zahtevanim imenom '{0}'.</value>
+ </data>
+ <data name="NoEncryptionSessionRequiresHttps" xml:space="preserve">
+ <value>Osim ako se ne koristi enkripcija transportnog sloja, "no-encryption" NE SME biti korišćen.</value>
+ </data>
+ <data name="NoSessionTypeFound" xml:space="preserve">
+ <value>Diffie-Hellman sesija tipa '{0}' nije pronađena za OpenID {1}.</value>
+ </data>
+ <data name="OpenIdEndpointNotFound" xml:space="preserve">
+ <value>Nijedna OpenID krajnja tačka nije pronađena.</value>
+ </data>
+ <data name="OperationOnlyValidForSetupRequiredState" xml:space="preserve">
+ <value>Ova operacija je jedino dozvoljena kada je IAuthenticationResponse.State == AuthenticationStatus.SetupRequired.</value>
+ </data>
+ <data name="ProviderVersionUnrecognized" xml:space="preserve">
+ <value>Nije moguće utvrditi verziju OpenID protokola implementiranog od strane Provider-a na krajnjoj tački '{0}'.</value>
+ </data>
+ <data name="RealmCausedRedirectUponDiscovery" xml:space="preserve">
+ <value>HTTP zahtev ka URL-u domena ({0}) rezultovao je redirekcijom, koja nije dozvoljena u togu pronalaženja Relying Party.</value>
+ </data>
+ <data name="ReturnToNotUnderRealm" xml:space="preserve">
+ <value>return_to '{0}' nije unutar domena '{1}'.</value>
+ </data>
+ <data name="ReturnToParamDoesNotMatchRequestUrl" xml:space="preserve">
+ <value>{0} parametar ({1}) se ne slaže sa trenutnim URL ({2}) sa kojim je zahtev napravljen.</value>
+ </data>
+ <data name="ReturnToRequiredForResponse" xml:space="preserve">
+ <value>openid.return_to parametar je neophodan u poruci zahteva da bi se konstruisao odgovor, ali ovaj parametar nedostaje.</value>
+ </data>
+ <data name="SignatureDoesNotIncludeMandatoryParts" xml:space="preserve">
+ <value>Sledeći parametri nisu uključeni u potpis a moraju da budu: {0}</value>
+ </data>
+ <data name="SregInvalidBirthdate" xml:space="preserve">
+ <value>Neispravna vrednost za datum rođenja. Mora biti u formi gggg-MM-dd.</value>
+ </data>
+ <data name="TypeMustImplementX" xml:space="preserve">
+ <value>Tip mora implementirati {0}.</value>
+ </data>
+ <data name="UnsolicitedAssertionsNotAllowedFrom1xOPs" xml:space="preserve">
+ <value>Nezahtevani iskazi nisu dozvoljeni od strane 1.0 OpenID Providers.</value>
+ </data>
+ <data name="UserSetupUrlRequiredInImmediateNegativeResponse" xml:space="preserve">
+ <value>openid.user_setup_url parametar je neophodan prilikom slanja negativnih poruka sa iskazima prilikom odgovaranja na zahteve u trenutnom modu.</value>
+ </data>
+ <data name="XriResolutionFailed" xml:space="preserve">
+ <value>XRI razrešivanje neuspešno.</value>
+ </data>
+ <data name="StoreRequiredWhenNoHttpContextAvailable" xml:space="preserve">
+ <value>Tekući HttpContext nije detektovan, tako da {0} instanca mora biti eksplicitno postavljena ili specificirana u .config fajlu. Pozvati preklopljeni konstruktor koji uzima parametar {0}.</value>
+ </data>
+ <data name="AttributeAlreadyAdded" xml:space="preserve">
+ <value>Atribut sa URI tipom '{0}' je već dodat.</value>
+ </data>
+ <data name="AttributeTooManyValues" xml:space="preserve">
+ <value>Samo {0} vrednosti za atribut '{1}' su zahtevane, ali {2} su ponuđene.</value>
+ </data>
+ <data name="UnspecifiedDateTimeKindNotAllowed" xml:space="preserve">
+ <value>Prosleđivanje objekta tipa DateTime čije svojstvo Kind ima vrednost Unspecified nije dozvoljeno.</value>
+ </data>
+ <data name="AssociationOrSessionTypeUnrecognizedOrNotSupported" xml:space="preserve">
+ <value>Zahtevani tip asocijacije '{0}' sa sesijom tipa '{1}' nije prepoznat ili nije podržan od strane ovog Provider-a zbog bezbedonosnih zahteva.</value>
+ </data>
+ <data name="IncompatibleAssociationAndSessionTypes" xml:space="preserve">
+ <value>Provider je zahtevao asocijaciju tipa '{0}' i sesiju tipa '{1}', koje nisu međusobno kompatibilne.</value>
+ </data>
+ <data name="CreateRequestAlreadyCalled" xml:space="preserve">
+ <value>Zahtev za autentifikacijom je već kreiran korišćenjem CreateRequest().</value>
+ </data>
+ <data name="OpenIdTextBoxEmpty" xml:space="preserve">
+ <value>Nijedan OpenID url nije ponuđen.</value>
+ </data>
+ <data name="ClientScriptExtensionPropertyNameCollision" xml:space="preserve">
+ <value>Ekstenzija sa svojstvom ovog imena ('{0}') je već registrovana.</value>
+ </data>
+ <data name="ClientScriptExtensionTypeCollision" xml:space="preserve">
+ <value>Ekstenzija '{0}' je već registrovana.</value>
+ </data>
+ <data name="UnexpectedHttpStatusCode" xml:space="preserve">
+ <value>Neočekivani HTTP statusni kod {0} {1} primljen u direktnom odgovoru.</value>
+ </data>
+ <data name="NotSupportedByAuthenticationSnapshot" xml:space="preserve">
+ <value>Ova operacija nije podržana od strane serijalizovanih odgovora za autentifikaciju. Pokušati ovu operaciju iz LoggedIn handler-a događaja.</value>
+ </data>
+ <data name="NoRelyingPartyEndpointDiscovered" xml:space="preserve">
+ <value>Nijedan XRDS dokument koji sadrži informaciju o OpenID Relying Party krajnjoj tački nije pronadjen u {0}.</value>
+ </data>
+ <data name="AbsoluteUriRequired" xml:space="preserve">
+ <value>Absolutni URI je zahtevan za ovu vrednost.</value>
+ </data>
+ <data name="UnsolicitedAssertionForUnrelatedClaimedIdentifier" xml:space="preserve">
+ <value>Nezahtevani iskaz ne može biti poslat za navedeni identifikator {0} jer ovo nije autorizovani Provider za taj identifikator.</value>
+ </data>
+ <data name="MaximumAuthenticationTimeExpired" xml:space="preserve">
+ <value>Maksimalno dozvoljeno vreme za kompletiranje autentifikacije je isteklo. Molimo pokušajte ponovo.</value>
+ </data>
+ <data name="PrivateRPSecretNotFound" xml:space="preserve">
+ <value>Ne može se pronaći tajna za potpisivanje od strane handle-a '{0}'.</value>
+ </data>
+ <data name="ResponseNotReady" xml:space="preserve">
+ <value>Odgovor nije spreman. Koristiti najpre IsResponseReady za proveru da li je odgovor spreman.</value>
+ </data>
+ <data name="UnsupportedChannelConfiguration" xml:space="preserve">
+ <value>Ovo svojstvo nije dostupno zbog nepoznate konfiguracije kanala.</value>
+ </data>
+ <data name="IdentityAndClaimedIdentifierMustBeBothPresentOrAbsent" xml:space="preserve">
+ <value>openid.identity i openid.claimed_id parametri moraju istovremeno biti prisutna ili istovremeno odsutna u poruci.</value>
+ </data>
+ <data name="ReturnToRequiredForOperation" xml:space="preserve">
+ <value>Svojstvo ReturnTo ne sme biti null da bi se podržala ova operacija.</value>
+ </data>
+ <data name="UnsolicitedAssertionRejectionRequiresNonceStore" xml:space="preserve">
+ <value>Odbijanje nezahtevanih iskaza zahteva skladište jedinstvenih identifikatora i skladište asocijacija.</value>
+ </data>
+ <data name="UnsolicitedAssertionsNotAllowed" xml:space="preserve">
+ <value>Nezahtevani iskazi nisu dozvoljeni od strane ovog Relying Party.</value>
+ </data>
+ <data name="DelegatingIdentifiersNotAllowed" xml:space="preserve">
+ <value>Samo OpenID-jevi izdati direktno od strane njihovog OpenID Provider-a su ovde dozvoljeni.</value>
+ </data>
+ <data name="XriResolutionDisabled" xml:space="preserve">
+ <value>XRI podrška je onemogućena na ovom sajtu.</value>
+ </data>
+ <data name="AssociationStoreRequired" xml:space="preserve">
+ <value>Skladište asocijacija nije dato a zahtevano je za trenutnu konfiguraciju.</value>
+ </data>
+ <data name="UnexpectedEnumPropertyValue" xml:space="preserve">
+ <value>Svojstvo {0} je imalo neočekivanu vrednost {1}.</value>
+ </data>
+ <data name="NoIdentifierSet" xml:space="preserve">
+ <value>Ni jedan identifikator nije podešen.</value>
+ </data>
+ <data name="PropertyValueNotSupported" xml:space="preserve">
+ <value>Ova vrednost svojstva nije podržana od strane ove kontrole.</value>
+ </data>
+ <data name="ArgumentIsPpidIdentifier" xml:space="preserve">
+ <value>Ovo je već PPID Identifier.</value>
+ </data>
+ <data name="RequireSslNotSatisfiedByAssertedClaimedId" xml:space="preserve">
+ <value>Žao nam je. Ovaj sajt jedino prihvata OpenID-jeve koji su HTTPS-bezbedni, a {0} nije bezbedni Identifier.</value>
+ </data>
+</root> \ No newline at end of file
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/OpenIdUtilities.cs b/src/DotNetOpenAuth.OpenId/OpenId/OpenIdUtilities.cs
new file mode 100644
index 0000000..a5202de
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/OpenIdUtilities.cs
@@ -0,0 +1,218 @@
+//-----------------------------------------------------------------------
+// <copyright file="OpenIdUtilities.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.IO;
+ using System.Linq;
+ using System.Text.RegularExpressions;
+ using System.Web;
+ using System.Web.UI;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.ChannelElements;
+ using DotNetOpenAuth.OpenId.Extensions;
+ using Org.Mentalis.Security.Cryptography;
+
+ /// <summary>
+ /// A set of utilities especially useful to OpenID.
+ /// </summary>
+ public static class OpenIdUtilities {
+ /// <summary>
+ /// The prefix to designate this library's proprietary parameters added to the protocol.
+ /// </summary>
+ internal const string CustomParameterPrefix = "dnoa.";
+
+ /// <summary>
+ /// A static variable that carries the results of a check for the presence of
+ /// assemblies that are required for the Diffie-Hellman algorithm.
+ /// </summary>
+ private static bool? diffieHellmanPresent;
+
+ /// <summary>
+ /// Gets a value indicating whether Diffie Hellman is available in this installation.
+ /// </summary>
+ /// <value>
+ /// <c>true</c> if Diffie-Hellman functionality is present; otherwise, <c>false</c>.
+ /// </value>
+ internal static bool IsDiffieHellmanPresent {
+ get {
+ if (!diffieHellmanPresent.HasValue) {
+ try {
+ LoadDiffieHellmanTypes();
+ diffieHellmanPresent = true;
+ } catch (FileNotFoundException) {
+ diffieHellmanPresent = false;
+ } catch (TypeLoadException) {
+ diffieHellmanPresent = false;
+ }
+
+ if (diffieHellmanPresent.Value) {
+ Logger.OpenId.Info("Diffie-Hellman supporting assemblies found and loaded.");
+ } else {
+ Logger.OpenId.Warn("Diffie-Hellman supporting assemblies failed to load. Only associations with HTTPS OpenID Providers will be supported.");
+ }
+ }
+
+ return diffieHellmanPresent.Value;
+ }
+ }
+
+ /// <summary>
+ /// Creates a random association handle.
+ /// </summary>
+ /// <returns>The association handle.</returns>
+ public static string GenerateRandomAssociationHandle() {
+ Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>()));
+
+ // Generate the handle. It must be unique, and preferably unpredictable,
+ // so we use a time element and a random data element to generate it.
+ string uniq = MessagingUtilities.GetCryptoRandomDataAsBase64(4);
+ return string.Format(CultureInfo.InvariantCulture, "{{{0}}}{{{1}}}", DateTime.UtcNow.Ticks, uniq);
+ }
+
+ /// <summary>
+ /// Gets the OpenID protocol instance for the version in a message.
+ /// </summary>
+ /// <param name="message">The message.</param>
+ /// <returns>The OpenID protocol instance.</returns>
+ internal static Protocol GetProtocol(this IProtocolMessage message) {
+ Requires.NotNull(message, "message");
+ return Protocol.Lookup(message.Version);
+ }
+
+ /// <summary>
+ /// Changes the position of some element in a list.
+ /// </summary>
+ /// <typeparam name="T">The type of elements stored in the list.</typeparam>
+ /// <param name="list">The list to be modified.</param>
+ /// <param name="position">The new position for the given element.</param>
+ /// <param name="value">The element to move within the list.</param>
+ /// <exception cref="InternalErrorException">Thrown if the element does not already exist in the list.</exception>
+ internal static void MoveTo<T>(this IList<T> list, int position, T value) {
+ ErrorUtilities.VerifyInternal(list.Remove(value), "Unable to find element in list.");
+ list.Insert(position, value);
+ }
+
+ /// <summary>
+ /// Corrects any URI decoding the Provider may have inappropriately done
+ /// to our return_to URL, resulting in an otherwise corrupted base64 encoded value.
+ /// </summary>
+ /// <param name="value">The base64 encoded value. May be null.</param>
+ /// <returns>
+ /// The value; corrected if corruption had occurred.
+ /// </returns>
+ /// <remarks>
+ /// AOL may have incorrectly URI-decoded the token for us in the return_to,
+ /// resulting in a token URI-decoded twice by the time we see it, and no
+ /// longer being a valid base64 string.
+ /// It turns out that the only symbols from base64 that is also encoded
+ /// in URI encoding rules are the + and / characters.
+ /// AOL decodes the %2b sequence to the + character
+ /// and the %2f sequence to the / character (it shouldn't decode at all).
+ /// When we do our own URI decoding, the + character becomes a space (corrupting base64)
+ /// but the / character remains a /, so no further corruption happens to this character.
+ /// So to correct this we just need to change any spaces we find in the token
+ /// back to + characters.
+ /// </remarks>
+ internal static string FixDoublyUriDecodedBase64String(string value) {
+ if (value == null) {
+ return null;
+ }
+
+ if (value.Contains(" ")) {
+ Logger.OpenId.Error("Deserializing a corrupted token. The OpenID Provider may have inappropriately decoded the return_to URL before sending it back to us.");
+ value = value.Replace(' ', '+'); // Undo any extra decoding the Provider did
+ }
+
+ return value;
+ }
+
+ /// <summary>
+ /// Rounds the given <see cref="DateTime"/> downward to the whole second.
+ /// </summary>
+ /// <param name="dateTime">The DateTime object to adjust.</param>
+ /// <returns>The new <see cref="DateTime"/> value.</returns>
+ internal static DateTime CutToSecond(DateTime dateTime) {
+ return new DateTime(dateTime.Ticks - (dateTime.Ticks % TimeSpan.TicksPerSecond), dateTime.Kind);
+ }
+
+ /// <summary>
+ /// Gets the fully qualified Realm URL, given a Realm that may be relative to a particular page.
+ /// </summary>
+ /// <param name="page">The hosting page that has the realm value to resolve.</param>
+ /// <param name="realm">The realm, which may begin with "*." or "~/".</param>
+ /// <param name="requestContext">The request context.</param>
+ /// <returns>The fully-qualified realm.</returns>
+ [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "DotNetOpenAuth.OpenId.Realm", Justification = "Using ctor for validation.")]
+ internal static UriBuilder GetResolvedRealm(Page page, string realm, HttpRequestBase requestContext) {
+ Requires.NotNull(page, "page");
+ Requires.NotNull(requestContext, "requestContext");
+
+ // Allow for *. realm notation, as well as ASP.NET ~/ shortcuts.
+
+ // We have to temporarily remove the *. notation if it's there so that
+ // the rest of our URL manipulation will succeed.
+ bool foundWildcard = false;
+
+ // Note: we don't just use string.Replace because poorly written URLs
+ // could potentially have multiple :// sequences in them.
+ MatchEvaluator matchDelegate = delegate(Match m) {
+ foundWildcard = true;
+ return m.Groups[1].Value;
+ };
+ string realmNoWildcard = Regex.Replace(realm, @"^(\w+://)\*\.", matchDelegate);
+
+ UriBuilder fullyQualifiedRealm = new UriBuilder(
+ new Uri(requestContext.GetPublicFacingUrl(), page.ResolveUrl(realmNoWildcard)));
+
+ if (foundWildcard) {
+ fullyQualifiedRealm.Host = "*." + fullyQualifiedRealm.Host;
+ }
+
+ // Is it valid?
+ new Realm(fullyQualifiedRealm); // throws if not valid
+
+ return fullyQualifiedRealm;
+ }
+
+ /// <summary>
+ /// Gets the extension factories from the extension aggregator on an OpenID channel.
+ /// </summary>
+ /// <param name="channel">The channel.</param>
+ /// <returns>The list of factories that will be used to generate extension instances.</returns>
+ /// <remarks>
+ /// This is an extension method on <see cref="Channel"/> rather than an instance
+ /// method on <see cref="OpenIdChannel"/> because the OpenIdRelyingParty
+ /// and OpenIdProvider classes don't strong-type to <see cref="OpenIdChannel"/>
+ /// to allow flexibility in the specific type of channel the user (or tests)
+ /// can plug in.
+ /// </remarks>
+ internal static IList<IOpenIdExtensionFactory> GetExtensionFactories(this Channel channel) {
+ Requires.NotNull(channel, "channel");
+
+ var extensionsBindingElement = channel.BindingElements.OfType<ExtensionsBindingElement>().SingleOrDefault();
+ ErrorUtilities.VerifyOperation(extensionsBindingElement != null, OpenIdStrings.UnsupportedChannelConfiguration);
+ IOpenIdExtensionFactory factory = extensionsBindingElement.ExtensionFactory;
+ var aggregator = factory as OpenIdExtensionFactoryAggregator;
+ ErrorUtilities.VerifyOperation(aggregator != null, OpenIdStrings.UnsupportedChannelConfiguration);
+ return aggregator.Factories;
+ }
+
+ /// <summary>
+ /// Loads the Diffie-Hellman assemblies.
+ /// </summary>
+ /// <exception cref="FileNotFoundException">Thrown if the DH assemblies are missing.</exception>
+ private static void LoadDiffieHellmanTypes() {
+ // This seeming no-op instruction is enough for the CLR to throw a FileNotFoundException
+ // If the assemblies are missing.
+ new DiffieHellmanManaged();
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/OpenIdXrdsHelper.cs b/src/DotNetOpenAuth.OpenId/OpenId/OpenIdXrdsHelper.cs
new file mode 100644
index 0000000..284f8db
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/OpenIdXrdsHelper.cs
@@ -0,0 +1,68 @@
+//-----------------------------------------------------------------------
+// <copyright file="OpenIdXrdsHelper.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Xrds;
+
+ /// <summary>
+ /// Utility methods for working with XRDS documents.
+ /// </summary>
+ internal static class OpenIdXrdsHelper {
+ /// <summary>
+ /// Finds the Relying Party return_to receiving endpoints.
+ /// </summary>
+ /// <param name="xrds">The XrdsDocument instance to use in this process.</param>
+ /// <returns>A sequence of Relying Party descriptors for the return_to endpoints.</returns>
+ /// <remarks>
+ /// This is useful for Providers to send unsolicited assertions to Relying Parties,
+ /// or for Provider's to perform RP discovery/verification as part of authentication.
+ /// </remarks>
+ internal static IEnumerable<RelyingPartyEndpointDescription> FindRelyingPartyReceivingEndpoints(this XrdsDocument xrds) {
+ Requires.NotNull(xrds, "xrds");
+ Contract.Ensures(Contract.Result<IEnumerable<RelyingPartyEndpointDescription>>() != null);
+
+ return from service in xrds.FindReturnToServices()
+ from uri in service.UriElements
+ select new RelyingPartyEndpointDescription(uri.Uri, service.TypeElementUris);
+ }
+
+ /// <summary>
+ /// Finds the icons the relying party wants an OP to display as part of authentication,
+ /// per the UI extension spec.
+ /// </summary>
+ /// <param name="xrds">The XrdsDocument to search.</param>
+ /// <returns>A sequence of the icon URLs in preferred order.</returns>
+ internal static IEnumerable<Uri> FindRelyingPartyIcons(this XrdsDocument xrds) {
+ Requires.NotNull(xrds, "xrds");
+ Contract.Ensures(Contract.Result<IEnumerable<Uri>>() != null);
+
+ return from xrd in xrds.XrdElements
+ from service in xrd.OpenIdRelyingPartyIcons
+ from uri in service.UriElements
+ select uri.Uri;
+ }
+
+ /// <summary>
+ /// Enumerates the XRDS service elements that describe OpenID Relying Party return_to URLs
+ /// that can receive authentication assertions.
+ /// </summary>
+ /// <param name="xrds">The XrdsDocument instance to use in this process.</param>
+ /// <returns>A sequence of service elements.</returns>
+ private static IEnumerable<ServiceElement> FindReturnToServices(this XrdsDocument xrds) {
+ Requires.NotNull(xrds, "xrds");
+ Contract.Ensures(Contract.Result<IEnumerable<ServiceElement>>() != null);
+
+ return from xrd in xrds.XrdElements
+ from service in xrd.OpenIdRelyingPartyReturnToServices
+ select service;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Protocol.cs b/src/DotNetOpenAuth.OpenId/OpenId/Protocol.cs
new file mode 100644
index 0000000..22f5b9c
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Protocol.cs
@@ -0,0 +1,478 @@
+// <auto-generated/> // disable StyleCop on this file
+//-----------------------------------------------------------------------
+// <copyright file="Protocol.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId {
+ using System;
+ using System.Collections.Generic;
+ using DotNetOpenAuth.Messaging;
+ using System.Globalization;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Diagnostics;
+
+ /// <summary>
+ /// An enumeration of the OpenID protocol versions supported by this library.
+ /// </summary>
+ public enum ProtocolVersion {
+ /// <summary>
+ /// OpenID Authentication 1.0
+ /// </summary>
+ V10,
+ /// <summary>
+ /// OpenID Authentication 1.1
+ /// </summary>
+ V11,
+ /// <summary>
+ /// OpenID Authentication 2.0
+ /// </summary>
+ V20,
+ }
+
+ /// <summary>
+ /// Tracks the several versions of OpenID this library supports and the unique
+ /// constants to each version used in the protocol.
+ /// </summary>
+ [DebuggerDisplay("OpenID {Version}")]
+ internal sealed class Protocol {
+ /// <summary>
+ /// The value of the openid.ns parameter in the OpenID 2.0 specification.
+ /// </summary>
+ internal const string OpenId2Namespace = "http://specs.openid.net/auth/2.0";
+
+ /// <summary>
+ /// The parameter of the callback parameter we tack onto the return_to URL
+ /// to store the replay-detection nonce.
+ /// </summary>
+ internal const string ReturnToNonceParameter = OpenIdUtilities.CustomParameterPrefix + "request_nonce";
+
+ /// <summary>
+ /// Scans a list for matches with some element of the OpenID protocol,
+ /// searching from newest to oldest protocol for the first and best match.
+ /// </summary>
+ /// <typeparam name="T">The type of element retrieved from the <see cref="Protocol"/> instance.</typeparam>
+ /// <param name="elementOf">Takes a <see cref="Protocol"/> instance and returns an element of it.</param>
+ /// <param name="list">The list to scan for matches.</param>
+ /// <returns>The protocol with the element that matches some item in the list.</returns>
+ internal static Protocol FindBestVersion<T>(Func<Protocol, T> elementOf, IEnumerable<T> list) {
+ foreach (var protocol in Protocol.AllVersions) {
+ foreach (var item in list) {
+ if (item != null && item.Equals(elementOf(protocol)))
+ return protocol;
+ }
+ }
+ return null;
+ }
+
+ Protocol(QueryParameters queryBits) {
+ openidnp = queryBits;
+ openid = new QueryParameters(queryBits);
+ }
+
+ // Well-known, supported versions of the OpenID spec.
+ public static readonly Protocol V10 = new Protocol(new QueryParameters()) {
+ Version = new Version(1, 0),
+ XmlNamespace = "http://openid.net/xmlns/1.0",
+ QueryDeclaredNamespaceVersion = null,
+ ClaimedIdentifierServiceTypeURI = "http://openid.net/signon/1.0",
+ OPIdentifierServiceTypeURI = null, // not supported
+ ClaimedIdentifierForOPIdentifier = null, // not supported
+ RPReturnToTypeURI = null, // not supported
+ HtmlDiscoveryProviderKey = "openid.server",
+ HtmlDiscoveryLocalIdKey = "openid.delegate",
+ };
+ public static readonly Protocol V11 = new Protocol(new QueryParameters()) {
+ Version = new Version(1, 1),
+ XmlNamespace = "http://openid.net/xmlns/1.0",
+ QueryDeclaredNamespaceVersion = null,
+ ClaimedIdentifierServiceTypeURI = "http://openid.net/signon/1.1",
+ OPIdentifierServiceTypeURI = null, // not supported
+ ClaimedIdentifierForOPIdentifier = null, // not supported
+ RPReturnToTypeURI = null, // not supported
+ HtmlDiscoveryProviderKey = "openid.server",
+ HtmlDiscoveryLocalIdKey = "openid.delegate",
+ };
+ public static readonly Protocol V20 = new Protocol(new QueryParameters() {
+ Realm = "realm",
+ op_endpoint = "op_endpoint",
+ response_nonce = "response_nonce",
+ error_code = "error_code",
+ user_setup_url = null,
+ }) {
+ Version = new Version(2, 0),
+ XmlNamespace = null, // no longer applicable
+ QueryDeclaredNamespaceVersion = Protocol.OpenId2Namespace,
+ ClaimedIdentifierServiceTypeURI = "http://specs.openid.net/auth/2.0/signon",
+ OPIdentifierServiceTypeURI = "http://specs.openid.net/auth/2.0/server",
+ ClaimedIdentifierForOPIdentifier = "http://specs.openid.net/auth/2.0/identifier_select",
+ RPReturnToTypeURI = "http://specs.openid.net/auth/2.0/return_to",
+ HtmlDiscoveryProviderKey = "openid2.provider",
+ HtmlDiscoveryLocalIdKey = "openid2.local_id",
+ Args = new QueryArguments() {
+ SessionType = new QueryArguments.SessionTypes() {
+ NoEncryption = "no-encryption",
+ DH_SHA256 = "DH-SHA256",
+ DH_SHA384 = "DH-SHA384",
+ DH_SHA512 = "DH-SHA512",
+ },
+ SignatureAlgorithm = new QueryArguments.SignatureAlgorithms() {
+ HMAC_SHA256 = "HMAC-SHA256",
+ HMAC_SHA384 = "HMAC-SHA384",
+ HMAC_SHA512 = "HMAC-SHA512",
+ },
+ Mode = new QueryArguments.Modes() {
+ setup_needed = "setup_needed",
+ },
+ },
+ };
+
+ /// <summary>
+ /// A list of all supported OpenID versions, in order starting from newest version.
+ /// </summary>
+ public readonly static List<Protocol> AllVersions = new List<Protocol>() { V20, V11, V10 };
+
+ /// <summary>
+ /// A list of all supported OpenID versions, in order starting from newest version.
+ /// V1.1 and V1.0 are considered the same and only V1.1 is in the list.
+ /// </summary>
+ public readonly static List<Protocol> AllPracticalVersions = new List<Protocol>() { V20, V11 };
+
+ /// <summary>
+ /// The default (or most recent) supported version of the OpenID protocol.
+ /// </summary>
+ public readonly static Protocol Default = AllVersions[0];
+ public static Protocol Lookup(Version version) {
+ foreach (Protocol protocol in AllVersions) {
+ if (protocol.Version == version) return protocol;
+ }
+ throw new ArgumentOutOfRangeException("version");
+ }
+ public static Protocol Lookup(ProtocolVersion version) {
+ switch (version) {
+ case ProtocolVersion.V10: return Protocol.V10;
+ case ProtocolVersion.V11: return Protocol.V11;
+ case ProtocolVersion.V20: return Protocol.V20;
+ default: throw new ArgumentOutOfRangeException("version");
+ }
+ }
+ /// <summary>
+ /// Attempts to detect the right OpenID protocol version based on the contents
+ /// of an incoming OpenID <i>indirect</i> message or <i>direct request</i>.
+ /// </summary>
+ internal static Protocol Detect(IDictionary<string, string> query) {
+ Requires.NotNull(query, "query");
+ return query.ContainsKey(V20.openid.ns) ? V20 : V11;
+ }
+ /// <summary>
+ /// Attempts to detect the right OpenID protocol version based on the contents
+ /// of an incoming OpenID <i>direct</i> response message.
+ /// </summary>
+ internal static Protocol DetectFromDirectResponse(IDictionary<string, string> query) {
+ Requires.NotNull(query, "query");
+ return query.ContainsKey(V20.openidnp.ns) ? V20 : V11;
+ }
+ /// <summary>
+ /// Attemps to detect the highest OpenID protocol version supported given a set
+ /// of XRDS Service Type URIs included for some service.
+ /// </summary>
+ internal static Protocol Detect(IEnumerable<string> serviceTypeURIs) {
+ Requires.NotNull(serviceTypeURIs, "serviceTypeURIs");
+ return FindBestVersion(p => p.OPIdentifierServiceTypeURI, serviceTypeURIs) ??
+ FindBestVersion(p => p.ClaimedIdentifierServiceTypeURI, serviceTypeURIs) ??
+ FindBestVersion(p => p.RPReturnToTypeURI, serviceTypeURIs);
+ }
+
+ /// <summary>
+ /// The OpenID version that this <see cref="Protocol"/> instance describes.
+ /// </summary>
+ public Version Version;
+ /// <summary>
+ /// Returns the <see cref="ProtocolVersion"/> enum value for the <see cref="Protocol"/> instance.
+ /// </summary>
+ public ProtocolVersion ProtocolVersion {
+ get {
+ switch (Version.Major) {
+ case 1: return ProtocolVersion.V11;
+ case 2: return ProtocolVersion.V20;
+ default: throw new ArgumentException(null); // this should never happen
+ }
+ }
+ }
+ /// <summary>
+ /// The namespace of OpenId 1.x elements in XRDS documents.
+ /// </summary>
+ public string XmlNamespace;
+ /// <summary>
+ /// The value of the openid.ns parameter that appears on the query string
+ /// whenever data is passed between relying party and provider for OpenID 2.0
+ /// and later.
+ /// </summary>
+ public string QueryDeclaredNamespaceVersion;
+ /// <summary>
+ /// The XRD/Service/Type value discovered in an XRDS document when
+ /// "discovering" on a Claimed Identifier (http://andrewarnott.yahoo.com)
+ /// </summary>
+ public string ClaimedIdentifierServiceTypeURI;
+ /// <summary>
+ /// The XRD/Service/Type value discovered in an XRDS document when
+ /// "discovering" on an OP Identifier rather than a Claimed Identifier.
+ /// (http://yahoo.com)
+ /// </summary>
+ public string OPIdentifierServiceTypeURI;
+ /// <summary>
+ /// The XRD/Service/Type value discovered in an XRDS document when
+ /// "discovering" on a Realm URL and looking for the endpoint URL
+ /// that can receive authentication assertions.
+ /// </summary>
+ public string RPReturnToTypeURI;
+ /// <summary>
+ /// Used as the Claimed Identifier and the OP Local Identifier when
+ /// the User Supplied Identifier is an OP Identifier.
+ /// </summary>
+ public string ClaimedIdentifierForOPIdentifier;
+ /// <summary>
+ /// The value of the 'rel' attribute in an HTML document's LINK tag
+ /// when the same LINK tag's HREF attribute value contains the URL to an
+ /// OP Endpoint URL.
+ /// </summary>
+ public string HtmlDiscoveryProviderKey;
+ /// <summary>
+ /// The value of the 'rel' attribute in an HTML document's LINK tag
+ /// when the same LINK tag's HREF attribute value contains the URL to use
+ /// as the OP Local Identifier.
+ /// </summary>
+ public string HtmlDiscoveryLocalIdKey;
+ /// <summary>
+ /// Parts of the protocol that define parameter names that appear in the
+ /// query string. Each parameter name is prefixed with 'openid.'.
+ /// </summary>
+ public readonly QueryParameters openid;
+ /// <summary>
+ /// Parts of the protocol that define parameter names that appear in the
+ /// query string. Each parameter name is NOT prefixed with 'openid.'.
+ /// </summary>
+ public readonly QueryParameters openidnp;
+ /// <summary>
+ /// The various 'constants' that appear as parameter arguments (values).
+ /// </summary>
+ public QueryArguments Args = new QueryArguments();
+
+ internal sealed class QueryParameters {
+ /// <summary>
+ /// The value "openid."
+ /// </summary>
+ public readonly string Prefix = "openid.";
+ [SuppressMessage("Microsoft.Performance", "CA1805:DoNotInitializeUnnecessarily")]
+ public QueryParameters() { }
+ [SuppressMessage("Microsoft.Performance", "CA1805:DoNotInitializeUnnecessarily")]
+ public QueryParameters(QueryParameters addPrefixTo) {
+ ns = addPrefix(addPrefixTo.ns);
+ return_to = addPrefix(addPrefixTo.return_to);
+ Realm = addPrefix(addPrefixTo.Realm);
+ mode = addPrefix(addPrefixTo.mode);
+ error = addPrefix(addPrefixTo.error);
+ error_code = addPrefix(addPrefixTo.error_code);
+ identity = addPrefix(addPrefixTo.identity);
+ op_endpoint = addPrefix(addPrefixTo.op_endpoint);
+ response_nonce = addPrefix(addPrefixTo.response_nonce);
+ claimed_id = addPrefix(addPrefixTo.claimed_id);
+ expires_in = addPrefix(addPrefixTo.expires_in);
+ assoc_type = addPrefix(addPrefixTo.assoc_type);
+ assoc_handle = addPrefix(addPrefixTo.assoc_handle);
+ session_type = addPrefix(addPrefixTo.session_type);
+ is_valid = addPrefix(addPrefixTo.is_valid);
+ sig = addPrefix(addPrefixTo.sig);
+ signed = addPrefix(addPrefixTo.signed);
+ user_setup_url = addPrefix(addPrefixTo.user_setup_url);
+ invalidate_handle = addPrefix(addPrefixTo.invalidate_handle);
+ dh_modulus = addPrefix(addPrefixTo.dh_modulus);
+ dh_gen = addPrefix(addPrefixTo.dh_gen);
+ dh_consumer_public = addPrefix(addPrefixTo.dh_consumer_public);
+ dh_server_public = addPrefix(addPrefixTo.dh_server_public);
+ enc_mac_key = addPrefix(addPrefixTo.enc_mac_key);
+ mac_key = addPrefix(addPrefixTo.mac_key);
+ }
+ string addPrefix(string original) {
+ return (original != null) ? Prefix + original : null;
+ }
+ // These fields default to 1.x specifications, and are overridden
+ // as necessary by later versions in the Protocol class initializers.
+ // Null values in any version suggests that that feature is absent from that version.
+ public string ns = "ns";
+ public string return_to = "return_to";
+ public string Realm = "trust_root";
+ public string mode = "mode";
+ public string error = "error";
+ public string error_code = null;
+ public string identity = "identity";
+ public string op_endpoint = null;
+ public string response_nonce = null;
+ public string claimed_id = "claimed_id";
+ public string expires_in = "expires_in";
+ public string assoc_type = "assoc_type";
+ public string assoc_handle = "assoc_handle";
+ public string session_type = "session_type";
+ public string is_valid = "is_valid";
+ public string sig = "sig";
+ public string signed = "signed";
+ public string user_setup_url = "user_setup_url";
+ public string invalidate_handle = "invalidate_handle";
+ public string dh_modulus = "dh_modulus";
+ public string dh_gen = "dh_gen";
+ public string dh_consumer_public = "dh_consumer_public";
+ public string dh_server_public = "dh_server_public";
+ public string enc_mac_key = "enc_mac_key";
+ public string mac_key = "mac_key";
+
+#if CONTRACTS_FULL
+ /// <summary>
+ /// Verifies conditions that should be true for any valid state of this object.
+ /// </summary>
+ [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")]
+ [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")]
+ [ContractInvariantMethod]
+ private void ObjectInvariant() {
+ Contract.Invariant(!string.IsNullOrEmpty(this.Prefix));
+ }
+#endif
+ }
+
+ internal sealed class QueryArguments {
+ public ErrorCodes ErrorCode = new ErrorCodes();
+ public SessionTypes SessionType = new SessionTypes();
+ public SignatureAlgorithms SignatureAlgorithm = new SignatureAlgorithms();
+ public Modes Mode = new Modes();
+ public IsValidValues IsValid = new IsValidValues();
+
+ internal sealed class ErrorCodes {
+ public string UnsupportedType = "unsupported-type";
+ }
+ internal sealed class SessionTypes {
+ /// <summary>
+ /// A preference order list of all supported session types.
+ /// </summary>
+ public string[] All { get { return new[] { DH_SHA512, DH_SHA384, DH_SHA256, DH_SHA1, NoEncryption }; } }
+ public string[] AllDiffieHellman { get { return new[] { DH_SHA512, DH_SHA384, DH_SHA256, DH_SHA1 }; } }
+ public string DH_SHA1 = "DH-SHA1";
+ public string DH_SHA256;
+ public string DH_SHA384;
+ public string DH_SHA512;
+ public string NoEncryption = string.Empty;
+ public string Best {
+ get {
+ foreach (string algorithmName in All) {
+ if (algorithmName != null) {
+ return algorithmName;
+ }
+ }
+ throw new ProtocolException(); // really bad... we have no signing algorithms at all
+ }
+ }
+ }
+ internal sealed class SignatureAlgorithms {
+ /// <summary>
+ /// A preference order list of signature algorithms we support.
+ /// </summary>
+ public string[] All { get { return new[] { HMAC_SHA512, HMAC_SHA384, HMAC_SHA256, HMAC_SHA1 }; } }
+ public string HMAC_SHA1 = "HMAC-SHA1";
+ public string HMAC_SHA256;
+ public string HMAC_SHA384;
+ public string HMAC_SHA512;
+ public string Best {
+ get {
+ foreach (string algorithmName in All) {
+ if (algorithmName != null) {
+ return algorithmName;
+ }
+ }
+ throw new ProtocolException(); // really bad... we have no signing algorithms at all
+ }
+ }
+ }
+ internal sealed class Modes {
+ public string cancel = "cancel";
+ public string error = "error";
+ public string id_res = "id_res";
+ public string checkid_immediate = "checkid_immediate";
+ public string checkid_setup = "checkid_setup";
+ public string check_authentication = "check_authentication";
+ public string associate = "associate";
+ public string setup_needed = "id_res"; // V2 overrides this
+ }
+ internal sealed class IsValidValues {
+ public string True = "true";
+ public string False = "false";
+ }
+ }
+
+ /// <summary>
+ /// The maximum time a user can be allowed to take to complete authentication.
+ /// </summary>
+ /// <remarks>
+ /// This is used to calculate the length of time that nonces are stored.
+ /// This is internal until we can decide whether to leave this static, or make
+ /// it an instance member, or put it inside the IConsumerApplicationStore interface.
+ /// </remarks>
+ internal static TimeSpan MaximumUserAgentAuthenticationTime = TimeSpan.FromMinutes(5);
+ /// <summary>
+ /// The maximum permissible difference in clocks between relying party and
+ /// provider web servers, discounting time zone differences.
+ /// </summary>
+ /// <remarks>
+ /// This is used when storing/validating nonces from the provider.
+ /// If it is conceivable that a server's clock could be up to five minutes
+ /// off from true UTC time, then the maximum time skew should be set to
+ /// ten minutes to allow one server to be five minutes ahead and the remote
+ /// server to be five minutes behind and still be able to communicate.
+ /// </remarks>
+ internal static TimeSpan MaximumAllowableTimeSkew = TimeSpan.FromMinutes(10);
+
+ /// <summary>
+ /// Checks whether a given Protocol version practically equals this one
+ /// for purposes of verifying a match for assertion verification.
+ /// </summary>
+ /// <param name="other">The other version to check against this one.</param>
+ /// <returns><c>true</c> if this and the given Protocol versions are essentially the same.</returns>
+ /// <remarks>
+ /// OpenID v1.0 never had a spec, and 1.0 and 1.1 are indistinguishable because of that.
+ /// Therefore for assertion verification, 1.0 and 1.1 are considered equivalent.
+ /// </remarks>
+ public bool EqualsPractically(Protocol other) {
+ if (other == null) {
+ return false;
+ }
+
+ // Exact version match is definitely equality.
+ if (this.Version == other.Version) {
+ return true;
+ }
+
+ // If both protocol versions are 1.x, it doesn't matter if one
+ // is 1.0 and the other is 1.1 for assertion verification purposes.
+ if (this.Version.Major == 1 && other.Version.Major == 1) {
+ return true;
+ }
+
+ // Different version.
+ return false;
+ }
+
+ public override bool Equals(object obj) {
+ Protocol other = obj as Protocol;
+ if (other == null) {
+ return false;
+ }
+
+ return this.Version == other.Version;
+ }
+ public override int GetHashCode() {
+ return Version.GetHashCode();
+ }
+ public override string ToString() {
+ return string.Format(CultureInfo.CurrentCulture, "OpenID Authentication {0}.{1}", Version.Major, Version.Minor);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Provider/IAuthenticationRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Provider/IAuthenticationRequest.cs
new file mode 100644
index 0000000..5e8efa5
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Provider/IAuthenticationRequest.cs
@@ -0,0 +1,367 @@
+//-----------------------------------------------------------------------
+// <copyright file="IAuthenticationRequest.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Provider {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// Instances of this interface represent incoming authentication requests.
+ /// This interface provides the details of the request and allows setting
+ /// the response.
+ /// </summary>
+ [ContractClass(typeof(IAuthenticationRequestContract))]
+ public interface IAuthenticationRequest : IHostProcessedRequest {
+ /// <summary>
+ /// Gets a value indicating whether the Provider should help the user
+ /// select a Claimed Identifier to send back to the relying party.
+ /// </summary>
+ bool IsDirectedIdentity { get; }
+
+ /// <summary>
+ /// Gets a value indicating whether the requesting Relying Party is using a delegated URL.
+ /// </summary>
+ /// <remarks>
+ /// When delegated identifiers are used, the <see cref="ClaimedIdentifier"/> should not
+ /// be changed at the Provider during authentication.
+ /// Delegation is only detectable on requests originating from OpenID 2.0 relying parties.
+ /// A relying party implementing only OpenID 1.x may use delegation and this property will
+ /// return false anyway.
+ /// </remarks>
+ bool IsDelegatedIdentifier { get; }
+
+ /// <summary>
+ /// Gets or sets the Local Identifier to this OpenID Provider of the user attempting
+ /// to authenticate. Check <see cref="IsDirectedIdentity"/> to see if
+ /// this value is valid.
+ /// </summary>
+ /// <remarks>
+ /// This may or may not be the same as the Claimed Identifier that the user agent
+ /// originally supplied to the relying party. The Claimed Identifier
+ /// endpoint may be delegating authentication to this provider using
+ /// this provider's local id, which is what this property contains.
+ /// Use this identifier when looking up this user in the provider's user account
+ /// list.
+ /// </remarks>
+ Identifier LocalIdentifier { get; set; }
+
+ /// <summary>
+ /// Gets or sets the identifier that the user agent is claiming at the relying party site.
+ /// Check <see cref="IsDirectedIdentity"/> to see if this value is valid.
+ /// </summary>
+ /// <remarks>
+ /// <para>This property can only be set if <see cref="IsDelegatedIdentifier"/> is
+ /// false, to prevent breaking URL delegation.</para>
+ /// <para>This will not be the same as this provider's local identifier for the user
+ /// if the user has set up his/her own identity page that points to this
+ /// provider for authentication.</para>
+ /// <para>The provider may use this identifier for displaying to the user when
+ /// asking for the user's permission to authenticate to the relying party.</para>
+ /// </remarks>
+ /// <exception cref="InvalidOperationException">Thrown from the setter
+ /// if <see cref="IsDelegatedIdentifier"/> is true.</exception>
+ Identifier ClaimedIdentifier { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the provider has determined that the
+ /// <see cref="ClaimedIdentifier"/> belongs to the currently logged in user
+ /// and wishes to share this information with the consumer.
+ /// </summary>
+ bool? IsAuthenticated { get; set; }
+
+ /// <summary>
+ /// Adds an optional fragment (#fragment) portion to the ClaimedIdentifier.
+ /// Useful for identifier recycling.
+ /// </summary>
+ /// <param name="fragment">
+ /// Should not include the # prefix character as that will be added internally.
+ /// May be null or the empty string to clear a previously set fragment.
+ /// </param>
+ /// <remarks>
+ /// <para>Unlike the <see cref="ClaimedIdentifier"/> property, which can only be set if
+ /// using directed identity, this method can be called on any URI claimed identifier.</para>
+ /// <para>Because XRI claimed identifiers (the canonical IDs) are never recycled,
+ /// this method should<i>not</i> be called for XRIs.</para>
+ /// </remarks>
+ /// <exception cref="InvalidOperationException">
+ /// Thrown when this method is called on an XRI, or on a directed identity
+ /// request before the <see cref="ClaimedIdentifier"/> property is set.
+ /// </exception>
+ void SetClaimedIdentifierFragment(string fragment);
+ }
+
+ /// <summary>
+ /// Code contract class for the <see cref="IAuthenticationRequest"/> type.
+ /// </summary>
+ [ContractClassFor(typeof(IAuthenticationRequest))]
+ internal abstract class IAuthenticationRequestContract : IAuthenticationRequest {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="IAuthenticationRequestContract"/> class.
+ /// </summary>
+ protected IAuthenticationRequestContract() {
+ }
+
+ #region IAuthenticationRequest Properties
+
+ /// <summary>
+ /// Gets a value indicating whether the Provider should help the user
+ /// select a Claimed Identifier to send back to the relying party.
+ /// </summary>
+ bool IAuthenticationRequest.IsDirectedIdentity {
+ get { throw new NotImplementedException(); }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether the requesting Relying Party is using a delegated URL.
+ /// </summary>
+ /// <remarks>
+ /// When delegated identifiers are used, the <see cref="IAuthenticationRequest.ClaimedIdentifier"/> should not
+ /// be changed at the Provider during authentication.
+ /// Delegation is only detectable on requests originating from OpenID 2.0 relying parties.
+ /// A relying party implementing only OpenID 1.x may use delegation and this property will
+ /// return false anyway.
+ /// </remarks>
+ bool IAuthenticationRequest.IsDelegatedIdentifier {
+ get { throw new NotImplementedException(); }
+ }
+
+ /// <summary>
+ /// Gets or sets the Local Identifier to this OpenID Provider of the user attempting
+ /// to authenticate. Check <see cref="IAuthenticationRequest.IsDirectedIdentity"/> to see if
+ /// this value is valid.
+ /// </summary>
+ /// <remarks>
+ /// This may or may not be the same as the Claimed Identifier that the user agent
+ /// originally supplied to the relying party. The Claimed Identifier
+ /// endpoint may be delegating authentication to this provider using
+ /// this provider's local id, which is what this property contains.
+ /// Use this identifier when looking up this user in the provider's user account
+ /// list.
+ /// </remarks>
+ Identifier IAuthenticationRequest.LocalIdentifier {
+ get {
+ throw new NotImplementedException();
+ }
+
+ set {
+ throw new NotImplementedException();
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the identifier that the user agent is claiming at the relying party site.
+ /// Check <see cref="IAuthenticationRequest.IsDirectedIdentity"/> to see if this value is valid.
+ /// </summary>
+ /// <remarks>
+ /// <para>This property can only be set if <see cref="IAuthenticationRequest.IsDelegatedIdentifier"/> is
+ /// false, to prevent breaking URL delegation.</para>
+ /// <para>This will not be the same as this provider's local identifier for the user
+ /// if the user has set up his/her own identity page that points to this
+ /// provider for authentication.</para>
+ /// <para>The provider may use this identifier for displaying to the user when
+ /// asking for the user's permission to authenticate to the relying party.</para>
+ /// </remarks>
+ /// <exception cref="InvalidOperationException">Thrown from the setter
+ /// if <see cref="IAuthenticationRequest.IsDelegatedIdentifier"/> is true.</exception>
+ Identifier IAuthenticationRequest.ClaimedIdentifier {
+ get {
+ throw new NotImplementedException();
+ }
+
+ set {
+ IAuthenticationRequest req = this;
+ Requires.ValidState(!req.IsDelegatedIdentifier, OpenIdStrings.ClaimedIdentifierCannotBeSetOnDelegatedAuthentication);
+ Requires.ValidState(!req.IsDirectedIdentity || !(req.LocalIdentifier != null && req.LocalIdentifier != value), OpenIdStrings.IdentifierSelectRequiresMatchingIdentifiers);
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the provider has determined that the
+ /// <see cref="IAuthenticationRequest.ClaimedIdentifier"/> belongs to the currently logged in user
+ /// and wishes to share this information with the consumer.
+ /// </summary>
+ bool? IAuthenticationRequest.IsAuthenticated {
+ get {
+ throw new NotImplementedException();
+ }
+
+ set {
+ throw new NotImplementedException();
+ }
+ }
+
+ #endregion
+
+ #region IHostProcessedRequest Properties
+
+ /// <summary>
+ /// Gets the version of OpenID being used by the relying party that sent the request.
+ /// </summary>
+ ProtocolVersion IHostProcessedRequest.RelyingPartyVersion {
+ get { throw new NotImplementedException(); }
+ }
+
+ /// <summary>
+ /// Gets the URL the consumer site claims to use as its 'base' address.
+ /// </summary>
+ Realm IHostProcessedRequest.Realm {
+ get { throw new NotImplementedException(); }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether the consumer demands an immediate response.
+ /// If false, the consumer is willing to wait for the identity provider
+ /// to authenticate the user.
+ /// </summary>
+ bool IHostProcessedRequest.Immediate {
+ get { throw new NotImplementedException(); }
+ }
+
+ /// <summary>
+ /// Gets or sets the provider endpoint claimed in the positive assertion.
+ /// </summary>
+ /// <value>
+ /// The default value is the URL that the request came in on from the relying party.
+ /// This value MUST match the value for the OP Endpoint in the discovery results for the
+ /// claimed identifier being asserted in a positive response.
+ /// </value>
+ Uri IHostProcessedRequest.ProviderEndpoint {
+ get {
+ throw new NotImplementedException();
+ }
+
+ set {
+ throw new NotImplementedException();
+ }
+ }
+
+ #endregion
+
+ #region IRequest Properties
+
+ /// <summary>
+ /// Gets a value indicating whether the response is ready to be sent to the user agent.
+ /// </summary>
+ /// <remarks>
+ /// This property returns false if there are properties that must be set on this
+ /// request instance before the response can be sent.
+ /// </remarks>
+ bool IRequest.IsResponseReady {
+ get { throw new NotImplementedException(); }
+ }
+
+ /// <summary>
+ /// Gets or sets the security settings that apply to this request.
+ /// </summary>
+ /// <value>
+ /// Defaults to the OpenIdProvider.SecuritySettings on the OpenIdProvider.
+ /// </value>
+ ProviderSecuritySettings IRequest.SecuritySettings {
+ get {
+ throw new NotImplementedException();
+ }
+
+ set {
+ throw new NotImplementedException();
+ }
+ }
+
+ #endregion
+
+ #region IAuthenticationRequest Methods
+
+ /// <summary>
+ /// Adds an optional fragment (#fragment) portion to the ClaimedIdentifier.
+ /// Useful for identifier recycling.
+ /// </summary>
+ /// <param name="fragment">Should not include the # prefix character as that will be added internally.
+ /// May be null or the empty string to clear a previously set fragment.</param>
+ /// <remarks>
+ /// <para>Unlike the <see cref="IAuthenticationRequest.ClaimedIdentifier"/> property, which can only be set if
+ /// using directed identity, this method can be called on any URI claimed identifier.</para>
+ /// <para>Because XRI claimed identifiers (the canonical IDs) are never recycled,
+ /// this method should<i>not</i> be called for XRIs.</para>
+ /// </remarks>
+ /// <exception cref="InvalidOperationException">
+ /// Thrown when this method is called on an XRI, or on a directed identity
+ /// request before the <see cref="IAuthenticationRequest.ClaimedIdentifier"/> property is set.
+ /// </exception>
+ void IAuthenticationRequest.SetClaimedIdentifierFragment(string fragment) {
+ Requires.ValidState(!(((IAuthenticationRequest)this).IsDirectedIdentity && ((IAuthenticationRequest)this).ClaimedIdentifier == null), OpenIdStrings.ClaimedIdentifierMustBeSetFirst);
+ Requires.ValidState(!(((IAuthenticationRequest)this).ClaimedIdentifier is XriIdentifier), OpenIdStrings.FragmentNotAllowedOnXRIs);
+
+ throw new NotImplementedException();
+ }
+
+ #endregion
+
+ #region IHostProcessedRequest Methods
+
+ /// <summary>
+ /// Attempts to perform relying party discovery of the return URL claimed by the Relying Party.
+ /// </summary>
+ /// <param name="webRequestHandler">The web request handler to use for the RP discovery request.</param>
+ /// <returns>
+ /// The details of how successful the relying party discovery was.
+ /// </returns>
+ /// <remarks>
+ /// <para>Return URL verification is only attempted if this method is called.</para>
+ /// <para>See OpenID Authentication 2.0 spec section 9.2.1.</para>
+ /// </remarks>
+ RelyingPartyDiscoveryResult IHostProcessedRequest.IsReturnUrlDiscoverable(IDirectWebRequestHandler webRequestHandler) {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+
+ #region IRequest Methods
+
+ /// <summary>
+ /// Adds an extension to the response to send to the relying party.
+ /// </summary>
+ /// <param name="extension">The extension to add to the response message.</param>
+ void IRequest.AddResponseExtension(DotNetOpenAuth.OpenId.Messages.IOpenIdMessageExtension extension) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Removes any response extensions previously added using <see cref="IRequest.AddResponseExtension"/>.
+ /// </summary>
+ /// <remarks>
+ /// This should be called before sending a negative response back to the relying party
+ /// if extensions were already added, since negative responses cannot carry extensions.
+ /// </remarks>
+ void IRequest.ClearResponseExtensions() {
+ }
+
+ /// <summary>
+ /// Gets an extension sent from the relying party.
+ /// </summary>
+ /// <typeparam name="T">The type of the extension.</typeparam>
+ /// <returns>
+ /// An instance of the extension initialized with values passed in with the request.
+ /// </returns>
+ T IRequest.GetExtension<T>() {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Gets an extension sent from the relying party.
+ /// </summary>
+ /// <param name="extensionType">The type of the extension.</param>
+ /// <returns>
+ /// An instance of the extension initialized with values passed in with the request.
+ /// </returns>
+ DotNetOpenAuth.OpenId.Messages.IOpenIdMessageExtension IRequest.GetExtension(Type extensionType) {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Provider/IHostProcessedRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Provider/IHostProcessedRequest.cs
new file mode 100644
index 0000000..236f4a8
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Provider/IHostProcessedRequest.cs
@@ -0,0 +1,202 @@
+//-----------------------------------------------------------------------
+// <copyright file="IHostProcessedRequest.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Provider {
+ using System;
+ using System.Diagnostics.Contracts;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// Interface exposing incoming messages to the OpenID Provider that
+ /// require interaction with the host site.
+ /// </summary>
+ [ContractClass(typeof(IHostProcessedRequestContract))]
+ public interface IHostProcessedRequest : IRequest {
+ /// <summary>
+ /// Gets the version of OpenID being used by the relying party that sent the request.
+ /// </summary>
+ ProtocolVersion RelyingPartyVersion { get; }
+
+ /// <summary>
+ /// Gets the URL the consumer site claims to use as its 'base' address.
+ /// </summary>
+ Realm Realm { get; }
+
+ /// <summary>
+ /// Gets a value indicating whether the consumer demands an immediate response.
+ /// If false, the consumer is willing to wait for the identity provider
+ /// to authenticate the user.
+ /// </summary>
+ bool Immediate { get; }
+
+ /// <summary>
+ /// Gets or sets the provider endpoint claimed in the positive assertion.
+ /// </summary>
+ /// <value>
+ /// The default value is the URL that the request came in on from the relying party.
+ /// This value MUST match the value for the OP Endpoint in the discovery results for the
+ /// claimed identifier being asserted in a positive response.
+ /// </value>
+ Uri ProviderEndpoint { get; set; }
+
+ /// <summary>
+ /// Attempts to perform relying party discovery of the return URL claimed by the Relying Party.
+ /// </summary>
+ /// <param name="webRequestHandler">The web request handler.</param>
+ /// <returns>
+ /// The details of how successful the relying party discovery was.
+ /// </returns>
+ /// <remarks>
+ /// <para>Return URL verification is only attempted if this method is called.</para>
+ /// <para>See OpenID Authentication 2.0 spec section 9.2.1.</para>
+ /// </remarks>
+ RelyingPartyDiscoveryResult IsReturnUrlDiscoverable(IDirectWebRequestHandler webRequestHandler);
+ }
+
+ /// <summary>
+ /// Code contract for the <see cref="IHostProcessedRequest"/> type.
+ /// </summary>
+ [ContractClassFor(typeof(IHostProcessedRequest))]
+ internal abstract class IHostProcessedRequestContract : IHostProcessedRequest {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="IHostProcessedRequestContract"/> class.
+ /// </summary>
+ protected IHostProcessedRequestContract() {
+ }
+
+ #region IHostProcessedRequest Properties
+
+ /// <summary>
+ /// Gets the version of OpenID being used by the relying party that sent the request.
+ /// </summary>
+ ProtocolVersion IHostProcessedRequest.RelyingPartyVersion {
+ get { throw new System.NotImplementedException(); }
+ }
+
+ /// <summary>
+ /// Gets the URL the consumer site claims to use as its 'base' address.
+ /// </summary>
+ Realm IHostProcessedRequest.Realm {
+ get { throw new System.NotImplementedException(); }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether the consumer demands an immediate response.
+ /// If false, the consumer is willing to wait for the identity provider
+ /// to authenticate the user.
+ /// </summary>
+ bool IHostProcessedRequest.Immediate {
+ get { throw new System.NotImplementedException(); }
+ }
+
+ /// <summary>
+ /// Gets or sets the provider endpoint.
+ /// </summary>
+ /// <value>
+ /// The default value is the URL that the request came in on from the relying party.
+ /// </value>
+ Uri IHostProcessedRequest.ProviderEndpoint {
+ get {
+ Contract.Ensures(Contract.Result<Uri>() != null);
+ throw new NotImplementedException();
+ }
+
+ set {
+ Contract.Requires(value != null);
+ throw new NotImplementedException();
+ }
+ }
+
+ #endregion
+
+ #region IRequest Members
+
+ /// <summary>
+ /// Gets or sets the security settings that apply to this request.
+ /// </summary>
+ /// <value>
+ /// Defaults to the OpenIdProvider.SecuritySettings on the OpenIdProvider.
+ /// </value>
+ ProviderSecuritySettings IRequest.SecuritySettings {
+ get { throw new NotImplementedException(); }
+ set { throw new NotImplementedException(); }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether the response is ready to be sent to the user agent.
+ /// </summary>
+ /// <remarks>
+ /// This property returns false if there are properties that must be set on this
+ /// request instance before the response can be sent.
+ /// </remarks>
+ bool IRequest.IsResponseReady {
+ get { throw new System.NotImplementedException(); }
+ }
+
+ /// <summary>
+ /// Adds an extension to the response to send to the relying party.
+ /// </summary>
+ /// <param name="extension">The extension to add to the response message.</param>
+ void IRequest.AddResponseExtension(DotNetOpenAuth.OpenId.Messages.IOpenIdMessageExtension extension) {
+ throw new System.NotImplementedException();
+ }
+
+ /// <summary>
+ /// Removes any response extensions previously added using <see cref="IRequest.AddResponseExtension"/>.
+ /// </summary>
+ /// <remarks>
+ /// This should be called before sending a negative response back to the relying party
+ /// if extensions were already added, since negative responses cannot carry extensions.
+ /// </remarks>
+ void IRequest.ClearResponseExtensions() {
+ }
+
+ /// <summary>
+ /// Gets an extension sent from the relying party.
+ /// </summary>
+ /// <typeparam name="T">The type of the extension.</typeparam>
+ /// <returns>
+ /// An instance of the extension initialized with values passed in with the request.
+ /// </returns>
+ T IRequest.GetExtension<T>() {
+ throw new System.NotImplementedException();
+ }
+
+ /// <summary>
+ /// Gets an extension sent from the relying party.
+ /// </summary>
+ /// <param name="extensionType">The type of the extension.</param>
+ /// <returns>
+ /// An instance of the extension initialized with values passed in with the request.
+ /// </returns>
+ DotNetOpenAuth.OpenId.Messages.IOpenIdMessageExtension IRequest.GetExtension(System.Type extensionType) {
+ throw new System.NotImplementedException();
+ }
+
+ #endregion
+
+ #region IHostProcessedRequest Methods
+
+ /// <summary>
+ /// Attempts to perform relying party discovery of the return URL claimed by the Relying Party.
+ /// </summary>
+ /// <param name="webRequestHandler">The web request handler.</param>
+ /// <returns>
+ /// The details of how successful the relying party discovery was.
+ /// </returns>
+ /// <remarks>
+ /// <para>Return URL verification is only attempted if this method is called.</para>
+ /// <para>See OpenID Authentication 2.0 spec section 9.2.1.</para>
+ /// </remarks>
+ RelyingPartyDiscoveryResult IHostProcessedRequest.IsReturnUrlDiscoverable(IDirectWebRequestHandler webRequestHandler) {
+ Requires.NotNull(webRequestHandler, "webRequestHandler");
+ throw new System.NotImplementedException();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Provider/IProviderBehavior.cs b/src/DotNetOpenAuth.OpenId/OpenId/Provider/IProviderBehavior.cs
new file mode 100644
index 0000000..840fe3a
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Provider/IProviderBehavior.cs
@@ -0,0 +1,114 @@
+//-----------------------------------------------------------------------
+// <copyright file="IProviderBehavior.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Provider {
+ using System;
+ using System.Diagnostics.Contracts;
+ using DotNetOpenAuth.OpenId.ChannelElements;
+
+ /// <summary>
+ /// Applies a custom security policy to certain OpenID security settings and behaviors.
+ /// </summary>
+ [ContractClass(typeof(IProviderBehaviorContract))]
+ public interface IProviderBehavior {
+ /// <summary>
+ /// Applies a well known set of security requirements to a default set of security settings.
+ /// </summary>
+ /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param>
+ /// <remarks>
+ /// Care should be taken to never decrease security when applying a profile.
+ /// Profiles should only enhance security requirements to avoid being
+ /// incompatible with each other.
+ /// </remarks>
+ void ApplySecuritySettings(ProviderSecuritySettings securitySettings);
+
+ /// <summary>
+ /// Called when a request is received by the Provider.
+ /// </summary>
+ /// <param name="request">The incoming request.</param>
+ /// <returns>
+ /// <c>true</c> if this behavior owns this request and wants to stop other behaviors
+ /// from handling it; <c>false</c> to allow other behaviors to process this request.
+ /// </returns>
+ /// <remarks>
+ /// Implementations may set a new value to <see cref="IRequest.SecuritySettings"/> but
+ /// should not change the properties on the instance of <see cref="ProviderSecuritySettings"/>
+ /// itself as that instance may be shared across many requests.
+ /// </remarks>
+ bool OnIncomingRequest(IRequest request);
+
+ /// <summary>
+ /// Called when the Provider is preparing to send a response to an authentication request.
+ /// </summary>
+ /// <param name="request">The request that is configured to generate the outgoing response.</param>
+ /// <returns>
+ /// <c>true</c> if this behavior owns this request and wants to stop other behaviors
+ /// from handling it; <c>false</c> to allow other behaviors to process this request.
+ /// </returns>
+ bool OnOutgoingResponse(IAuthenticationRequest request);
+ }
+
+ /// <summary>
+ /// Code contract for the <see cref="IProviderBehavior"/> type.
+ /// </summary>
+ [ContractClassFor(typeof(IProviderBehavior))]
+ internal abstract class IProviderBehaviorContract : IProviderBehavior {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="IProviderBehaviorContract"/> class.
+ /// </summary>
+ protected IProviderBehaviorContract() {
+ }
+
+ #region IProviderBehavior Members
+
+ /// <summary>
+ /// Applies a well known set of security requirements to a default set of security settings.
+ /// </summary>
+ /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param>
+ /// <remarks>
+ /// Care should be taken to never decrease security when applying a profile.
+ /// Profiles should only enhance security requirements to avoid being
+ /// incompatible with each other.
+ /// </remarks>
+ void IProviderBehavior.ApplySecuritySettings(ProviderSecuritySettings securitySettings) {
+ Requires.NotNull(securitySettings, "securitySettings");
+ throw new System.NotImplementedException();
+ }
+
+ /// <summary>
+ /// Called when a request is received by the Provider.
+ /// </summary>
+ /// <param name="request">The incoming request.</param>
+ /// <returns>
+ /// <c>true</c> if this behavior owns this request and wants to stop other behaviors
+ /// from handling it; <c>false</c> to allow other behaviors to process this request.
+ /// </returns>
+ /// <remarks>
+ /// Implementations may set a new value to <see cref="IRequest.SecuritySettings"/> but
+ /// should not change the properties on the instance of <see cref="ProviderSecuritySettings"/>
+ /// itself as that instance may be shared across many requests.
+ /// </remarks>
+ bool IProviderBehavior.OnIncomingRequest(IRequest request) {
+ Requires.NotNull(request, "request");
+ throw new System.NotImplementedException();
+ }
+
+ /// <summary>
+ /// Called when the Provider is preparing to send a response to an authentication request.
+ /// </summary>
+ /// <param name="request">The request that is configured to generate the outgoing response.</param>
+ /// <returns>
+ /// <c>true</c> if this behavior owns this request and wants to stop other behaviors
+ /// from handling it; <c>false</c> to allow other behaviors to process this request.
+ /// </returns>
+ bool IProviderBehavior.OnOutgoingResponse(IAuthenticationRequest request) {
+ Requires.NotNull(request, "request");
+ throw new System.NotImplementedException();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Provider/IRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/Provider/IRequest.cs
new file mode 100644
index 0000000..0e29792
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Provider/IRequest.cs
@@ -0,0 +1,149 @@
+//-----------------------------------------------------------------------
+// <copyright file="IRequest.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Provider {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// Represents an incoming OpenId authentication request.
+ /// </summary>
+ /// <remarks>
+ /// Requests may be infrastructural to OpenID and allow auto-responses, or they may
+ /// be authentication requests where the Provider site has to make decisions based
+ /// on its own user database and policies.
+ /// </remarks>
+ [ContractClass(typeof(IRequestContract))]
+ public interface IRequest {
+ /// <summary>
+ /// Gets a value indicating whether the response is ready to be sent to the user agent.
+ /// </summary>
+ /// <remarks>
+ /// This property returns false if there are properties that must be set on this
+ /// request instance before the response can be sent.
+ /// </remarks>
+ bool IsResponseReady { get; }
+
+ /// <summary>
+ /// Gets or sets the security settings that apply to this request.
+ /// </summary>
+ /// <value>Defaults to the OpenIdProvider.SecuritySettings on the OpenIdProvider.</value>
+ ProviderSecuritySettings SecuritySettings { get; set; }
+
+ /// <summary>
+ /// Adds an extension to the response to send to the relying party.
+ /// </summary>
+ /// <param name="extension">The extension to add to the response message.</param>
+ void AddResponseExtension(IOpenIdMessageExtension extension);
+
+ /// <summary>
+ /// Removes any response extensions previously added using <see cref="IRequest.AddResponseExtension"/>.
+ /// </summary>
+ /// <remarks>
+ /// This should be called before sending a negative response back to the relying party
+ /// if extensions were already added, since negative responses cannot carry extensions.
+ /// </remarks>
+ void ClearResponseExtensions();
+
+ /// <summary>
+ /// Gets an extension sent from the relying party.
+ /// </summary>
+ /// <typeparam name="T">The type of the extension.</typeparam>
+ /// <returns>An instance of the extension initialized with values passed in with the request.</returns>
+ [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "No parameter to make of type T.")]
+ T GetExtension<T>() where T : IOpenIdMessageExtension, new();
+
+ /// <summary>
+ /// Gets an extension sent from the relying party.
+ /// </summary>
+ /// <param name="extensionType">The type of the extension.</param>
+ /// <returns>An instance of the extension initialized with values passed in with the request.</returns>
+ IOpenIdMessageExtension GetExtension(Type extensionType);
+ }
+
+ /// <summary>
+ /// Code contract for the <see cref="IRequest"/> interface.
+ /// </summary>
+ [ContractClassFor(typeof(IRequest))]
+ internal abstract class IRequestContract : IRequest {
+ /// <summary>
+ /// Prevents a default instance of the <see cref="IRequestContract"/> class from being created.
+ /// </summary>
+ private IRequestContract() {
+ }
+
+ #region IRequest Members
+
+ /// <summary>
+ /// Gets or sets the security settings that apply to this request.
+ /// </summary>
+ /// <value>Defaults to the OpenIdProvider.SecuritySettings on the OpenIdProvider.</value>
+ ProviderSecuritySettings IRequest.SecuritySettings {
+ get { throw new NotImplementedException(); }
+ set { throw new NotImplementedException(); }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether the response is ready to be sent to the user agent.
+ /// </summary>
+ /// <remarks>
+ /// This property returns false if there are properties that must be set on this
+ /// request instance before the response can be sent.
+ /// </remarks>
+ bool IRequest.IsResponseReady {
+ get { throw new NotImplementedException(); }
+ }
+
+ /// <summary>
+ /// Adds an extension to the response to send to the relying party.
+ /// </summary>
+ /// <param name="extension">The extension to add to the response message.</param>
+ void IRequest.AddResponseExtension(IOpenIdMessageExtension extension) {
+ Requires.NotNull(extension, "extension");
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Removes any response extensions previously added using <see cref="IRequest.AddResponseExtension"/>.
+ /// </summary>
+ /// <remarks>
+ /// This should be called before sending a negative response back to the relying party
+ /// if extensions were already added, since negative responses cannot carry extensions.
+ /// </remarks>
+ void IRequest.ClearResponseExtensions() {
+ }
+
+ /// <summary>
+ /// Gets an extension sent from the relying party.
+ /// </summary>
+ /// <typeparam name="T">The type of the extension.</typeparam>
+ /// <returns>
+ /// An instance of the extension initialized with values passed in with the request.
+ /// </returns>
+ T IRequest.GetExtension<T>() {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Gets an extension sent from the relying party.
+ /// </summary>
+ /// <param name="extensionType">The type of the extension.</param>
+ /// <returns>
+ /// An instance of the extension initialized with values passed in with the request.
+ /// </returns>
+ IOpenIdMessageExtension IRequest.GetExtension(Type extensionType) {
+ Requires.NotNull(extensionType, "extensionType");
+ throw new NotImplementedException();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Provider/ProviderSecuritySettings.cs b/src/DotNetOpenAuth.OpenId/OpenId/Provider/ProviderSecuritySettings.cs
new file mode 100644
index 0000000..dd53b92
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Provider/ProviderSecuritySettings.cs
@@ -0,0 +1,167 @@
+//-----------------------------------------------------------------------
+// <copyright file="ProviderSecuritySettings.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Provider {
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.ObjectModel;
+ using System.Collections.Specialized;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Linq;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// Security settings that are applicable to providers.
+ /// </summary>
+ [Serializable]
+ public sealed class ProviderSecuritySettings : SecuritySettings {
+ /// <summary>
+ /// The default value for the <see cref="ProtectDownlevelReplayAttacks"/> property.
+ /// </summary>
+ internal const bool ProtectDownlevelReplayAttacksDefault = true;
+
+ /// <summary>
+ /// The default value for the <see cref="EncodeAssociationSecretsInHandles"/> property.
+ /// </summary>
+ internal const bool EncodeAssociationSecretsInHandlesDefault = true;
+
+ /// <summary>
+ /// The default value for the <see cref="SignOutgoingExtensions"/> property.
+ /// </summary>
+ internal const bool SignOutgoingExtensionsDefault = true;
+
+ /// <summary>
+ /// The default value for the <see cref="UnsolicitedAssertionVerification"/> property.
+ /// </summary>
+ internal const UnsolicitedAssertionVerificationLevel UnsolicitedAssertionVerificationDefault = UnsolicitedAssertionVerificationLevel.RequireSuccess;
+
+ /// <summary>
+ /// The subset of association types and their customized lifetimes.
+ /// </summary>
+ private IDictionary<string, TimeSpan> associationLifetimes = new Dictionary<string, TimeSpan>();
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ProviderSecuritySettings"/> class.
+ /// </summary>
+ internal ProviderSecuritySettings()
+ : base(true) {
+ this.SignOutgoingExtensions = SignOutgoingExtensionsDefault;
+ this.ProtectDownlevelReplayAttacks = ProtectDownlevelReplayAttacksDefault;
+ this.UnsolicitedAssertionVerification = UnsolicitedAssertionVerificationDefault;
+ }
+
+ /// <summary>
+ /// The behavior a Provider takes when verifying that it is authoritative for an
+ /// identifier it is about to send an unsolicited assertion for.
+ /// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "By design")]
+ public enum UnsolicitedAssertionVerificationLevel {
+ /// <summary>
+ /// Always verify that the Provider is authoritative for an identifier before
+ /// sending an unsolicited assertion for it and fail if it is not.
+ /// </summary>
+ RequireSuccess,
+
+ /// <summary>
+ /// Always check that the Provider is authoritative for an identifier before
+ /// sending an unsolicited assertion for it, but only log failures, and proceed
+ /// to send the unsolicited assertion.
+ /// </summary>
+ LogWarningOnFailure,
+
+ /// <summary>
+ /// Never verify that the Provider is authoritative for an identifier before
+ /// sending an unsolicited assertion for it.
+ /// </summary>
+ /// <remarks>
+ /// This setting is useful for web servers that refuse to allow a Provider to
+ /// introspectively perform an HTTP GET on itself, when sending unsolicited assertions
+ /// for identifiers that the OP controls.
+ /// </remarks>
+ NeverVerify,
+ }
+
+ /// <summary>
+ /// Gets a subset of the available association types and their
+ /// customized maximum lifetimes.
+ /// </summary>
+ public IDictionary<string, TimeSpan> AssociationLifetimes {
+ get { return this.associationLifetimes; }
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether Relying Party discovery will only
+ /// succeed if done over a secure HTTPS channel.
+ /// </summary>
+ /// <value>Default is <c>false</c>.</value>
+ public bool RequireSsl { get; set; }
+
+ /// <summary>
+ /// Gets or sets the level of verification a Provider performs on an identifier before
+ /// sending an unsolicited assertion for it.
+ /// </summary>
+ /// <value>The default value is <see cref="UnsolicitedAssertionVerificationLevel.RequireSuccess"/>.</value>
+ public UnsolicitedAssertionVerificationLevel UnsolicitedAssertionVerification { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the Provider should ease the burden of storing associations
+ /// by encoding them in signed, encrypted form into the association handles themselves, storing only
+ /// a few rotating, private symmetric keys in the Provider's store instead.
+ /// </summary>
+ /// <value>The default value for this property is <c>true</c>.</value>
+ public bool EncodeAssociationSecretsInHandles { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether OpenID 1.x relying parties that may not be
+ /// protecting their users from replay attacks are protected from
+ /// replay attacks by this provider.
+ /// </summary>
+ /// <value>The default value is <c>true</c>.</value>
+ /// <remarks>
+ /// <para>Nonces for protection against replay attacks were not mandated
+ /// by OpenID 1.x, which leaves users open to replay attacks.</para>
+ /// <para>This feature works by preventing associations from being used
+ /// with OpenID 1.x relying parties, thereby forcing them into
+ /// "dumb" mode and verifying every claim with this provider.
+ /// This gives the provider an opportunity to verify its own nonce
+ /// to protect against replay attacks.</para>
+ /// </remarks>
+ internal bool ProtectDownlevelReplayAttacks { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether outgoing extensions are always signed.
+ /// </summary>
+ /// <value>
+ /// <c>true</c> if outgoing extensions should be signed; otherwise, <c>false</c>.
+ /// The default is <c>true</c>.
+ /// </value>
+ /// <remarks>
+ /// This property is internal because Providers should never turn it off, but it is
+ /// needed for testing the RP's rejection of unsigned extensions.
+ /// </remarks>
+ internal bool SignOutgoingExtensions { get; set; }
+
+ /// <summary>
+ /// Creates a deep clone of this instance.
+ /// </summary>
+ /// <returns>A new instance that is a deep clone of this instance.</returns>
+ internal ProviderSecuritySettings Clone() {
+ var securitySettings = new ProviderSecuritySettings();
+ foreach (var pair in this.AssociationLifetimes) {
+ securitySettings.AssociationLifetimes.Add(pair);
+ }
+
+ securitySettings.MaximumHashBitLength = this.MaximumHashBitLength;
+ securitySettings.MinimumHashBitLength = this.MinimumHashBitLength;
+ securitySettings.ProtectDownlevelReplayAttacks = this.ProtectDownlevelReplayAttacks;
+ securitySettings.RequireSsl = this.RequireSsl;
+ securitySettings.SignOutgoingExtensions = this.SignOutgoingExtensions;
+ securitySettings.UnsolicitedAssertionVerification = this.UnsolicitedAssertionVerification;
+
+ return securitySettings;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Provider/RelyingPartyDiscoveryResult.cs b/src/DotNetOpenAuth.OpenId/OpenId/Provider/RelyingPartyDiscoveryResult.cs
new file mode 100644
index 0000000..2a8e6c1
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Provider/RelyingPartyDiscoveryResult.cs
@@ -0,0 +1,36 @@
+//-----------------------------------------------------------------------
+// <copyright file="RelyingPartyDiscoveryResult.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Provider {
+ /// <summary>
+ /// The result codes that may be returned from an attempt at relying party discovery.
+ /// </summary>
+ public enum RelyingPartyDiscoveryResult {
+ /// <summary>
+ /// Relying Party discovery failed to find an XRDS document or the document was invalid.
+ /// </summary>
+ /// <remarks>
+ /// This can happen either when a relying party does not offer a service document at all,
+ /// or when a man-in-the-middle attack is in progress that prevents the Provider from being
+ /// able to discover that document.
+ /// </remarks>
+ NoServiceDocument,
+
+ /// <summary>
+ /// Relying Party discovery yielded a valid XRDS document, but no matching return_to URI was found.
+ /// </summary>
+ /// <remarks>
+ /// This is perhaps the most dangerous rating for a relying party, since it suggests that
+ /// they are implementing OpenID 2.0 securely, but that a hijack operation may be in progress.
+ /// </remarks>
+ NoMatchingReturnTo,
+
+ /// <summary>
+ /// Relying Party discovery succeeded, and a matching return_to URI was found.
+ /// </summary>
+ Success,
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/ProviderEndpointDescription.cs b/src/DotNetOpenAuth.OpenId/OpenId/ProviderEndpointDescription.cs
new file mode 100644
index 0000000..7cac8c6
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/ProviderEndpointDescription.cs
@@ -0,0 +1,134 @@
+//-----------------------------------------------------------------------
+// <copyright file="ProviderEndpointDescription.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId {
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.ObjectModel;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+
+ /// <summary>
+ /// Describes some OpenID Provider endpoint and its capabilities.
+ /// </summary>
+ /// <remarks>
+ /// This is an immutable type.
+ /// </remarks>
+ [Serializable]
+ internal sealed class ProviderEndpointDescription : IProviderEndpoint {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ProviderEndpointDescription"/> class.
+ /// </summary>
+ /// <param name="providerEndpoint">The OpenID Provider endpoint URL.</param>
+ /// <param name="openIdVersion">The OpenID version supported by this particular endpoint.</param>
+ internal ProviderEndpointDescription(Uri providerEndpoint, Version openIdVersion) {
+ Requires.NotNull(providerEndpoint, "providerEndpoint");
+ Requires.NotNull(openIdVersion, "openIdVersion");
+
+ this.Uri = providerEndpoint;
+ this.Version = openIdVersion;
+ this.Capabilities = new ReadOnlyCollection<string>(EmptyList<string>.Instance);
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ProviderEndpointDescription"/> class.
+ /// </summary>
+ /// <param name="providerEndpoint">The URI the provider listens on for OpenID requests.</param>
+ /// <param name="serviceTypeURIs">The set of services offered by this endpoint.</param>
+ internal ProviderEndpointDescription(Uri providerEndpoint, IEnumerable<string> serviceTypeURIs) {
+ Requires.NotNull(providerEndpoint, "providerEndpoint");
+ Requires.NotNull(serviceTypeURIs, "serviceTypeURIs");
+
+ this.Uri = providerEndpoint;
+ this.Capabilities = new ReadOnlyCollection<string>(serviceTypeURIs.ToList());
+
+ Protocol opIdentifierProtocol = Protocol.FindBestVersion(p => p.OPIdentifierServiceTypeURI, serviceTypeURIs);
+ Protocol claimedIdentifierProviderVersion = Protocol.FindBestVersion(p => p.ClaimedIdentifierServiceTypeURI, serviceTypeURIs);
+ if (opIdentifierProtocol != null) {
+ this.Version = opIdentifierProtocol.Version;
+ } else if (claimedIdentifierProviderVersion != null) {
+ this.Version = claimedIdentifierProviderVersion.Version;
+ } else {
+ ErrorUtilities.ThrowProtocol(OpenIdStrings.ProviderVersionUnrecognized, this.Uri);
+ }
+ }
+
+ /// <summary>
+ /// Gets the URL that the OpenID Provider listens for incoming OpenID messages on.
+ /// </summary>
+ public Uri Uri { get; private set; }
+
+ /// <summary>
+ /// Gets the OpenID protocol version this endpoint supports.
+ /// </summary>
+ /// <remarks>
+ /// If an endpoint supports multiple versions, each version must be represented
+ /// by its own <see cref="ProviderEndpointDescription"/> object.
+ /// </remarks>
+ public Version Version { get; private set; }
+
+ /// <summary>
+ /// Gets the collection of service type URIs found in the XRDS document describing this Provider.
+ /// </summary>
+ internal ReadOnlyCollection<string> Capabilities { get; private set; }
+
+ #region IProviderEndpoint Members
+
+ /// <summary>
+ /// Checks whether the OpenId Identifier claims support for a given extension.
+ /// </summary>
+ /// <typeparam name="T">The extension whose support is being queried.</typeparam>
+ /// <returns>
+ /// True if support for the extension is advertised. False otherwise.
+ /// </returns>
+ /// <remarks>
+ /// Note that a true or false return value is no guarantee of a Provider's
+ /// support for or lack of support for an extension. The return value is
+ /// determined by how the authenticating user filled out his/her XRDS document only.
+ /// The only way to be sure of support for a given extension is to include
+ /// the extension in the request and see if a response comes back for that extension.
+ /// </remarks>
+ bool IProviderEndpoint.IsExtensionSupported<T>() {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Checks whether the OpenId Identifier claims support for a given extension.
+ /// </summary>
+ /// <param name="extensionType">The extension whose support is being queried.</param>
+ /// <returns>
+ /// True if support for the extension is advertised. False otherwise.
+ /// </returns>
+ /// <remarks>
+ /// Note that a true or false return value is no guarantee of a Provider's
+ /// support for or lack of support for an extension. The return value is
+ /// determined by how the authenticating user filled out his/her XRDS document only.
+ /// The only way to be sure of support for a given extension is to include
+ /// the extension in the request and see if a response comes back for that extension.
+ /// </remarks>
+ bool IProviderEndpoint.IsExtensionSupported(Type extensionType) {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+
+#if CONTRACTS_FULL
+ /// <summary>
+ /// Verifies conditions that should be true for any valid state of this object.
+ /// </summary>
+ [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")]
+ [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")]
+ [ContractInvariantMethod]
+ private void ObjectInvariant() {
+ Contract.Invariant(this.Capabilities != null);
+ }
+#endif
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/Realm.cs b/src/DotNetOpenAuth.OpenId/OpenId/Realm.cs
new file mode 100644
index 0000000..8f1baed
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/Realm.cs
@@ -0,0 +1,534 @@
+//-----------------------------------------------------------------------
+// <copyright file="Realm.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.Linq;
+ using System.Text.RegularExpressions;
+ using System.Web;
+ using System.Xml;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Reflection;
+ using DotNetOpenAuth.Xrds;
+ using DotNetOpenAuth.Yadis;
+
+ /// <summary>
+ /// A trust root to validate requests and match return URLs against.
+ /// </summary>
+ /// <remarks>
+ /// This fills the OpenID Authentication 2.0 specification for realms.
+ /// See http://openid.net/specs/openid-authentication-2_0.html#realms
+ /// </remarks>
+ [Serializable]
+ [Pure]
+ [DefaultEncoder(typeof(MessagePartRealmConverter))]
+ public class Realm {
+ /// <summary>
+ /// A regex used to detect a wildcard that is being used in the realm.
+ /// </summary>
+ private const string WildcardDetectionPattern = @"^(\w+://)\*\.";
+
+ /// <summary>
+ /// A (more or less) comprehensive list of top-level (i.e. ".com") domains,
+ /// for use by <see cref="IsSane"/> in order to disallow overly-broad realms
+ /// that allow all web sites ending with '.com', for example.
+ /// </summary>
+ private static readonly string[] topLevelDomains = { "com", "edu", "gov", "int", "mil", "net", "org", "biz", "info", "name", "museum", "coop", "aero", "ac", "ad", "ae",
+ "af", "ag", "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au", "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi", "bj",
+ "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by", "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl", "cm", "cn", "co", "cr",
+ "cu", "cv", "cx", "cy", "cz", "de", "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er", "es", "et", "fi", "fj", "fk", "fm", "fo",
+ "fr", "ga", "gd", "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq", "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
+ "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir", "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki", "km", "kn", "kp",
+ "kr", "kw", "ky", "kz", "la", "lb", "lc", "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc", "md", "mg", "mh", "mk", "ml", "mm",
+ "mn", "mo", "mp", "mq", "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na", "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr",
+ "nu", "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm", "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru", "rw", "sa",
+ "sb", "sc", "sd", "se", "sg", "sh", "si", "sj", "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz", "tc", "td", "tf", "tg", "th",
+ "tj", "tk", "tm", "tn", "to", "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um", "us", "uy", "uz", "va", "vc", "ve", "vg", "vi",
+ "vn", "vu", "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw" };
+
+ /// <summary>
+ /// The Uri of the realm, with the wildcard (if any) removed.
+ /// </summary>
+ private Uri uri;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Realm"/> class.
+ /// </summary>
+ /// <param name="realmUrl">The realm URL to use in the new instance.</param>
+ [SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", Justification = "Not all realms are valid URLs (because of wildcards).")]
+ public Realm(string realmUrl) {
+ Requires.NotNull(realmUrl, "realmUrl"); // not non-zero check so we throw UriFormatException later
+ this.DomainWildcard = Regex.IsMatch(realmUrl, WildcardDetectionPattern);
+ this.uri = new Uri(Regex.Replace(realmUrl, WildcardDetectionPattern, m => m.Groups[1].Value));
+ if (!this.uri.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase) &&
+ !this.uri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase)) {
+ throw new UriFormatException(
+ string.Format(CultureInfo.CurrentCulture, OpenIdStrings.InvalidScheme, this.uri.Scheme));
+ }
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Realm"/> class.
+ /// </summary>
+ /// <param name="realmUrl">The realm URL of the Relying Party.</param>
+ public Realm(Uri realmUrl) {
+ Requires.NotNull(realmUrl, "realmUrl");
+ this.uri = realmUrl;
+ if (!this.uri.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase) &&
+ !this.uri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase)) {
+ throw new UriFormatException(
+ string.Format(CultureInfo.CurrentCulture, OpenIdStrings.InvalidScheme, this.uri.Scheme));
+ }
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Realm"/> class.
+ /// </summary>
+ /// <param name="realmUriBuilder">The realm URI builder.</param>
+ /// <remarks>
+ /// This is useful because UriBuilder can construct a host with a wildcard
+ /// in the Host property, but once there it can't be converted to a Uri.
+ /// </remarks>
+ internal Realm(UriBuilder realmUriBuilder)
+ : this(SafeUriBuilderToString(realmUriBuilder)) { }
+
+ /// <summary>
+ /// Gets the suggested realm to use for the calling web application.
+ /// </summary>
+ /// <value>A realm that matches this applications root URL.</value>
+ /// <remarks>
+ /// <para>For most circumstances the Realm generated by this property is sufficient.
+ /// However a wildcard Realm, such as "http://*.microsoft.com/" may at times be more
+ /// desirable than "http://www.microsoft.com/" in order to allow identifier
+ /// correlation across related web sites for directed identity Providers.</para>
+ /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para>
+ /// </remarks>
+ public static Realm AutoDetect {
+ get {
+ Requires.ValidState(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired);
+ Contract.Ensures(Contract.Result<Realm>() != null);
+
+ var realmUrl = new UriBuilder(MessagingUtilities.GetWebRoot());
+
+ // For RP discovery, the realm url MUST NOT redirect. To prevent this for
+ // virtual directory hosted apps, we need to make sure that the realm path ends
+ // in a slash (since our calculation above guarantees it doesn't end in a specific
+ // page like default.aspx).
+ if (!realmUrl.Path.EndsWith("/", StringComparison.Ordinal)) {
+ realmUrl.Path += "/";
+ }
+
+ return realmUrl.Uri;
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether a '*.' prefix to the hostname is
+ /// used in the realm to allow subdomains or hosts to be added to the URL.
+ /// </summary>
+ public bool DomainWildcard { get; private set; }
+
+ /// <summary>
+ /// Gets the host component of this instance.
+ /// </summary>
+ public string Host {
+ [DebuggerStepThrough]
+ get { return this.uri.Host; }
+ }
+
+ /// <summary>
+ /// Gets the scheme name for this URI.
+ /// </summary>
+ public string Scheme {
+ [DebuggerStepThrough]
+ get { return this.uri.Scheme; }
+ }
+
+ /// <summary>
+ /// Gets the port number of this URI.
+ /// </summary>
+ public int Port {
+ [DebuggerStepThrough]
+ get { return this.uri.Port; }
+ }
+
+ /// <summary>
+ /// Gets the absolute path of the URI.
+ /// </summary>
+ public string AbsolutePath {
+ [DebuggerStepThrough]
+ get { return this.uri.AbsolutePath; }
+ }
+
+ /// <summary>
+ /// Gets the System.Uri.AbsolutePath and System.Uri.Query properties separated
+ /// by a question mark (?).
+ /// </summary>
+ public string PathAndQuery {
+ [DebuggerStepThrough]
+ get { return this.uri.PathAndQuery; }
+ }
+
+ /// <summary>
+ /// Gets the original string.
+ /// </summary>
+ /// <value>The original string.</value>
+ internal string OriginalString {
+ get { return this.uri.OriginalString; }
+ }
+
+ /// <summary>
+ /// Gets the realm URL. If the realm includes a wildcard, it is not included here.
+ /// </summary>
+ internal Uri NoWildcardUri {
+ [DebuggerStepThrough]
+ get { return this.uri; }
+ }
+
+ /// <summary>
+ /// Gets the Realm discovery URL, where the wildcard (if present) is replaced with "www.".
+ /// </summary>
+ /// <remarks>
+ /// See OpenID 2.0 spec section 9.2.1 for the explanation on the addition of
+ /// the "www" prefix.
+ /// </remarks>
+ internal Uri UriWithWildcardChangedToWww {
+ get {
+ if (this.DomainWildcard) {
+ UriBuilder builder = new UriBuilder(this.NoWildcardUri);
+ builder.Host = "www." + builder.Host;
+ return builder.Uri;
+ } else {
+ return this.NoWildcardUri;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this realm represents a reasonable (sane) set of URLs.
+ /// </summary>
+ /// <remarks>
+ /// 'http://*.com/', for example is not a reasonable pattern, as it cannot meaningfully
+ /// specify the site claiming it. This function attempts to find many related examples,
+ /// but it can only work via heuristics. Negative responses from this method should be
+ /// treated as advisory, used only to alert the user to examine the trust root carefully.
+ /// </remarks>
+ internal bool IsSane {
+ get {
+ if (this.Host.Equals("localhost", StringComparison.OrdinalIgnoreCase)) {
+ return true;
+ }
+
+ string[] host_parts = this.Host.Split('.');
+
+ string tld = host_parts[host_parts.Length - 1];
+
+ if (Array.IndexOf(topLevelDomains, tld) < 0) {
+ return false;
+ }
+
+ if (tld.Length == 2) {
+ if (host_parts.Length == 1) {
+ return false;
+ }
+
+ if (host_parts[host_parts.Length - 2].Length <= 3) {
+ return host_parts.Length > 2;
+ }
+ } else {
+ return host_parts.Length > 1;
+ }
+
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Implicitly converts the string-form of a URI to a <see cref="Realm"/> object.
+ /// </summary>
+ /// <param name="uri">The URI that the new Realm instance will represent.</param>
+ /// <returns>The result of the conversion.</returns>
+ [SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", Justification = "Not all realms are valid URLs (because of wildcards).")]
+ [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Not all Realms are valid URLs.")]
+ [DebuggerStepThrough]
+ public static implicit operator Realm(string uri) {
+ Contract.Ensures((Contract.Result<Realm>() != null) == (uri != null));
+ return uri != null ? new Realm(uri) : null;
+ }
+
+ /// <summary>
+ /// Implicitly converts a <see cref="Uri"/> to a <see cref="Realm"/> object.
+ /// </summary>
+ /// <param name="uri">The URI to convert to a realm.</param>
+ /// <returns>The result of the conversion.</returns>
+ [DebuggerStepThrough]
+ public static implicit operator Realm(Uri uri) {
+ Contract.Ensures((Contract.Result<Realm>() != null) == (uri != null));
+ return uri != null ? new Realm(uri) : null;
+ }
+
+ /// <summary>
+ /// Implicitly converts a <see cref="Realm"/> object to its <see cref="String"/> form.
+ /// </summary>
+ /// <param name="realm">The realm to convert to a string value.</param>
+ /// <returns>The result of the conversion.</returns>
+ [DebuggerStepThrough]
+ public static implicit operator string(Realm realm) {
+ Contract.Ensures((Contract.Result<string>() != null) == (realm != null));
+ return realm != null ? realm.ToString() : null;
+ }
+
+ /// <summary>
+ /// Checks whether one <see cref="Realm"/> is equal to another.
+ /// </summary>
+ /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
+ /// <returns>
+ /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
+ /// </returns>
+ /// <exception cref="T:System.NullReferenceException">
+ /// The <paramref name="obj"/> parameter is null.
+ /// </exception>
+ public override bool Equals(object obj) {
+ Realm other = obj as Realm;
+ if (other == null) {
+ return false;
+ }
+ return this.uri.Equals(other.uri) && this.DomainWildcard == other.DomainWildcard;
+ }
+
+ /// <summary>
+ /// Returns the hash code used for storing this object in a hash table.
+ /// </summary>
+ /// <returns>
+ /// A hash code for the current <see cref="T:System.Object"/>.
+ /// </returns>
+ public override int GetHashCode() {
+ return this.uri.GetHashCode() + (this.DomainWildcard ? 1 : 0);
+ }
+
+ /// <summary>
+ /// Returns the string form of this <see cref="Realm"/>.
+ /// </summary>
+ /// <returns>
+ /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
+ /// </returns>
+ public override string ToString() {
+ if (this.DomainWildcard) {
+ UriBuilder builder = new UriBuilder(this.uri);
+ builder.Host = "*." + builder.Host;
+ return builder.ToStringWithImpliedPorts();
+ } else {
+ return this.uri.AbsoluteUri;
+ }
+ }
+
+ /// <summary>
+ /// Validates a URL against this trust root.
+ /// </summary>
+ /// <param name="url">A string specifying URL to check.</param>
+ /// <returns>Whether the given URL is within this trust root.</returns>
+ internal bool Contains(string url) {
+ return this.Contains(new Uri(url));
+ }
+
+ /// <summary>
+ /// Validates a URL against this trust root.
+ /// </summary>
+ /// <param name="url">The URL to check.</param>
+ /// <returns>Whether the given URL is within this trust root.</returns>
+ internal bool Contains(Uri url) {
+ if (url.Scheme != this.Scheme) {
+ return false;
+ }
+
+ if (url.Port != this.Port) {
+ return false;
+ }
+
+ if (!this.DomainWildcard) {
+ if (url.Host != this.Host) {
+ return false;
+ }
+ } else {
+ Debug.Assert(!string.IsNullOrEmpty(this.Host), "The host part of the Regex should evaluate to at least one char for successful parsed trust roots.");
+ string[] host_parts = this.Host.Split('.');
+ string[] url_parts = url.Host.Split('.');
+
+ // If the domain containing the wildcard has more parts than the URL to match against,
+ // it naturally can't be valid.
+ // Unless *.example.com actually matches example.com too.
+ if (host_parts.Length > url_parts.Length) {
+ return false;
+ }
+
+ // Compare last part first and move forward.
+ // Maybe could be done by using EndsWith, but piecewies helps ensure that
+ // *.my.com doesn't match ohmeohmy.com but can still match my.com.
+ for (int i = 0; i < host_parts.Length; i++) {
+ string hostPart = host_parts[host_parts.Length - 1 - i];
+ string urlPart = url_parts[url_parts.Length - 1 - i];
+ if (!string.Equals(hostPart, urlPart, StringComparison.OrdinalIgnoreCase)) {
+ return false;
+ }
+ }
+ }
+
+ // If path matches or is specified to root ...
+ // (deliberately case sensitive to protect security on case sensitive systems)
+ if (this.PathAndQuery.Equals(url.PathAndQuery, StringComparison.Ordinal)
+ || this.PathAndQuery.Equals("/", StringComparison.Ordinal)) {
+ return true;
+ }
+
+ // If trust root has a longer path, the return URL must be invalid.
+ if (this.PathAndQuery.Length > url.PathAndQuery.Length) {
+ return false;
+ }
+
+ // The following code assures that http://example.com/directory isn't below http://example.com/dir,
+ // but makes sure http://example.com/dir/ectory is below http://example.com/dir
+ int path_len = this.PathAndQuery.Length;
+ string url_prefix = url.PathAndQuery.Substring(0, path_len);
+
+ if (this.PathAndQuery != url_prefix) {
+ return false;
+ }
+
+ // If trust root includes a query string ...
+ if (this.PathAndQuery.Contains("?")) {
+ // ... make sure return URL begins with a new argument
+ return url.PathAndQuery[path_len] == '&';
+ }
+
+ // Or make sure a query string is introduced or a path below trust root
+ return this.PathAndQuery.EndsWith("/", StringComparison.Ordinal)
+ || url.PathAndQuery[path_len] == '?'
+ || url.PathAndQuery[path_len] == '/';
+ }
+
+ /// <summary>
+ /// Searches for an XRDS document at the realm URL, and if found, searches
+ /// for a description of a relying party endpoints (OpenId login pages).
+ /// </summary>
+ /// <param name="requestHandler">The mechanism to use for sending HTTP requests.</param>
+ /// <param name="allowRedirects">Whether redirects may be followed when discovering the Realm.
+ /// This may be true when creating an unsolicited assertion, but must be
+ /// false when performing return URL verification per 2.0 spec section 9.2.1.</param>
+ /// <returns>
+ /// The details of the endpoints if found; or <c>null</c> if no service document was discovered.
+ /// </returns>
+ internal virtual IEnumerable<RelyingPartyEndpointDescription> DiscoverReturnToEndpoints(IDirectWebRequestHandler requestHandler, bool allowRedirects) {
+ XrdsDocument xrds = this.Discover(requestHandler, allowRedirects);
+ if (xrds != null) {
+ return xrds.FindRelyingPartyReceivingEndpoints();
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Searches for an XRDS document at the realm URL.
+ /// </summary>
+ /// <param name="requestHandler">The mechanism to use for sending HTTP requests.</param>
+ /// <param name="allowRedirects">Whether redirects may be followed when discovering the Realm.
+ /// This may be true when creating an unsolicited assertion, but must be
+ /// false when performing return URL verification per 2.0 spec section 9.2.1.</param>
+ /// <returns>
+ /// The XRDS document if found; or <c>null</c> if no service document was discovered.
+ /// </returns>
+ internal virtual XrdsDocument Discover(IDirectWebRequestHandler requestHandler, bool allowRedirects) {
+ // Attempt YADIS discovery
+ DiscoveryResult yadisResult = Yadis.Discover(requestHandler, this.UriWithWildcardChangedToWww, false);
+ if (yadisResult != null) {
+ // Detect disallowed redirects, since realm discovery never allows them for security.
+ ErrorUtilities.VerifyProtocol(allowRedirects || yadisResult.NormalizedUri == yadisResult.RequestUri, OpenIdStrings.RealmCausedRedirectUponDiscovery, yadisResult.RequestUri);
+ if (yadisResult.IsXrds) {
+ try {
+ return new XrdsDocument(yadisResult.ResponseText);
+ } catch (XmlException ex) {
+ throw ErrorUtilities.Wrap(ex, XrdsStrings.InvalidXRDSDocument);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Calls <see cref="UriBuilder.ToString"/> if the argument is non-null.
+ /// Otherwise throws <see cref="ArgumentNullException"/>.
+ /// </summary>
+ /// <param name="realmUriBuilder">The realm URI builder.</param>
+ /// <returns>The result of UriBuilder.ToString()</returns>
+ /// <remarks>
+ /// This simple method is worthwhile because it checks for null
+ /// before dereferencing the UriBuilder. Since this is called from
+ /// within a constructor's base(...) call, this avoids a <see cref="NullReferenceException"/>
+ /// when we should be throwing an <see cref="ArgumentNullException"/>.
+ /// </remarks>
+ private static string SafeUriBuilderToString(UriBuilder realmUriBuilder) {
+ Requires.NotNull(realmUriBuilder, "realmUriBuilder");
+
+ // Note: we MUST use ToString. Uri property throws if wildcard is present.
+ // Note that Uri.ToString() should generally be avoided, but UriBuilder.ToString()
+ // is safe: http://blog.nerdbank.net/2008/04/uriabsoluteuri-and-uritostring-are-not.html
+ return realmUriBuilder.ToString();
+ }
+
+#if CONTRACTS_FULL
+ /// <summary>
+ /// Verifies conditions that should be true for any valid state of this object.
+ /// </summary>
+ [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")]
+ [ContractInvariantMethod]
+ private void ObjectInvariant() {
+ Contract.Invariant(this.uri != null);
+ Contract.Invariant(this.uri.AbsoluteUri != null);
+ }
+#endif
+
+ /// <summary>
+ /// Provides conversions to and from strings for messages that include members of this type.
+ /// </summary>
+ private class MessagePartRealmConverter : IMessagePartOriginalEncoder {
+ /// <summary>
+ /// Encodes the specified value.
+ /// </summary>
+ /// <param name="value">The value. Guaranteed to never be null.</param>
+ /// <returns>The <paramref name="value"/> in string form, ready for message transport.</returns>
+ public string Encode(object value) {
+ Requires.NotNull(value, "value");
+ return value.ToString();
+ }
+
+ /// <summary>
+ /// Decodes the specified value.
+ /// </summary>
+ /// <param name="value">The string value carried by the transport. Guaranteed to never be null, although it may be empty.</param>
+ /// <returns>The deserialized form of the given string.</returns>
+ /// <exception cref="FormatException">Thrown when the string value given cannot be decoded into the required object type.</exception>
+ public object Decode(string value) {
+ Requires.NotNull(value, "value");
+ return new Realm(value);
+ }
+
+ /// <summary>
+ /// Encodes the specified value as the original value that was formerly decoded.
+ /// </summary>
+ /// <param name="value">The value. Guaranteed to never be null.</param>
+ /// <returns>The <paramref name="value"/> in string form, ready for message transport.</returns>
+ public string EncodeAsOriginalString(object value) {
+ Requires.NotNull(value, "value");
+ return ((Realm)value).OriginalString;
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/AuthenticationStatus.cs b/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/AuthenticationStatus.cs
new file mode 100644
index 0000000..56f6d01
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/AuthenticationStatus.cs
@@ -0,0 +1,43 @@
+//-----------------------------------------------------------------------
+// <copyright file="AuthenticationStatus.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.RelyingParty {
+ /// <summary>
+ /// An enumeration of the possible results of an authentication attempt.
+ /// </summary>
+ public enum AuthenticationStatus {
+ /// <summary>
+ /// The authentication was canceled by the user agent while at the provider.
+ /// </summary>
+ Canceled,
+
+ /// <summary>
+ /// The authentication failed because an error was detected in the OpenId communication.
+ /// </summary>
+ Failed,
+
+ /// <summary>
+ /// <para>The Provider responded to a request for immediate authentication approval
+ /// with a message stating that additional user agent interaction is required
+ /// before authentication can be completed.</para>
+ /// <para>Casting the <see cref="IAuthenticationResponse"/> to a
+ /// ISetupRequiredAuthenticationResponse in this case can help
+ /// you retry the authentication using setup (non-immediate) mode.</para>
+ /// </summary>
+ SetupRequired,
+
+ /// <summary>
+ /// Authentication is completed successfully.
+ /// </summary>
+ Authenticated,
+
+ /// <summary>
+ /// The Provider sent a message that did not contain an identity assertion,
+ /// but may carry OpenID extensions.
+ /// </summary>
+ ExtensionsOnly,
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/IAuthenticationRequest.cs b/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/IAuthenticationRequest.cs
new file mode 100644
index 0000000..92e3233
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/IAuthenticationRequest.cs
@@ -0,0 +1,186 @@
+//-----------------------------------------------------------------------
+// <copyright file="IAuthenticationRequest.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.RelyingParty {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// Instances of this interface represent relying party authentication
+ /// requests that may be queried/modified in specific ways before being
+ /// routed to the OpenID Provider.
+ /// </summary>
+ [ContractClass(typeof(IAuthenticationRequestContract))]
+ public interface IAuthenticationRequest {
+ /// <summary>
+ /// Gets or sets the mode the Provider should use during authentication.
+ /// </summary>
+ AuthenticationRequestMode Mode { get; set; }
+
+ /// <summary>
+ /// Gets the HTTP response the relying party should send to the user agent
+ /// to redirect it to the OpenID Provider to start the OpenID authentication process.
+ /// </summary>
+ OutgoingWebResponse RedirectingResponse { get; }
+
+ /// <summary>
+ /// Gets the URL that the user agent will return to after authentication
+ /// completes or fails at the Provider.
+ /// </summary>
+ Uri ReturnToUrl { get; }
+
+ /// <summary>
+ /// Gets the URL that identifies this consumer web application that
+ /// the Provider will display to the end user.
+ /// </summary>
+ Realm Realm { get; }
+
+ /// <summary>
+ /// Gets the Claimed Identifier that the User Supplied Identifier
+ /// resolved to. Null if the user provided an OP Identifier
+ /// (directed identity).
+ /// </summary>
+ /// <remarks>
+ /// Null is returned if the user is using the directed identity feature
+ /// of OpenID 2.0 to make it nearly impossible for a relying party site
+ /// to improperly store the reserved OpenID URL used for directed identity
+ /// as a user's own Identifier.
+ /// However, to test for the Directed Identity feature, please test the
+ /// <see cref="IsDirectedIdentity"/> property rather than testing this
+ /// property for a null value.
+ /// </remarks>
+ Identifier ClaimedIdentifier { get; }
+
+ /// <summary>
+ /// Gets a value indicating whether the authenticating user has chosen to let the Provider
+ /// determine and send the ClaimedIdentifier after authentication.
+ /// </summary>
+ bool IsDirectedIdentity { get; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this request only carries extensions
+ /// and is not a request to verify that the user controls some identifier.
+ /// </summary>
+ /// <value>
+ /// <c>true</c> if this request is merely a carrier of extensions and is not
+ /// about an OpenID identifier; otherwise, <c>false</c>.
+ /// </value>
+ /// <remarks>
+ /// <para>Although OpenID is first and primarily an authentication protocol, its extensions
+ /// can be interesting all by themselves. For instance, a relying party might want
+ /// to know that its user is over 21 years old, or perhaps a member of some organization.
+ /// OpenID extensions can provide this, without any need for asserting the identity of the user.</para>
+ /// <para>Constructing an OpenID request for only extensions can be done by calling
+ /// OpenIdRelyingParty.CreateRequest with any valid OpenID identifier
+ /// (claimed identifier or OP identifier). But once this property is set to <c>true</c>,
+ /// the claimed identifier value in the request is not included in the transmitted message.</para>
+ /// <para>It is anticipated that an RP would only issue these types of requests to OPs that
+ /// trusts to make assertions regarding the individual holding an account at that OP, so it
+ /// is not likely that the RP would allow the user to type in an arbitrary claimed identifier
+ /// without checking that it resolved to an OP endpoint the RP has on a trust whitelist.</para>
+ /// </remarks>
+ bool IsExtensionOnly { get; set; }
+
+ /// <summary>
+ /// Gets information about the OpenId Provider, as advertised by the
+ /// OpenID discovery documents found at the <see cref="ClaimedIdentifier"/>
+ /// location.
+ /// </summary>
+ IProviderEndpoint Provider { get; }
+
+ /// <summary>
+ /// Gets the discovery result leading to the formulation of this request.
+ /// </summary>
+ /// <value>The discovery result.</value>
+ IdentifierDiscoveryResult DiscoveryResult { get; }
+
+ /// <summary>
+ /// Makes a dictionary of key/value pairs available when the authentication is completed.
+ /// </summary>
+ /// <param name="arguments">The arguments to add to the request's return_to URI. Values must not be null.</param>
+ /// <remarks>
+ /// <para>Note that these values are NOT protected against eavesdropping in transit. No
+ /// privacy-sensitive data should be stored using this method.</para>
+ /// <para>The values stored here can be retrieved using
+ /// <see cref="IAuthenticationResponse.GetCallbackArgument"/>, which will only return the value
+ /// if it can be verified as untampered with in transit.</para>
+ /// <para>Since the data set here is sent in the querystring of the request and some
+ /// servers place limits on the size of a request URL, this data should be kept relatively
+ /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para>
+ /// </remarks>
+ void AddCallbackArguments(IDictionary<string, string> arguments);
+
+ /// <summary>
+ /// Makes a key/value pair available when the authentication is completed.
+ /// </summary>
+ /// <param name="key">The parameter name.</param>
+ /// <param name="value">The value of the argument. Must not be null.</param>
+ /// <remarks>
+ /// <para>Note that these values are NOT protected against eavesdropping in transit. No
+ /// privacy-sensitive data should be stored using this method.</para>
+ /// <para>The value stored here can be retrieved using
+ /// <see cref="IAuthenticationResponse.GetCallbackArgument"/>, which will only return the value
+ /// if it can be verified as untampered with in transit.</para>
+ /// <para>Since the data set here is sent in the querystring of the request and some
+ /// servers place limits on the size of a request URL, this data should be kept relatively
+ /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para>
+ /// </remarks>
+ void AddCallbackArguments(string key, string value);
+
+ /// <summary>
+ /// Makes a key/value pair available when the authentication is completed.
+ /// </summary>
+ /// <param name="key">The parameter name.</param>
+ /// <param name="value">The value of the argument. Must not be null.</param>
+ /// <remarks>
+ /// <para>Note that these values are NOT protected against eavesdropping in transit. No
+ /// security-sensitive data should be stored using this method.</para>
+ /// <para>The value stored here can be retrieved using
+ /// <see cref="IAuthenticationResponse.GetCallbackArgument"/>.</para>
+ /// <para>Since the data set here is sent in the querystring of the request and some
+ /// servers place limits on the size of a request URL, this data should be kept relatively
+ /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para>
+ /// </remarks>
+ void SetCallbackArgument(string key, string value);
+
+ /// <summary>
+ /// Makes a key/value pair available when the authentication is completed without
+ /// requiring a return_to signature to protect against tampering of the callback argument.
+ /// </summary>
+ /// <param name="key">The parameter name.</param>
+ /// <param name="value">The value of the argument. Must not be null.</param>
+ /// <remarks>
+ /// <para>Note that these values are NOT protected against eavesdropping or tampering in transit. No
+ /// security-sensitive data should be stored using this method. </para>
+ /// <para>The value stored here can be retrieved using
+ /// <see cref="IAuthenticationResponse.GetCallbackArgument"/>.</para>
+ /// <para>Since the data set here is sent in the querystring of the request and some
+ /// servers place limits on the size of a request URL, this data should be kept relatively
+ /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para>
+ /// </remarks>
+ void SetUntrustedCallbackArgument(string key, string value);
+
+ /// <summary>
+ /// Adds an OpenID extension to the request directed at the OpenID provider.
+ /// </summary>
+ /// <param name="extension">The initialized extension to add to the request.</param>
+ void AddExtension(IOpenIdMessageExtension extension);
+
+ /// <summary>
+ /// Redirects the user agent to the provider for authentication.
+ /// Execution of the current page terminates after this call.
+ /// </summary>
+ /// <remarks>
+ /// This method requires an ASP.NET HttpContext.
+ /// </remarks>
+ void RedirectToProvider();
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/IAuthenticationRequestContract.cs b/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/IAuthenticationRequestContract.cs
new file mode 100644
index 0000000..4ddc6ae
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/IAuthenticationRequestContract.cs
@@ -0,0 +1,111 @@
+// <auto-generated />
+
+namespace DotNetOpenAuth.OpenId.RelyingParty {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ [ContractClassFor(typeof(IAuthenticationRequest))]
+ internal abstract class IAuthenticationRequestContract : IAuthenticationRequest {
+ #region IAuthenticationRequest Members
+
+ AuthenticationRequestMode IAuthenticationRequest.Mode {
+ get {
+ throw new NotImplementedException();
+ }
+
+ set {
+ throw new NotImplementedException();
+ }
+ }
+
+ OutgoingWebResponse IAuthenticationRequest.RedirectingResponse {
+ get { throw new NotImplementedException(); }
+ }
+
+ Uri IAuthenticationRequest.ReturnToUrl {
+ get { throw new NotImplementedException(); }
+ }
+
+ Realm IAuthenticationRequest.Realm {
+ get {
+ Contract.Ensures(Contract.Result<Realm>() != null);
+ throw new NotImplementedException();
+ }
+ }
+
+ Identifier IAuthenticationRequest.ClaimedIdentifier {
+ get {
+ throw new NotImplementedException();
+ }
+ }
+
+ bool IAuthenticationRequest.IsDirectedIdentity {
+ get { throw new NotImplementedException(); }
+ }
+
+ bool IAuthenticationRequest.IsExtensionOnly {
+ get {
+ throw new NotImplementedException();
+ }
+
+ set {
+ throw new NotImplementedException();
+ }
+ }
+
+ IProviderEndpoint IAuthenticationRequest.Provider {
+ get {
+ Contract.Ensures(Contract.Result<IProviderEndpoint>() != null);
+ throw new NotImplementedException();
+ }
+ }
+
+ IdentifierDiscoveryResult IAuthenticationRequest.DiscoveryResult {
+ get {
+ Contract.Ensures(Contract.Result<IdentifierDiscoveryResult>() != null);
+ throw new NotImplementedException();
+ }
+ }
+
+ void IAuthenticationRequest.AddCallbackArguments(IDictionary<string, string> arguments) {
+ Requires.NotNull(arguments, "arguments");
+ Requires.True(arguments.Keys.All(k => !string.IsNullOrEmpty(k)), "arguments");
+ Requires.True(arguments.Values.All(v => v != null), "arguments");
+ throw new NotImplementedException();
+ }
+
+ void IAuthenticationRequest.AddCallbackArguments(string key, string value) {
+ Requires.NotNullOrEmpty(key, "key");
+ Requires.NotNull(value, "value");
+ throw new NotImplementedException();
+ }
+
+ void IAuthenticationRequest.SetCallbackArgument(string key, string value) {
+ Requires.NotNullOrEmpty(key, "key");
+ Requires.NotNull(value, "value");
+ throw new NotImplementedException();
+ }
+
+ void IAuthenticationRequest.AddExtension(IOpenIdMessageExtension extension) {
+ Requires.NotNull(extension, "extension");
+ throw new NotImplementedException();
+ }
+
+ void IAuthenticationRequest.RedirectToProvider() {
+ throw new NotImplementedException();
+ }
+
+ void IAuthenticationRequest.SetUntrustedCallbackArgument(string key, string value) {
+ Requires.NotNullOrEmpty(key, "key");
+ Requires.NotNull(value, "value");
+ throw new NotImplementedException();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/IAuthenticationResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/IAuthenticationResponse.cs
new file mode 100644
index 0000000..10e99e4
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/IAuthenticationResponse.cs
@@ -0,0 +1,530 @@
+//-----------------------------------------------------------------------
+// <copyright file="IAuthenticationResponse.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.RelyingParty {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.Text;
+ using System.Web;
+ using DotNetOpenAuth.OpenId.Extensions;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// An instance of this interface represents an identity assertion
+ /// from an OpenID Provider. It may be in response to an authentication
+ /// request previously put to it by a Relying Party site or it may be an
+ /// unsolicited assertion.
+ /// </summary>
+ /// <remarks>
+ /// Relying party web sites should handle both solicited and unsolicited
+ /// assertions. This interface does not offer a way to discern between
+ /// solicited and unsolicited assertions as they should be treated equally.
+ /// </remarks>
+ [ContractClass(typeof(IAuthenticationResponseContract))]
+ public interface IAuthenticationResponse {
+ /// <summary>
+ /// Gets the Identifier that the end user claims to own. For use with user database storage and lookup.
+ /// May be null for some failed authentications (i.e. failed directed identity authentications).
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// This is the secure identifier that should be used for database storage and lookup.
+ /// It is not always friendly (i.e. =Arnott becomes =!9B72.7DD1.50A9.5CCD), but it protects
+ /// user identities against spoofing and other attacks.
+ /// </para>
+ /// <para>
+ /// For user-friendly identifiers to display, use the
+ /// <see cref="FriendlyIdentifierForDisplay"/> property.
+ /// </para>
+ /// </remarks>
+ Identifier ClaimedIdentifier { get; }
+
+ /// <summary>
+ /// Gets a user-friendly OpenID Identifier for display purposes ONLY.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// This <i>should</i> be put through <see cref="HttpUtility.HtmlEncode(string)"/> before
+ /// sending to a browser to secure against javascript injection attacks.
+ /// </para>
+ /// <para>
+ /// This property retains some aspects of the user-supplied identifier that get lost
+ /// in the <see cref="ClaimedIdentifier"/>. For example, XRIs used as user-supplied
+ /// identifiers (i.e. =Arnott) become unfriendly unique strings (i.e. =!9B72.7DD1.50A9.5CCD).
+ /// For display purposes, such as text on a web page that says "You're logged in as ...",
+ /// this property serves to provide the =Arnott string, or whatever else is the most friendly
+ /// string close to what the user originally typed in.
+ /// </para>
+ /// <para>
+ /// If the user-supplied identifier is a URI, this property will be the URI after all
+ /// redirects, and with the protocol and fragment trimmed off.
+ /// If the user-supplied identifier is an XRI, this property will be the original XRI.
+ /// If the user-supplied identifier is an OpenID Provider identifier (i.e. yahoo.com),
+ /// this property will be the Claimed Identifier, with the protocol stripped if it is a URI.
+ /// </para>
+ /// <para>
+ /// It is <b>very</b> important that this property <i>never</i> be used for database storage
+ /// or lookup to avoid identity spoofing and other security risks. For database storage
+ /// and lookup please use the <see cref="ClaimedIdentifier"/> property.
+ /// </para>
+ /// </remarks>
+ string FriendlyIdentifierForDisplay { get; }
+
+ /// <summary>
+ /// Gets the detailed success or failure status of the authentication attempt.
+ /// </summary>
+ AuthenticationStatus Status { get; }
+
+ /// <summary>
+ /// Gets information about the OpenId Provider, as advertised by the
+ /// OpenID discovery documents found at the <see cref="ClaimedIdentifier"/>
+ /// location, if available.
+ /// </summary>
+ /// <value>
+ /// The Provider endpoint that issued the positive assertion;
+ /// or <c>null</c> if information about the Provider is unavailable.
+ /// </value>
+ IProviderEndpoint Provider { get; }
+
+ /// <summary>
+ /// Gets the details regarding a failed authentication attempt, if available.
+ /// This will be set if and only if <see cref="Status"/> is <see cref="AuthenticationStatus.Failed"/>.
+ /// </summary>
+ Exception Exception { get; }
+
+ /// <summary>
+ /// Gets a callback argument's value that was previously added using
+ /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.
+ /// </summary>
+ /// <param name="key">The name of the parameter whose value is sought.</param>
+ /// <returns>
+ /// The value of the argument, or null if the named parameter could not be found.
+ /// </returns>
+ /// <remarks>
+ /// Callback parameters are only available if they are complete and untampered with
+ /// since the original request message (as proven by a signature).
+ /// If the relying party is operating in stateless mode <c>null</c> is always
+ /// returned since the callback arguments could not be signed to protect against
+ /// tampering.
+ /// </remarks>
+ string GetCallbackArgument(string key);
+
+ /// <summary>
+ /// Gets a callback argument's value that was previously added using
+ /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.
+ /// </summary>
+ /// <param name="key">The name of the parameter whose value is sought.</param>
+ /// <returns>
+ /// The value of the argument, or null if the named parameter could not be found.
+ /// </returns>
+ /// <remarks>
+ /// Callback parameters are only available even if the RP is in stateless mode,
+ /// or the callback parameters are otherwise unverifiable as untampered with.
+ /// Therefore, use this method only when the callback argument is not to be
+ /// used to make a security-sensitive decision.
+ /// </remarks>
+ string GetUntrustedCallbackArgument(string key);
+
+ /// <summary>
+ /// Gets all the callback arguments that were previously added using
+ /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part
+ /// of the return_to URL.
+ /// </summary>
+ /// <returns>A name-value dictionary. Never null.</returns>
+ /// <remarks>
+ /// Callback parameters are only available if they are complete and untampered with
+ /// since the original request message (as proven by a signature).
+ /// If the relying party is operating in stateless mode an empty dictionary is always
+ /// returned since the callback arguments could not be signed to protect against
+ /// tampering.
+ /// </remarks>
+ [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Historically an expensive operation.")]
+ IDictionary<string, string> GetCallbackArguments();
+
+ /// <summary>
+ /// Gets all the callback arguments that were previously added using
+ /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part
+ /// of the return_to URL.
+ /// </summary>
+ /// <returns>A name-value dictionary. Never null.</returns>
+ /// <remarks>
+ /// Callback parameters are only available even if the RP is in stateless mode,
+ /// or the callback parameters are otherwise unverifiable as untampered with.
+ /// Therefore, use this method only when the callback argument is not to be
+ /// used to make a security-sensitive decision.
+ /// </remarks>
+ [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Historically an expensive operation.")]
+ IDictionary<string, string> GetUntrustedCallbackArguments();
+
+ /// <summary>
+ /// Tries to get an OpenID extension that may be present in the response.
+ /// </summary>
+ /// <typeparam name="T">The type of extension to look for in the response message.</typeparam>
+ /// <returns>
+ /// The extension, if it is found. Null otherwise.
+ /// </returns>
+ /// <remarks>
+ /// <para>Extensions are returned only if the Provider signed them.
+ /// Relying parties that do not care if the values were modified in
+ /// transit should use the <see cref="GetUntrustedExtension&lt;T&gt;"/> method
+ /// in order to allow the Provider to not sign the extension. </para>
+ /// <para>Unsigned extensions are completely unreliable and should be
+ /// used only to prefill user forms since the user or any other third
+ /// party may have tampered with the data carried by the extension.</para>
+ /// <para>Signed extensions are only reliable if the relying party
+ /// trusts the OpenID Provider that signed them. Signing does not mean
+ /// the relying party can trust the values -- it only means that the values
+ /// have not been tampered with since the Provider sent the message.</para>
+ /// </remarks>
+ [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "No parameter at all is required. T is used for return type.")]
+ T GetExtension<T>() where T : IOpenIdMessageExtension;
+
+ /// <summary>
+ /// Tries to get an OpenID extension that may be present in the response.
+ /// </summary>
+ /// <param name="extensionType">Type of the extension to look for in the response.</param>
+ /// <returns>
+ /// The extension, if it is found. Null otherwise.
+ /// </returns>
+ /// <remarks>
+ /// <para>Extensions are returned only if the Provider signed them.
+ /// Relying parties that do not care if the values were modified in
+ /// transit should use the <see cref="GetUntrustedExtension"/> method
+ /// in order to allow the Provider to not sign the extension. </para>
+ /// <para>Unsigned extensions are completely unreliable and should be
+ /// used only to prefill user forms since the user or any other third
+ /// party may have tampered with the data carried by the extension.</para>
+ /// <para>Signed extensions are only reliable if the relying party
+ /// trusts the OpenID Provider that signed them. Signing does not mean
+ /// the relying party can trust the values -- it only means that the values
+ /// have not been tampered with since the Provider sent the message.</para>
+ /// </remarks>
+ IOpenIdMessageExtension GetExtension(Type extensionType);
+
+ /// <summary>
+ /// Tries to get an OpenID extension that may be present in the response, without
+ /// requiring it to be signed by the Provider.
+ /// </summary>
+ /// <typeparam name="T">The type of extension to look for in the response message.</typeparam>
+ /// <returns>
+ /// The extension, if it is found. Null otherwise.
+ /// </returns>
+ /// <remarks>
+ /// <para>Extensions are returned whether they are signed or not.
+ /// Use the <see cref="GetExtension&lt;T&gt;"/> method to retrieve
+ /// extension responses only if they are signed by the Provider to
+ /// protect against tampering. </para>
+ /// <para>Unsigned extensions are completely unreliable and should be
+ /// used only to prefill user forms since the user or any other third
+ /// party may have tampered with the data carried by the extension.</para>
+ /// <para>Signed extensions are only reliable if the relying party
+ /// trusts the OpenID Provider that signed them. Signing does not mean
+ /// the relying party can trust the values -- it only means that the values
+ /// have not been tampered with since the Provider sent the message.</para>
+ /// </remarks>
+ [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "No parameter at all is required. T is used for return type.")]
+ T GetUntrustedExtension<T>() where T : IOpenIdMessageExtension;
+
+ /// <summary>
+ /// Tries to get an OpenID extension that may be present in the response, without
+ /// requiring it to be signed by the Provider.
+ /// </summary>
+ /// <param name="extensionType">Type of the extension to look for in the response.</param>
+ /// <returns>
+ /// The extension, if it is found. Null otherwise.
+ /// </returns>
+ /// <remarks>
+ /// <para>Extensions are returned whether they are signed or not.
+ /// Use the <see cref="GetExtension"/> method to retrieve
+ /// extension responses only if they are signed by the Provider to
+ /// protect against tampering. </para>
+ /// <para>Unsigned extensions are completely unreliable and should be
+ /// used only to prefill user forms since the user or any other third
+ /// party may have tampered with the data carried by the extension.</para>
+ /// <para>Signed extensions are only reliable if the relying party
+ /// trusts the OpenID Provider that signed them. Signing does not mean
+ /// the relying party can trust the values -- it only means that the values
+ /// have not been tampered with since the Provider sent the message.</para>
+ /// </remarks>
+ IOpenIdMessageExtension GetUntrustedExtension(Type extensionType);
+ }
+
+ /// <summary>
+ /// Code contract for the <see cref="IAuthenticationResponse"/> type.
+ /// </summary>
+ [ContractClassFor(typeof(IAuthenticationResponse))]
+ internal abstract class IAuthenticationResponseContract : IAuthenticationResponse {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="IAuthenticationResponseContract"/> class.
+ /// </summary>
+ protected IAuthenticationResponseContract() {
+ }
+
+ #region IAuthenticationResponse Members
+
+ /// <summary>
+ /// Gets the Identifier that the end user claims to own. For use with user database storage and lookup.
+ /// May be null for some failed authentications (i.e. failed directed identity authentications).
+ /// </summary>
+ /// <value></value>
+ /// <remarks>
+ /// <para>
+ /// This is the secure identifier that should be used for database storage and lookup.
+ /// It is not always friendly (i.e. =Arnott becomes =!9B72.7DD1.50A9.5CCD), but it protects
+ /// user identities against spoofing and other attacks.
+ /// </para>
+ /// <para>
+ /// For user-friendly identifiers to display, use the
+ /// <see cref="IAuthenticationResponse.FriendlyIdentifierForDisplay"/> property.
+ /// </para>
+ /// </remarks>
+ Identifier IAuthenticationResponse.ClaimedIdentifier {
+ get { throw new NotImplementedException(); }
+ }
+
+ /// <summary>
+ /// Gets a user-friendly OpenID Identifier for display purposes ONLY.
+ /// </summary>
+ /// <value></value>
+ /// <remarks>
+ /// <para>
+ /// This <i>should</i> be put through <see cref="HttpUtility.HtmlEncode(string)"/> before
+ /// sending to a browser to secure against javascript injection attacks.
+ /// </para>
+ /// <para>
+ /// This property retains some aspects of the user-supplied identifier that get lost
+ /// in the <see cref="IAuthenticationResponse.ClaimedIdentifier"/>. For example, XRIs used as user-supplied
+ /// identifiers (i.e. =Arnott) become unfriendly unique strings (i.e. =!9B72.7DD1.50A9.5CCD).
+ /// For display purposes, such as text on a web page that says "You're logged in as ...",
+ /// this property serves to provide the =Arnott string, or whatever else is the most friendly
+ /// string close to what the user originally typed in.
+ /// </para>
+ /// <para>
+ /// If the user-supplied identifier is a URI, this property will be the URI after all
+ /// redirects, and with the protocol and fragment trimmed off.
+ /// If the user-supplied identifier is an XRI, this property will be the original XRI.
+ /// If the user-supplied identifier is an OpenID Provider identifier (i.e. yahoo.com),
+ /// this property will be the Claimed Identifier, with the protocol stripped if it is a URI.
+ /// </para>
+ /// <para>
+ /// It is <b>very</b> important that this property <i>never</i> be used for database storage
+ /// or lookup to avoid identity spoofing and other security risks. For database storage
+ /// and lookup please use the <see cref="IAuthenticationResponse.ClaimedIdentifier"/> property.
+ /// </para>
+ /// </remarks>
+ string IAuthenticationResponse.FriendlyIdentifierForDisplay {
+ get { throw new NotImplementedException(); }
+ }
+
+ /// <summary>
+ /// Gets the detailed success or failure status of the authentication attempt.
+ /// </summary>
+ /// <value></value>
+ AuthenticationStatus IAuthenticationResponse.Status {
+ get { throw new NotImplementedException(); }
+ }
+
+ /// <summary>
+ /// Gets information about the OpenId Provider, as advertised by the
+ /// OpenID discovery documents found at the <see cref="IAuthenticationResponse.ClaimedIdentifier"/>
+ /// location, if available.
+ /// </summary>
+ /// <value>
+ /// The Provider endpoint that issued the positive assertion;
+ /// or <c>null</c> if information about the Provider is unavailable.
+ /// </value>
+ IProviderEndpoint IAuthenticationResponse.Provider {
+ get { throw new NotImplementedException(); }
+ }
+
+ /// <summary>
+ /// Gets the details regarding a failed authentication attempt, if available.
+ /// This will be set if and only if <see cref="IAuthenticationResponse.Status"/> is <see cref="AuthenticationStatus.Failed"/>.
+ /// </summary>
+ /// <value></value>
+ Exception IAuthenticationResponse.Exception {
+ get { throw new NotImplementedException(); }
+ }
+
+ /// <summary>
+ /// Gets a callback argument's value that was previously added using
+ /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.
+ /// </summary>
+ /// <param name="key">The name of the parameter whose value is sought.</param>
+ /// <returns>
+ /// The value of the argument, or null if the named parameter could not be found.
+ /// </returns>
+ /// <remarks>
+ /// <para>This may return any argument on the querystring that came with the authentication response,
+ /// which may include parameters not explicitly added using
+ /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.</para>
+ /// <para>Note that these values are NOT protected against tampering in transit.</para>
+ /// </remarks>
+ string IAuthenticationResponse.GetCallbackArgument(string key) {
+ Requires.NotNullOrEmpty(key, "key");
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Gets all the callback arguments that were previously added using
+ /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part
+ /// of the return_to URL.
+ /// </summary>
+ /// <returns>A name-value dictionary. Never null.</returns>
+ /// <remarks>
+ /// <para>This MAY return any argument on the querystring that came with the authentication response,
+ /// which may include parameters not explicitly added using
+ /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.</para>
+ /// <para>Note that these values are NOT protected against tampering in transit.</para>
+ /// </remarks>
+ IDictionary<string, string> IAuthenticationResponse.GetCallbackArguments() {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Tries to get an OpenID extension that may be present in the response.
+ /// </summary>
+ /// <typeparam name="T">The type of extension to look for in the response message.</typeparam>
+ /// <returns>
+ /// The extension, if it is found. Null otherwise.
+ /// </returns>
+ /// <remarks>
+ /// <para>Extensions are returned only if the Provider signed them.
+ /// Relying parties that do not care if the values were modified in
+ /// transit should use the <see cref="IAuthenticationResponse.GetUntrustedExtension&lt;T&gt;"/> method
+ /// in order to allow the Provider to not sign the extension. </para>
+ /// <para>Unsigned extensions are completely unreliable and should be
+ /// used only to prefill user forms since the user or any other third
+ /// party may have tampered with the data carried by the extension.</para>
+ /// <para>Signed extensions are only reliable if the relying party
+ /// trusts the OpenID Provider that signed them. Signing does not mean
+ /// the relying party can trust the values -- it only means that the values
+ /// have not been tampered with since the Provider sent the message.</para>
+ /// </remarks>
+ T IAuthenticationResponse.GetExtension<T>() {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Tries to get an OpenID extension that may be present in the response.
+ /// </summary>
+ /// <param name="extensionType">Type of the extension to look for in the response.</param>
+ /// <returns>
+ /// The extension, if it is found. Null otherwise.
+ /// </returns>
+ /// <remarks>
+ /// <para>Extensions are returned only if the Provider signed them.
+ /// Relying parties that do not care if the values were modified in
+ /// transit should use the <see cref="IAuthenticationResponse.GetUntrustedExtension"/> method
+ /// in order to allow the Provider to not sign the extension. </para>
+ /// <para>Unsigned extensions are completely unreliable and should be
+ /// used only to prefill user forms since the user or any other third
+ /// party may have tampered with the data carried by the extension.</para>
+ /// <para>Signed extensions are only reliable if the relying party
+ /// trusts the OpenID Provider that signed them. Signing does not mean
+ /// the relying party can trust the values -- it only means that the values
+ /// have not been tampered with since the Provider sent the message.</para>
+ /// </remarks>
+ IOpenIdMessageExtension IAuthenticationResponse.GetExtension(Type extensionType) {
+ Requires.NotNullSubtype<IOpenIdMessageExtension>(extensionType, "extensionType");
+ ////ErrorUtilities.VerifyArgument(typeof(IOpenIdMessageExtension).IsAssignableFrom(extensionType), string.Format(CultureInfo.CurrentCulture, OpenIdStrings.TypeMustImplementX, typeof(IOpenIdMessageExtension).FullName));
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Tries to get an OpenID extension that may be present in the response, without
+ /// requiring it to be signed by the Provider.
+ /// </summary>
+ /// <typeparam name="T">The type of extension to look for in the response message.</typeparam>
+ /// <returns>
+ /// The extension, if it is found. Null otherwise.
+ /// </returns>
+ /// <remarks>
+ /// <para>Extensions are returned whether they are signed or not.
+ /// Use the <see cref="IAuthenticationResponse.GetExtension&lt;T&gt;"/> method to retrieve
+ /// extension responses only if they are signed by the Provider to
+ /// protect against tampering. </para>
+ /// <para>Unsigned extensions are completely unreliable and should be
+ /// used only to prefill user forms since the user or any other third
+ /// party may have tampered with the data carried by the extension.</para>
+ /// <para>Signed extensions are only reliable if the relying party
+ /// trusts the OpenID Provider that signed them. Signing does not mean
+ /// the relying party can trust the values -- it only means that the values
+ /// have not been tampered with since the Provider sent the message.</para>
+ /// </remarks>
+ T IAuthenticationResponse.GetUntrustedExtension<T>() {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Tries to get an OpenID extension that may be present in the response, without
+ /// requiring it to be signed by the Provider.
+ /// </summary>
+ /// <param name="extensionType">Type of the extension to look for in the response.</param>
+ /// <returns>
+ /// The extension, if it is found. Null otherwise.
+ /// </returns>
+ /// <remarks>
+ /// <para>Extensions are returned whether they are signed or not.
+ /// Use the <see cref="IAuthenticationResponse.GetExtension"/> method to retrieve
+ /// extension responses only if they are signed by the Provider to
+ /// protect against tampering. </para>
+ /// <para>Unsigned extensions are completely unreliable and should be
+ /// used only to prefill user forms since the user or any other third
+ /// party may have tampered with the data carried by the extension.</para>
+ /// <para>Signed extensions are only reliable if the relying party
+ /// trusts the OpenID Provider that signed them. Signing does not mean
+ /// the relying party can trust the values -- it only means that the values
+ /// have not been tampered with since the Provider sent the message.</para>
+ /// </remarks>
+ IOpenIdMessageExtension IAuthenticationResponse.GetUntrustedExtension(Type extensionType) {
+ Requires.NotNullSubtype<IOpenIdMessageExtension>(extensionType, "extensionType");
+ ////ErrorUtilities.VerifyArgument(typeof(IOpenIdMessageExtension).IsAssignableFrom(extensionType), string.Format(CultureInfo.CurrentCulture, OpenIdStrings.TypeMustImplementX, typeof(IOpenIdMessageExtension).FullName));
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Gets a callback argument's value that was previously added using
+ /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.
+ /// </summary>
+ /// <param name="key">The name of the parameter whose value is sought.</param>
+ /// <returns>
+ /// The value of the argument, or null if the named parameter could not be found.
+ /// </returns>
+ /// <remarks>
+ /// Callback parameters are only available even if the RP is in stateless mode,
+ /// or the callback parameters are otherwise unverifiable as untampered with.
+ /// Therefore, use this method only when the callback argument is not to be
+ /// used to make a security-sensitive decision.
+ /// </remarks>
+ string IAuthenticationResponse.GetUntrustedCallbackArgument(string key) {
+ Requires.NotNullOrEmpty(key, "key");
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Gets all the callback arguments that were previously added using
+ /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part
+ /// of the return_to URL.
+ /// </summary>
+ /// <returns>A name-value dictionary. Never null.</returns>
+ /// <remarks>
+ /// Callback parameters are only available even if the RP is in stateless mode,
+ /// or the callback parameters are otherwise unverifiable as untampered with.
+ /// Therefore, use this method only when the callback argument is not to be
+ /// used to make a security-sensitive decision.
+ /// </remarks>
+ IDictionary<string, string> IAuthenticationResponse.GetUntrustedCallbackArguments() {
+ Contract.Ensures(Contract.Result<IDictionary<string, string>>() != null);
+ throw new NotImplementedException();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/IRelyingPartyBehavior.cs b/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/IRelyingPartyBehavior.cs
new file mode 100644
index 0000000..0113f62
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/IRelyingPartyBehavior.cs
@@ -0,0 +1,92 @@
+//-----------------------------------------------------------------------
+// <copyright file="IRelyingPartyBehavior.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.RelyingParty {
+ using System;
+ using System.Diagnostics.Contracts;
+
+ /// <summary>
+ /// Applies a custom security policy to certain OpenID security settings and behaviors.
+ /// </summary>
+ [ContractClass(typeof(IRelyingPartyBehaviorContract))]
+ public interface IRelyingPartyBehavior {
+ /// <summary>
+ /// Applies a well known set of security requirements to a default set of security settings.
+ /// </summary>
+ /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param>
+ /// <remarks>
+ /// Care should be taken to never decrease security when applying a profile.
+ /// Profiles should only enhance security requirements to avoid being
+ /// incompatible with each other.
+ /// </remarks>
+ void ApplySecuritySettings(RelyingPartySecuritySettings securitySettings);
+
+ /// <summary>
+ /// Called when an authentication request is about to be sent.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ /// <remarks>
+ /// Implementations should be prepared to be called multiple times on the same outgoing message
+ /// without malfunctioning.
+ /// </remarks>
+ void OnOutgoingAuthenticationRequest(IAuthenticationRequest request);
+
+ /// <summary>
+ /// Called when an incoming positive assertion is received.
+ /// </summary>
+ /// <param name="assertion">The positive assertion.</param>
+ void OnIncomingPositiveAssertion(IAuthenticationResponse assertion);
+ }
+
+ /// <summary>
+ /// Contract class for the <see cref="IRelyingPartyBehavior"/> interface.
+ /// </summary>
+ [ContractClassFor(typeof(IRelyingPartyBehavior))]
+ internal abstract class IRelyingPartyBehaviorContract : IRelyingPartyBehavior {
+ /// <summary>
+ /// Prevents a default instance of the <see cref="IRelyingPartyBehaviorContract"/> class from being created.
+ /// </summary>
+ private IRelyingPartyBehaviorContract() {
+ }
+
+ #region IRelyingPartyBehavior Members
+
+ /// <summary>
+ /// Applies a well known set of security requirements to a default set of security settings.
+ /// </summary>
+ /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param>
+ /// <remarks>
+ /// Care should be taken to never decrease security when applying a profile.
+ /// Profiles should only enhance security requirements to avoid being
+ /// incompatible with each other.
+ /// </remarks>
+ void IRelyingPartyBehavior.ApplySecuritySettings(RelyingPartySecuritySettings securitySettings) {
+ Requires.NotNull(securitySettings, "securitySettings");
+ }
+
+ /// <summary>
+ /// Called when an authentication request is about to be sent.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ /// <remarks>
+ /// Implementations should be prepared to be called multiple times on the same outgoing message
+ /// without malfunctioning.
+ /// </remarks>
+ void IRelyingPartyBehavior.OnOutgoingAuthenticationRequest(IAuthenticationRequest request) {
+ Requires.NotNull(request, "request");
+ }
+
+ /// <summary>
+ /// Called when an incoming positive assertion is received.
+ /// </summary>
+ /// <param name="assertion">The positive assertion.</param>
+ void IRelyingPartyBehavior.OnIncomingPositiveAssertion(IAuthenticationResponse assertion) {
+ Requires.NotNull(assertion, "assertion");
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/RelyingPartySecuritySettings.cs b/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/RelyingPartySecuritySettings.cs
new file mode 100644
index 0000000..77ccbca
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/RelyingPartySecuritySettings.cs
@@ -0,0 +1,172 @@
+//-----------------------------------------------------------------------
+// <copyright file="RelyingPartySecuritySettings.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.RelyingParty {
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.ObjectModel;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// Security settings that are applicable to relying parties.
+ /// </summary>
+ public sealed class RelyingPartySecuritySettings : SecuritySettings {
+ /// <summary>
+ /// The default value for the <see cref="ProtectDownlevelReplayAttacks"/> property.
+ /// </summary>
+ internal const bool ProtectDownlevelReplayAttacksDefault = true;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RelyingPartySecuritySettings"/> class.
+ /// </summary>
+ internal RelyingPartySecuritySettings()
+ : base(false) {
+ this.PrivateSecretMaximumAge = TimeSpan.FromDays(7);
+ this.ProtectDownlevelReplayAttacks = ProtectDownlevelReplayAttacksDefault;
+ this.AllowApproximateIdentifierDiscovery = true;
+ this.TrustedProviderEndpoints = new HashSet<Uri>();
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the entire pipeline from Identifier discovery to
+ /// Provider redirect is guaranteed to be encrypted using HTTPS for authentication to succeed.
+ /// </summary>
+ /// <remarks>
+ /// <para>Setting this property to true is appropriate for RPs with highly sensitive
+ /// personal information behind the authentication (money management, health records, etc.)</para>
+ /// <para>When set to true, some behavioral changes and additional restrictions are placed:</para>
+ /// <list>
+ /// <item>User-supplied identifiers lacking a scheme are prepended with
+ /// HTTPS:// rather than the standard HTTP:// automatically.</item>
+ /// <item>User-supplied identifiers are not allowed to use HTTP for the scheme.</item>
+ /// <item>All redirects during discovery on the user-supplied identifier must be HTTPS.</item>
+ /// <item>Any XRDS file found by discovery on the User-supplied identifier must be protected using HTTPS.</item>
+ /// <item>Only Provider endpoints found at HTTPS URLs will be considered.</item>
+ /// <item>If the discovered identifier is an OP Identifier (directed identity), the
+ /// Claimed Identifier eventually asserted by the Provider must be an HTTPS identifier.</item>
+ /// <item>In the case of an unsolicited assertion, the asserted Identifier, discovery on it and
+ /// the asserting provider endpoint must all be secured by HTTPS.</item>
+ /// </list>
+ /// <para>Although the first redirect from this relying party to the Provider is required
+ /// to use HTTPS, any additional redirects within the Provider cannot be protected and MAY
+ /// revert the user's connection to HTTP, based on individual Provider implementation.
+ /// There is nothing that the RP can do to detect or prevent this.</para>
+ /// <para>
+ /// A <see cref="ProtocolException"/> is thrown during discovery or authentication when a secure pipeline cannot be established.
+ /// </para>
+ /// </remarks>
+ public bool RequireSsl { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether only OP Identifiers will be discoverable
+ /// when creating authentication requests.
+ /// </summary>
+ public bool RequireDirectedIdentity { get; set; }
+
+ /// <summary>
+ /// Gets or sets the oldest version of OpenID the remote party is allowed to implement.
+ /// </summary>
+ /// <value>Defaults to <see cref="ProtocolVersion.V10"/></value>
+ public ProtocolVersion MinimumRequiredOpenIdVersion { get; set; }
+
+ /// <summary>
+ /// Gets or sets the maximum allowable age of the secret a Relying Party
+ /// uses to its return_to URLs and nonces with 1.0 Providers.
+ /// </summary>
+ /// <value>The default value is 7 days.</value>
+ public TimeSpan PrivateSecretMaximumAge { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether all unsolicited assertions should be ignored.
+ /// </summary>
+ /// <value>The default value is <c>false</c>.</value>
+ public bool RejectUnsolicitedAssertions { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether delegating identifiers are refused for authentication.
+ /// </summary>
+ /// <value>The default value is <c>false</c>.</value>
+ /// <remarks>
+ /// When set to <c>true</c>, login attempts that start at the RP or arrive via unsolicited
+ /// assertions will be rejected if discovery on the identifier shows that OpenID delegation
+ /// is used for the identifier. This is useful for an RP that should only accept identifiers
+ /// directly issued by the Provider that is sending the assertion.
+ /// </remarks>
+ public bool RejectDelegatingIdentifiers { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether unsigned extensions in authentication responses should be ignored.
+ /// </summary>
+ /// <value>The default value is <c>false</c>.</value>
+ /// <remarks>
+ /// When set to true, the <see cref="IAuthenticationResponse.GetUntrustedExtension"/> methods
+ /// will not return any extension that was not signed by the Provider.
+ /// </remarks>
+ public bool IgnoreUnsignedExtensions { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether authentication requests will only be
+ /// sent to Providers with whom we can create a shared association.
+ /// </summary>
+ /// <value>
+ /// <c>true</c> to immediately fail authentication if an association with the Provider cannot be established; otherwise, <c>false</c>.
+ /// The default value is <c>false</c>.
+ /// </value>
+ public bool RequireAssociation { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether certain Claimed Identifiers that exploit
+ /// features that .NET does not have the ability to send exact HTTP requests for will
+ /// still be allowed by using an approximate HTTP request.
+ /// </summary>
+ /// <value>
+ /// The default value is <c>true</c>.
+ /// </value>
+ public bool AllowApproximateIdentifierDiscovery { get; set; }
+
+ /// <summary>
+ /// Gets the set of trusted OpenID Provider Endpoint URIs.
+ /// </summary>
+ public HashSet<Uri> TrustedProviderEndpoints { get; private set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether any login attempt coming from an OpenID Provider Endpoint that is not on this
+ /// whitelist of trusted OP Endpoints will be rejected. If the trusted providers list is empty and this value
+ /// is true, all assertions are rejected.
+ /// </summary>
+ /// <value>Default is <c>false</c>.</value>
+ public bool RejectAssertionsFromUntrustedProviders { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether special measures are taken to
+ /// protect users from replay attacks when those users' identities are hosted
+ /// by OpenID 1.x Providers.
+ /// </summary>
+ /// <value>The default value is <c>true</c>.</value>
+ /// <remarks>
+ /// <para>Nonces for protection against replay attacks were not mandated
+ /// by OpenID 1.x, which leaves users open to replay attacks.</para>
+ /// <para>This feature works by adding a signed nonce to the authentication request.
+ /// This might increase the request size beyond what some OpenID 1.1 Providers
+ /// (such as Blogger) are capable of handling.</para>
+ /// </remarks>
+ internal bool ProtectDownlevelReplayAttacks { get; set; }
+
+ /// <summary>
+ /// Filters out any disallowed endpoints.
+ /// </summary>
+ /// <param name="endpoints">The endpoints discovered on an Identifier.</param>
+ /// <returns>A sequence of endpoints that satisfy all security requirements.</returns>
+ internal IEnumerable<IdentifierDiscoveryResult> FilterEndpoints(IEnumerable<IdentifierDiscoveryResult> endpoints) {
+ return endpoints
+ .Where(se => !this.RejectDelegatingIdentifiers || se.ClaimedIdentifier == se.ProviderLocalIdentifier)
+ .Where(se => !this.RequireDirectedIdentity || se.ClaimedIdentifier == se.Protocol.ClaimedIdentifierForOPIdentifier);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/RelyingPartyEndpointDescription.cs b/src/DotNetOpenAuth.OpenId/OpenId/RelyingPartyEndpointDescription.cs
new file mode 100644
index 0000000..f0d3b6a
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/RelyingPartyEndpointDescription.cs
@@ -0,0 +1,63 @@
+//-----------------------------------------------------------------------
+// <copyright file="RelyingPartyEndpointDescription.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// A description of some OpenID Relying Party endpoint.
+ /// </summary>
+ /// <remarks>
+ /// This is an immutable type.
+ /// </remarks>
+ internal class RelyingPartyEndpointDescription {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RelyingPartyEndpointDescription"/> class.
+ /// </summary>
+ /// <param name="returnTo">The return to.</param>
+ /// <param name="supportedServiceTypeUris">
+ /// The Type URIs of supported services advertised on a relying party's XRDS document.
+ /// </param>
+ internal RelyingPartyEndpointDescription(Uri returnTo, string[] supportedServiceTypeUris) {
+ Requires.NotNull(returnTo, "returnTo");
+ Requires.NotNull(supportedServiceTypeUris, "supportedServiceTypeUris");
+
+ this.ReturnToEndpoint = returnTo;
+ this.Protocol = GetProtocolFromServices(supportedServiceTypeUris);
+ }
+
+ /// <summary>
+ /// Gets the URL to the login page on the discovered relying party web site.
+ /// </summary>
+ public Uri ReturnToEndpoint { get; private set; }
+
+ /// <summary>
+ /// Gets the OpenId protocol that the discovered relying party supports.
+ /// </summary>
+ public Protocol Protocol { get; private set; }
+
+ /// <summary>
+ /// Derives the highest OpenID protocol that this library and the OpenID Provider have
+ /// in common.
+ /// </summary>
+ /// <param name="supportedServiceTypeUris">The supported service type URIs.</param>
+ /// <returns>The best OpenID protocol version to use when communicating with this Provider.</returns>
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "OpenID", Justification = "Spelling correct")]
+ private static Protocol GetProtocolFromServices(string[] supportedServiceTypeUris) {
+ Protocol protocol = Protocol.FindBestVersion(p => p.RPReturnToTypeURI, supportedServiceTypeUris);
+ if (protocol == null) {
+ throw new InvalidOperationException("Unable to determine the version of OpenID the Relying Party supports.");
+ }
+ return protocol;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/SecuritySettings.cs b/src/DotNetOpenAuth.OpenId/OpenId/SecuritySettings.cs
new file mode 100644
index 0000000..2035c9f
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/SecuritySettings.cs
@@ -0,0 +1,110 @@
+//-----------------------------------------------------------------------
+// <copyright file="SecuritySettings.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId {
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.Specialized;
+ using System.Diagnostics.Contracts;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// Security settings that may be applicable to both relying parties and providers.
+ /// </summary>
+ [Serializable]
+ public abstract class SecuritySettings {
+ /// <summary>
+ /// Gets the default minimum hash bit length.
+ /// </summary>
+ internal const int MinimumHashBitLengthDefault = 160;
+
+ /// <summary>
+ /// Gets the maximum hash bit length default for relying parties.
+ /// </summary>
+ internal const int MaximumHashBitLengthRPDefault = 256;
+
+ /// <summary>
+ /// Gets the maximum hash bit length default for providers.
+ /// </summary>
+ internal const int MaximumHashBitLengthOPDefault = 512;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SecuritySettings"/> class.
+ /// </summary>
+ /// <param name="isProvider">A value indicating whether this class is being instantiated for a Provider.</param>
+ protected SecuritySettings(bool isProvider) {
+ this.MaximumHashBitLength = isProvider ? MaximumHashBitLengthOPDefault : MaximumHashBitLengthRPDefault;
+ this.MinimumHashBitLength = MinimumHashBitLengthDefault;
+ }
+
+ /// <summary>
+ /// Gets or sets the minimum hash length (in bits) allowed to be used in an <see cref="Association"/>
+ /// with the remote party. The default is 160.
+ /// </summary>
+ /// <remarks>
+ /// SHA-1 (160 bits) has been broken. The minimum secure hash length is now 256 bits.
+ /// The default is still a 160 bit minimum to allow interop with common remote parties,
+ /// such as Yahoo! that only supports 160 bits.
+ /// For sites that require high security such as to store bank account information and
+ /// health records, 256 is the recommended value.
+ /// </remarks>
+ public int MinimumHashBitLength { get; set; }
+
+ /// <summary>
+ /// Gets or sets the maximum hash length (in bits) allowed to be used in an <see cref="Association"/>
+ /// with the remote party. The default is 256 for relying parties and 512 for providers.
+ /// </summary>
+ /// <remarks>
+ /// The longer the bit length, the more secure the identities of your visitors are.
+ /// Setting a value higher than 256 on a relying party site may reduce performance
+ /// as many association requests will be denied, causing secondary requests or even
+ /// authentication failures.
+ /// Setting a value higher than 256 on a provider increases security where possible
+ /// without these side-effects.
+ /// </remarks>
+ public int MaximumHashBitLength { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether identifiers that are both OP Identifiers and Claimed Identifiers
+ /// should ever be recognized as claimed identifiers.
+ /// </summary>
+ /// <value>
+ /// The default value is <c>false</c>, per the OpenID 2.0 spec.
+ /// </value>
+ /// <remarks>
+ /// OpenID 2.0 sections 7.3.2.2 and 11.2 specify that OP Identifiers never be recognized as Claimed Identifiers.
+ /// However, for some scenarios it may be desirable for an RP to override this behavior and allow this.
+ /// The security ramifications of setting this property to <c>true</c> have not been fully explored and
+ /// therefore this setting should only be changed with caution.
+ /// </remarks>
+ public bool AllowDualPurposeIdentifiers { get; set; }
+
+ /// <summary>
+ /// Determines whether a named association fits the security requirements.
+ /// </summary>
+ /// <param name="protocol">The protocol carrying the association.</param>
+ /// <param name="associationType">The value of the openid.assoc_type parameter.</param>
+ /// <returns>
+ /// <c>true</c> if the association is permitted given the security requirements; otherwise, <c>false</c>.
+ /// </returns>
+ internal bool IsAssociationInPermittedRange(Protocol protocol, string associationType) {
+ int lengthInBits = HmacShaAssociation.GetSecretLength(protocol, associationType) * 8;
+ return lengthInBits >= this.MinimumHashBitLength && lengthInBits <= this.MaximumHashBitLength;
+ }
+
+ /// <summary>
+ /// Determines whether a given association fits the security requirements.
+ /// </summary>
+ /// <param name="association">The association to check.</param>
+ /// <returns>
+ /// <c>true</c> if the association is permitted given the security requirements; otherwise, <c>false</c>.
+ /// </returns>
+ internal bool IsAssociationInPermittedRange(Association association) {
+ Requires.NotNull(association, "association");
+ return association.HashBitLength >= this.MinimumHashBitLength && association.HashBitLength <= this.MaximumHashBitLength;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/UriDiscoveryService.cs b/src/DotNetOpenAuth.OpenId/OpenId/UriDiscoveryService.cs
new file mode 100644
index 0000000..c262ac9
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/UriDiscoveryService.cs
@@ -0,0 +1,139 @@
+//-----------------------------------------------------------------------
+// <copyright file="UriDiscoveryService.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using System.Text.RegularExpressions;
+ using System.Web.UI.HtmlControls;
+ using System.Xml;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+ using DotNetOpenAuth.Xrds;
+ using DotNetOpenAuth.Yadis;
+
+ /// <summary>
+ /// The discovery service for URI identifiers.
+ /// </summary>
+ public class UriDiscoveryService : IIdentifierDiscoveryService {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="UriDiscoveryService"/> class.
+ /// </summary>
+ public UriDiscoveryService() {
+ }
+
+ #region IDiscoveryService Members
+
+ /// <summary>
+ /// Performs discovery on the specified identifier.
+ /// </summary>
+ /// <param name="identifier">The identifier to perform discovery on.</param>
+ /// <param name="requestHandler">The means to place outgoing HTTP requests.</param>
+ /// <param name="abortDiscoveryChain">if set to <c>true</c>, no further discovery services will be called for this identifier.</param>
+ /// <returns>
+ /// A sequence of service endpoints yielded by discovery. Must not be null, but may be empty.
+ /// </returns>
+ public IEnumerable<IdentifierDiscoveryResult> Discover(Identifier identifier, IDirectWebRequestHandler requestHandler, out bool abortDiscoveryChain) {
+ abortDiscoveryChain = false;
+ var uriIdentifier = identifier as UriIdentifier;
+ if (uriIdentifier == null) {
+ return Enumerable.Empty<IdentifierDiscoveryResult>();
+ }
+
+ var endpoints = new List<IdentifierDiscoveryResult>();
+
+ // Attempt YADIS discovery
+ DiscoveryResult yadisResult = Yadis.Discover(requestHandler, uriIdentifier, identifier.IsDiscoverySecureEndToEnd);
+ if (yadisResult != null) {
+ if (yadisResult.IsXrds) {
+ try {
+ XrdsDocument xrds = new XrdsDocument(yadisResult.ResponseText);
+ var xrdsEndpoints = xrds.XrdElements.CreateServiceEndpoints(yadisResult.NormalizedUri, uriIdentifier);
+
+ // Filter out insecure endpoints if high security is required.
+ if (uriIdentifier.IsDiscoverySecureEndToEnd) {
+ xrdsEndpoints = xrdsEndpoints.Where(se => se.ProviderEndpoint.IsTransportSecure());
+ }
+ endpoints.AddRange(xrdsEndpoints);
+ } catch (XmlException ex) {
+ Logger.Yadis.Error("Error while parsing the XRDS document. Falling back to HTML discovery.", ex);
+ }
+ }
+
+ // Failing YADIS discovery of an XRDS document, we try HTML discovery.
+ if (endpoints.Count == 0) {
+ yadisResult.TryRevertToHtmlResponse();
+ var htmlEndpoints = new List<IdentifierDiscoveryResult>(DiscoverFromHtml(yadisResult.NormalizedUri, uriIdentifier, yadisResult.ResponseText));
+ if (htmlEndpoints.Any()) {
+ Logger.Yadis.DebugFormat("Total services discovered in HTML: {0}", htmlEndpoints.Count);
+ Logger.Yadis.Debug(htmlEndpoints.ToStringDeferred(true));
+ endpoints.AddRange(htmlEndpoints.Where(ep => !uriIdentifier.IsDiscoverySecureEndToEnd || ep.ProviderEndpoint.IsTransportSecure()));
+ if (endpoints.Count == 0) {
+ Logger.Yadis.Info("No HTML discovered endpoints met the security requirements.");
+ }
+ } else {
+ Logger.Yadis.Debug("HTML discovery failed to find any endpoints.");
+ }
+ } else {
+ Logger.Yadis.Debug("Skipping HTML discovery because XRDS contained service endpoints.");
+ }
+ }
+ return endpoints;
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Searches HTML for the HEAD META tags that describe OpenID provider services.
+ /// </summary>
+ /// <param name="claimedIdentifier">The final URL that provided this HTML document.
+ /// This may not be the same as (this) userSuppliedIdentifier if the
+ /// userSuppliedIdentifier pointed to a 301 Redirect.</param>
+ /// <param name="userSuppliedIdentifier">The user supplied identifier.</param>
+ /// <param name="html">The HTML that was downloaded and should be searched.</param>
+ /// <returns>
+ /// A sequence of any discovered ServiceEndpoints.
+ /// </returns>
+ private static IEnumerable<IdentifierDiscoveryResult> DiscoverFromHtml(Uri claimedIdentifier, UriIdentifier userSuppliedIdentifier, string html) {
+ var linkTags = new List<HtmlLink>(HtmlParser.HeadTags<HtmlLink>(html));
+ foreach (var protocol in Protocol.AllPracticalVersions) {
+ // rel attributes are supposed to be interpreted with case INsensitivity,
+ // and is a space-delimited list of values. (http://www.htmlhelp.com/reference/html40/values.html#linktypes)
+ var serverLinkTag = linkTags.WithAttribute("rel").FirstOrDefault(tag => Regex.IsMatch(tag.Attributes["rel"], @"\b" + Regex.Escape(protocol.HtmlDiscoveryProviderKey) + @"\b", RegexOptions.IgnoreCase));
+ if (serverLinkTag == null) {
+ continue;
+ }
+
+ Uri providerEndpoint = null;
+ if (Uri.TryCreate(serverLinkTag.Href, UriKind.Absolute, out providerEndpoint)) {
+ // See if a LocalId tag of the discovered version exists
+ Identifier providerLocalIdentifier = null;
+ var delegateLinkTag = linkTags.WithAttribute("rel").FirstOrDefault(tag => Regex.IsMatch(tag.Attributes["rel"], @"\b" + Regex.Escape(protocol.HtmlDiscoveryLocalIdKey) + @"\b", RegexOptions.IgnoreCase));
+ if (delegateLinkTag != null) {
+ if (Identifier.IsValid(delegateLinkTag.Href)) {
+ providerLocalIdentifier = delegateLinkTag.Href;
+ } else {
+ Logger.Yadis.WarnFormat("Skipping endpoint data because local id is badly formed ({0}).", delegateLinkTag.Href);
+ continue; // skip to next version
+ }
+ }
+
+ // Choose the TypeURI to match the OpenID version detected.
+ string[] typeURIs = { protocol.ClaimedIdentifierServiceTypeURI };
+ yield return IdentifierDiscoveryResult.CreateForClaimedIdentifier(
+ claimedIdentifier,
+ userSuppliedIdentifier,
+ providerLocalIdentifier,
+ new ProviderEndpointDescription(providerEndpoint, typeURIs),
+ (int?)null,
+ (int?)null);
+ }
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/UriIdentifier.cs b/src/DotNetOpenAuth.OpenId/OpenId/UriIdentifier.cs
new file mode 100644
index 0000000..d601aed
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/UriIdentifier.cs
@@ -0,0 +1,729 @@
+//-----------------------------------------------------------------------
+// <copyright file="UriIdentifier.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Reflection;
+ using System.Security;
+ using System.Text;
+ using System.Text.RegularExpressions;
+ using System.Web.UI.HtmlControls;
+ using System.Xml;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Xrds;
+ using DotNetOpenAuth.Yadis;
+
+ /// <summary>
+ /// A URI style of OpenID Identifier.
+ /// </summary>
+ [Serializable]
+ [Pure]
+ public sealed class UriIdentifier : Identifier {
+ /// <summary>
+ /// The allowed protocol schemes in a URI Identifier.
+ /// </summary>
+ private static readonly string[] allowedSchemes = { "http", "https" };
+
+ /// <summary>
+ /// The special scheme to use for HTTP URLs that should not have their paths compressed.
+ /// </summary>
+ private static NonPathCompressingUriParser roundTrippingHttpParser = new NonPathCompressingUriParser(Uri.UriSchemeHttp);
+
+ /// <summary>
+ /// The special scheme to use for HTTPS URLs that should not have their paths compressed.
+ /// </summary>
+ private static NonPathCompressingUriParser roundTrippingHttpsParser = new NonPathCompressingUriParser(Uri.UriSchemeHttps);
+
+ /// <summary>
+ /// The special scheme to use for HTTP URLs that should not have their paths compressed.
+ /// </summary>
+ private static NonPathCompressingUriParser publishableHttpParser = new NonPathCompressingUriParser(Uri.UriSchemeHttp);
+
+ /// <summary>
+ /// The special scheme to use for HTTPS URLs that should not have their paths compressed.
+ /// </summary>
+ private static NonPathCompressingUriParser publishableHttpsParser = new NonPathCompressingUriParser(Uri.UriSchemeHttps);
+
+ /// <summary>
+ /// A value indicating whether scheme substitution is being used to workaround
+ /// .NET path compression that invalidates some OpenIDs that have trailing periods
+ /// in one of their path segments.
+ /// </summary>
+ private static bool schemeSubstitution;
+
+ /// <summary>
+ /// Initializes static members of the <see cref="UriIdentifier"/> class.
+ /// </summary>
+ /// <remarks>
+ /// This method attempts to workaround the .NET Uri class parsing bug described here:
+ /// https://connect.microsoft.com/VisualStudio/feedback/details/386695/system-uri-incorrectly-strips-trailing-dots?wa=wsignin1.0#tabs
+ /// since some identifiers (like some of the pseudonymous identifiers from Yahoo) include path segments
+ /// that end with periods, which the Uri class will typically trim off.
+ /// </remarks>
+ [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Some things just can't be done in a field initializer.")]
+ static UriIdentifier() {
+ // Our first attempt to handle trailing periods in path segments is to leverage
+ // full trust if it's available to rewrite the rules.
+ // In fact this is the ONLY way in .NET 3.5 (and arguably in .NET 4.0) to send
+ // outbound HTTP requests with trailing periods, so it's the only way to perform
+ // discovery on such an identifier.
+ try {
+ UriParser.Register(roundTrippingHttpParser, "dnoarthttp", 80);
+ UriParser.Register(roundTrippingHttpsParser, "dnoarthttps", 443);
+ UriParser.Register(publishableHttpParser, "dnoahttp", 80);
+ UriParser.Register(publishableHttpsParser, "dnoahttps", 443);
+ roundTrippingHttpParser.Initialize(false);
+ roundTrippingHttpsParser.Initialize(false);
+ publishableHttpParser.Initialize(true);
+ publishableHttpsParser.Initialize(true);
+ schemeSubstitution = true;
+ Logger.OpenId.Debug(".NET Uri class path compression overridden.");
+ Reporting.RecordFeatureUse("FullTrust");
+ } catch (SecurityException) {
+ // We must be running in partial trust. Nothing more we can do.
+ Logger.OpenId.Warn("Unable to coerce .NET to stop compressing URI paths due to partial trust limitations. Some URL identifiers may be unable to complete login.");
+ Reporting.RecordFeatureUse("PartialTrust");
+ }
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="UriIdentifier"/> class.
+ /// </summary>
+ /// <param name="uri">The value this identifier will represent.</param>
+ internal UriIdentifier(string uri)
+ : this(uri, false) {
+ Requires.NotNullOrEmpty(uri, "uri");
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="UriIdentifier"/> class.
+ /// </summary>
+ /// <param name="uri">The value this identifier will represent.</param>
+ /// <param name="requireSslDiscovery">if set to <c>true</c> [require SSL discovery].</param>
+ internal UriIdentifier(string uri, bool requireSslDiscovery)
+ : base(uri, requireSslDiscovery) {
+ Requires.NotNullOrEmpty(uri, "uri");
+ Uri canonicalUri;
+ bool schemePrepended;
+ if (!TryCanonicalize(uri, out canonicalUri, requireSslDiscovery, out schemePrepended)) {
+ throw new UriFormatException();
+ }
+ if (requireSslDiscovery && canonicalUri.Scheme != Uri.UriSchemeHttps) {
+ throw new ArgumentException(OpenIdStrings.ExplicitHttpUriSuppliedWithSslRequirement);
+ }
+ this.Uri = canonicalUri;
+ this.SchemeImplicitlyPrepended = schemePrepended;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="UriIdentifier"/> class.
+ /// </summary>
+ /// <param name="uri">The value this identifier will represent.</param>
+ internal UriIdentifier(Uri uri)
+ : this(uri, false) {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="UriIdentifier"/> class.
+ /// </summary>
+ /// <param name="uri">The value this identifier will represent.</param>
+ /// <param name="requireSslDiscovery">if set to <c>true</c> [require SSL discovery].</param>
+ internal UriIdentifier(Uri uri, bool requireSslDiscovery)
+ : base(uri != null ? uri.OriginalString : null, requireSslDiscovery) {
+ Requires.NotNull(uri, "uri");
+
+ string uriAsString = uri.OriginalString;
+ if (schemeSubstitution) {
+ uriAsString = NormalSchemeToSpecialRoundTrippingScheme(uriAsString);
+ }
+
+ if (!TryCanonicalize(uriAsString, out uri)) {
+ throw new UriFormatException();
+ }
+ if (requireSslDiscovery && uri.Scheme != Uri.UriSchemeHttps) {
+ throw new ArgumentException(OpenIdStrings.ExplicitHttpUriSuppliedWithSslRequirement);
+ }
+ this.Uri = uri;
+ this.SchemeImplicitlyPrepended = false;
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether scheme substitution is being used to workaround
+ /// .NET path compression that invalidates some OpenIDs that have trailing periods
+ /// in one of their path segments.
+ /// </summary>
+ internal static bool SchemeSubstitutionTestHook {
+ get { return schemeSubstitution; }
+ set { schemeSubstitution = value; }
+ }
+
+ /// <summary>
+ /// Gets the URI this instance represents.
+ /// </summary>
+ internal Uri Uri { get; private set; }
+
+ /// <summary>
+ /// Gets a value indicating whether the scheme was missing when this
+ /// Identifier was created and added automatically as part of the
+ /// normalization process.
+ /// </summary>
+ internal bool SchemeImplicitlyPrepended { get; private set; }
+
+ /// <summary>
+ /// Gets a value indicating whether this Identifier has characters or patterns that
+ /// the <see cref="Uri"/> class normalizes away and invalidating the Identifier.
+ /// </summary>
+ internal bool ProblematicNormalization {
+ get {
+ if (schemeSubstitution) {
+ // With full trust, we have no problematic URIs
+ return false;
+ }
+
+ var simpleUri = new SimpleUri(this.OriginalString);
+ if (simpleUri.Path.EndsWith(".", StringComparison.Ordinal) || simpleUri.Path.Contains("./")) {
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Converts a <see cref="UriIdentifier"/> instance to a <see cref="Uri"/> instance.
+ /// </summary>
+ /// <param name="identifier">The identifier to convert to an ordinary <see cref="Uri"/> instance.</param>
+ /// <returns>The result of the conversion.</returns>
+ public static implicit operator Uri(UriIdentifier identifier) {
+ if (identifier == null) {
+ return null;
+ }
+ return identifier.Uri;
+ }
+
+ /// <summary>
+ /// Converts a <see cref="Uri"/> instance to a <see cref="UriIdentifier"/> instance.
+ /// </summary>
+ /// <param name="identifier">The <see cref="Uri"/> instance to turn into a <see cref="UriIdentifier"/>.</param>
+ /// <returns>The result of the conversion.</returns>
+ public static implicit operator UriIdentifier(Uri identifier) {
+ if (identifier == null) {
+ return null;
+ }
+ return new UriIdentifier(identifier);
+ }
+
+ /// <summary>
+ /// Tests equality between this URI and another URI.
+ /// </summary>
+ /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
+ /// <returns>
+ /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
+ /// </returns>
+ /// <exception cref="T:System.NullReferenceException">
+ /// The <paramref name="obj"/> parameter is null.
+ /// </exception>
+ public override bool Equals(object obj) {
+ UriIdentifier other = obj as UriIdentifier;
+ if (obj != null && other == null && Identifier.EqualityOnStrings) { // test hook to enable MockIdentifier comparison
+ other = Identifier.Parse(obj.ToString()) as UriIdentifier;
+ }
+ if (other == null) {
+ return false;
+ }
+
+ if (this.ProblematicNormalization || other.ProblematicNormalization) {
+ return new SimpleUri(this.OriginalString).Equals(new SimpleUri(other.OriginalString));
+ } else {
+ return this.Uri == other.Uri;
+ }
+ }
+
+ /// <summary>
+ /// Returns the hash code of this XRI.
+ /// </summary>
+ /// <returns>
+ /// A hash code for the current <see cref="T:System.Object"/>.
+ /// </returns>
+ public override int GetHashCode() {
+ return Uri.GetHashCode();
+ }
+
+ /// <summary>
+ /// Returns the string form of the URI.
+ /// </summary>
+ /// <returns>
+ /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
+ /// </returns>
+ public override string ToString() {
+ if (this.ProblematicNormalization) {
+ return new SimpleUri(this.OriginalString).ToString();
+ } else {
+ return this.Uri.AbsoluteUri;
+ }
+ }
+
+ /// <summary>
+ /// Determines whether a URI is a valid OpenID Identifier (of any kind).
+ /// </summary>
+ /// <param name="uri">The URI to test for OpenID validity.</param>
+ /// <returns>
+ /// <c>true</c> if the identifier is valid; otherwise, <c>false</c>.
+ /// </returns>
+ /// <remarks>
+ /// A valid URI is absolute (not relative) and uses an http(s) scheme.
+ /// </remarks>
+ internal static bool IsValidUri(string uri) {
+ Uri normalized;
+ bool schemePrepended;
+ return TryCanonicalize(uri, out normalized, false, out schemePrepended);
+ }
+
+ /// <summary>
+ /// Determines whether a URI is a valid OpenID Identifier (of any kind).
+ /// </summary>
+ /// <param name="uri">The URI to test for OpenID validity.</param>
+ /// <returns>
+ /// <c>true</c> if the identifier is valid; otherwise, <c>false</c>.
+ /// </returns>
+ /// <remarks>
+ /// A valid URI is absolute (not relative) and uses an http(s) scheme.
+ /// </remarks>
+ internal static bool IsValidUri(Uri uri) {
+ if (uri == null) {
+ return false;
+ }
+ if (!uri.IsAbsoluteUri) {
+ return false;
+ }
+ if (!IsAllowedScheme(uri)) {
+ return false;
+ }
+ return true;
+ }
+
+ /// <summary>
+ /// Returns an <see cref="Identifier"/> that has no URI fragment.
+ /// Quietly returns the original <see cref="Identifier"/> if it is not
+ /// a <see cref="UriIdentifier"/> or no fragment exists.
+ /// </summary>
+ /// <returns>
+ /// A new <see cref="Identifier"/> instance if there was a
+ /// fragment to remove, otherwise this same instance..
+ /// </returns>
+ internal override Identifier TrimFragment() {
+ // If there is no fragment, we have no need to rebuild the Identifier.
+ if (Uri.Fragment == null || Uri.Fragment.Length == 0) {
+ return this;
+ }
+
+ // Strip the fragment.
+ return new UriIdentifier(this.OriginalString.Substring(0, this.OriginalString.IndexOf('#')));
+ }
+
+ /// <summary>
+ /// Converts a given identifier to its secure equivalent.
+ /// UriIdentifiers originally created with an implied HTTP scheme change to HTTPS.
+ /// Discovery is made to require SSL for the entire resolution process.
+ /// </summary>
+ /// <param name="secureIdentifier">The newly created secure identifier.
+ /// If the conversion fails, <paramref name="secureIdentifier"/> retains
+ /// <i>this</i> identifiers identity, but will never discover any endpoints.</param>
+ /// <returns>
+ /// True if the secure conversion was successful.
+ /// False if the Identifier was originally created with an explicit HTTP scheme.
+ /// </returns>
+ internal override bool TryRequireSsl(out Identifier secureIdentifier) {
+ // If this Identifier is already secure, reuse it.
+ if (IsDiscoverySecureEndToEnd) {
+ secureIdentifier = this;
+ return true;
+ }
+
+ // If this identifier already uses SSL for initial discovery, return one
+ // that guarantees it will be used throughout the discovery process.
+ if (string.Equals(Uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) {
+ secureIdentifier = new UriIdentifier(this.Uri, true);
+ return true;
+ }
+
+ // Otherwise, try to make this Identifier secure by normalizing to HTTPS instead of HTTP.
+ if (this.SchemeImplicitlyPrepended) {
+ UriBuilder newIdentifierUri = new UriBuilder(this.Uri);
+ newIdentifierUri.Scheme = Uri.UriSchemeHttps;
+ if (newIdentifierUri.Port == 80) {
+ newIdentifierUri.Port = 443;
+ }
+ secureIdentifier = new UriIdentifier(newIdentifierUri.Uri, true);
+ return true;
+ }
+
+ // This identifier is explicitly NOT https, so we cannot change it.
+ secureIdentifier = new NoDiscoveryIdentifier(this, true);
+ return false;
+ }
+
+ /// <summary>
+ /// Determines whether the given URI is using a scheme in the list of allowed schemes.
+ /// </summary>
+ /// <param name="uri">The URI whose scheme is to be checked.</param>
+ /// <returns>
+ /// <c>true</c> if the scheme is allowed; otherwise, <c>false</c>.
+ /// <c>false</c> is also returned if <paramref name="uri"/> is null.
+ /// </returns>
+ private static bool IsAllowedScheme(string uri) {
+ if (string.IsNullOrEmpty(uri)) {
+ return false;
+ }
+ return Array.FindIndex(
+ allowedSchemes,
+ s => uri.StartsWith(s + Uri.SchemeDelimiter, StringComparison.OrdinalIgnoreCase)) >= 0;
+ }
+
+ /// <summary>
+ /// Determines whether the given URI is using a scheme in the list of allowed schemes.
+ /// </summary>
+ /// <param name="uri">The URI whose scheme is to be checked.</param>
+ /// <returns>
+ /// <c>true</c> if the scheme is allowed; otherwise, <c>false</c>.
+ /// <c>false</c> is also returned if <paramref name="uri"/> is null.
+ /// </returns>
+ private static bool IsAllowedScheme(Uri uri) {
+ if (uri == null) {
+ return false;
+ }
+ return Array.FindIndex(
+ allowedSchemes,
+ s => uri.Scheme.Equals(s, StringComparison.OrdinalIgnoreCase)) >= 0;
+ }
+
+ /// <summary>
+ /// Tries to canonicalize a user-supplied identifier.
+ /// This does NOT convert a user-supplied identifier to a Claimed Identifier!
+ /// </summary>
+ /// <param name="uri">The user-supplied identifier.</param>
+ /// <param name="canonicalUri">The resulting canonical URI.</param>
+ /// <param name="forceHttpsDefaultScheme">If set to <c>true</c> and the user-supplied identifier lacks a scheme, the "https://" scheme will be prepended instead of the standard "http://" one.</param>
+ /// <param name="schemePrepended">if set to <c>true</c> [scheme prepended].</param>
+ /// <returns>
+ /// <c>true</c> if the identifier was valid and could be canonicalized.
+ /// <c>false</c> if the identifier is outside the scope of allowed inputs and should be rejected.
+ /// </returns>
+ /// <remarks>
+ /// Canonicalization is done by adding a scheme in front of an
+ /// identifier if it isn't already present. Other trivial changes that do not
+ /// require network access are also done, such as lower-casing the hostname in the URI.
+ /// </remarks>
+ private static bool TryCanonicalize(string uri, out Uri canonicalUri, bool forceHttpsDefaultScheme, out bool schemePrepended) {
+ Requires.NotNullOrEmpty(uri, "uri");
+
+ canonicalUri = null;
+ try {
+ uri = DoSimpleCanonicalize(uri, forceHttpsDefaultScheme, out schemePrepended);
+ if (schemeSubstitution) {
+ uri = NormalSchemeToSpecialRoundTrippingScheme(uri);
+ }
+
+ // Use a UriBuilder because it helps to normalize the URL as well.
+ return TryCanonicalize(uri, out canonicalUri);
+ } catch (UriFormatException) {
+ // We try not to land here with checks in the try block, but just in case.
+ schemePrepended = false;
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Fixes up the scheme if appropriate.
+ /// </summary>
+ /// <param name="uri">The URI, already in legal form (with http(s):// prepended if necessary).</param>
+ /// <param name="canonicalUri">The resulting canonical URI.</param>
+ /// <returns><c>true</c> if the canonicalization was successful; <c>false</c> otherwise.</returns>
+ /// <remarks>
+ /// This does NOT standardize an OpenID URL for storage in a database, as
+ /// it does nothing to convert the URL to a Claimed Identifier, besides the fact
+ /// that it only deals with URLs whereas OpenID 2.0 supports XRIs.
+ /// For this, you should lookup the value stored in IAuthenticationResponse.ClaimedIdentifier.
+ /// </remarks>
+ [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "The user will see the result of this operation and they want to see it in lower case.")]
+ private static bool TryCanonicalize(string uri, out Uri canonicalUri) {
+ Requires.NotNull(uri, "uri");
+
+ if (schemeSubstitution) {
+ UriBuilder uriBuilder = new UriBuilder(uri);
+
+ // Swap out our round-trippable scheme for the publishable (hidden) scheme.
+ uriBuilder.Scheme = uriBuilder.Scheme == roundTrippingHttpParser.RegisteredScheme ? publishableHttpParser.RegisteredScheme : publishableHttpsParser.RegisteredScheme;
+ canonicalUri = uriBuilder.Uri;
+ } else {
+ canonicalUri = new Uri(uri);
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Gets the special non-compressing scheme or URL for a standard scheme or URL.
+ /// </summary>
+ /// <param name="normal">The ordinary URL or scheme name.</param>
+ /// <returns>The non-compressing equivalent scheme or URL for the given value.</returns>
+ private static string NormalSchemeToSpecialRoundTrippingScheme(string normal) {
+ Requires.NotNullOrEmpty(normal, "normal");
+ Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>()));
+ ErrorUtilities.VerifyInternal(schemeSubstitution, "Wrong schemeSubstitution value.");
+
+ int delimiterIndex = normal.IndexOf(Uri.SchemeDelimiter, StringComparison.Ordinal);
+ string normalScheme = delimiterIndex < 0 ? normal : normal.Substring(0, delimiterIndex);
+ string nonCompressingScheme;
+ if (string.Equals(normalScheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(normalScheme, publishableHttpParser.RegisteredScheme, StringComparison.OrdinalIgnoreCase)) {
+ nonCompressingScheme = roundTrippingHttpParser.RegisteredScheme;
+ } else if (string.Equals(normalScheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(normalScheme, publishableHttpsParser.RegisteredScheme, StringComparison.OrdinalIgnoreCase)) {
+ nonCompressingScheme = roundTrippingHttpsParser.RegisteredScheme;
+ } else {
+ throw new NotSupportedException();
+ }
+
+ return delimiterIndex < 0 ? nonCompressingScheme : nonCompressingScheme + normal.Substring(delimiterIndex);
+ }
+
+ /// <summary>
+ /// Performs the minimal URL normalization to allow a string to be passed to the <see cref="Uri"/> constructor.
+ /// </summary>
+ /// <param name="uri">The user-supplied identifier URI to normalize.</param>
+ /// <param name="forceHttpsDefaultScheme">if set to <c>true</c>, a missing scheme should result in HTTPS being prepended instead of HTTP.</param>
+ /// <param name="schemePrepended">if set to <c>true</c>, the scheme was prepended during normalization.</param>
+ /// <returns>The somewhat normalized URL.</returns>
+ private static string DoSimpleCanonicalize(string uri, bool forceHttpsDefaultScheme, out bool schemePrepended) {
+ Requires.NotNullOrEmpty(uri, "uri");
+
+ schemePrepended = false;
+ uri = uri.Trim();
+
+ // Assume http:// scheme if an allowed scheme isn't given, and strip
+ // fragments off. Consistent with spec section 7.2#3
+ if (!IsAllowedScheme(uri)) {
+ uri = (forceHttpsDefaultScheme ? Uri.UriSchemeHttps : Uri.UriSchemeHttp) +
+ Uri.SchemeDelimiter + uri;
+ schemePrepended = true;
+ }
+
+ return uri;
+ }
+
+#if CONTRACTS_FULL
+ /// <summary>
+ /// Verifies conditions that should be true for any valid state of this object.
+ /// </summary>
+ [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")]
+ [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")]
+ [ContractInvariantMethod]
+ private void ObjectInvariant() {
+ Contract.Invariant(this.Uri != null);
+ Contract.Invariant(this.Uri.AbsoluteUri != null);
+ }
+#endif
+
+ /// <summary>
+ /// A simple URI class that doesn't suffer from the parsing problems of the <see cref="Uri"/> class.
+ /// </summary>
+ internal class SimpleUri {
+ /// <summary>
+ /// URI characters that separate the URI Path from subsequent elements.
+ /// </summary>
+ private static readonly char[] PathEndingCharacters = new char[] { '?', '#' };
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SimpleUri"/> class.
+ /// </summary>
+ /// <param name="value">The value.</param>
+ internal SimpleUri(string value) {
+ Requires.NotNullOrEmpty(value, "value");
+
+ bool schemePrepended;
+ value = DoSimpleCanonicalize(value, false, out schemePrepended);
+
+ // Leverage the Uri class's parsing where we can.
+ Uri uri = new Uri(value);
+ this.Scheme = uri.Scheme;
+ this.Authority = uri.Authority;
+ this.Query = uri.Query;
+ this.Fragment = uri.Fragment;
+
+ // Get the Path out ourselves, since the default Uri parser compresses it too much for OpenID.
+ int schemeLength = value.IndexOf(Uri.SchemeDelimiter, StringComparison.Ordinal);
+ Contract.Assume(schemeLength > 0);
+ int hostStart = schemeLength + Uri.SchemeDelimiter.Length;
+ int hostFinish = value.IndexOf('/', hostStart);
+ if (hostFinish < 0) {
+ this.Path = "/";
+ } else {
+ int pathFinish = value.IndexOfAny(PathEndingCharacters, hostFinish);
+ Contract.Assume(pathFinish >= hostFinish || pathFinish < 0);
+ if (pathFinish < 0) {
+ this.Path = value.Substring(hostFinish);
+ } else {
+ this.Path = value.Substring(hostFinish, pathFinish - hostFinish);
+ }
+ }
+
+ this.Path = NormalizePathEscaping(this.Path);
+ }
+
+ /// <summary>
+ /// Gets the scheme.
+ /// </summary>
+ /// <value>The scheme.</value>
+ public string Scheme { get; private set; }
+
+ /// <summary>
+ /// Gets the authority.
+ /// </summary>
+ /// <value>The authority.</value>
+ public string Authority { get; private set; }
+
+ /// <summary>
+ /// Gets the path of the URI.
+ /// </summary>
+ /// <value>The path from the URI.</value>
+ public string Path { get; private set; }
+
+ /// <summary>
+ /// Gets the query.
+ /// </summary>
+ /// <value>The query.</value>
+ public string Query { get; private set; }
+
+ /// <summary>
+ /// Gets the fragment.
+ /// </summary>
+ /// <value>The fragment.</value>
+ public string Fragment { get; private set; }
+
+ /// <summary>
+ /// Returns a <see cref="System.String"/> that represents this instance.
+ /// </summary>
+ /// <returns>
+ /// A <see cref="System.String"/> that represents this instance.
+ /// </returns>
+ public override string ToString() {
+ return this.Scheme + Uri.SchemeDelimiter + this.Authority + this.Path + this.Query + this.Fragment;
+ }
+
+ /// <summary>
+ /// Determines whether the specified <see cref="System.Object"/> is equal to this instance.
+ /// </summary>
+ /// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
+ /// <returns>
+ /// <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
+ /// </returns>
+ /// <exception cref="T:System.NullReferenceException">
+ /// The <paramref name="obj"/> parameter is null.
+ /// </exception>
+ public override bool Equals(object obj) {
+ SimpleUri other = obj as SimpleUri;
+ if (other == null) {
+ return false;
+ }
+
+ // Note that this equality check is intentionally leaving off the Fragment part
+ // to match Uri behavior, and is intentionally being case sensitive and insensitive
+ // for different parts.
+ return string.Equals(this.Scheme, other.Scheme, StringComparison.OrdinalIgnoreCase) &&
+ string.Equals(this.Authority, other.Authority, StringComparison.OrdinalIgnoreCase) &&
+ string.Equals(this.Path, other.Path, StringComparison.Ordinal) &&
+ string.Equals(this.Query, other.Query, StringComparison.Ordinal);
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>
+ /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
+ /// </returns>
+ public override int GetHashCode() {
+ int hashCode = 0;
+ hashCode += StringComparer.OrdinalIgnoreCase.GetHashCode(this.Scheme);
+ hashCode += StringComparer.OrdinalIgnoreCase.GetHashCode(this.Authority);
+ hashCode += StringComparer.Ordinal.GetHashCode(this.Path);
+ hashCode += StringComparer.Ordinal.GetHashCode(this.Query);
+ return hashCode;
+ }
+
+ /// <summary>
+ /// Normalizes the characters that are escaped in the given URI path.
+ /// </summary>
+ /// <param name="path">The path to normalize.</param>
+ /// <returns>The given path, with exactly those characters escaped which should be.</returns>
+ private static string NormalizePathEscaping(string path) {
+ Requires.NotNull(path, "path");
+
+ string[] segments = path.Split('/');
+ for (int i = 0; i < segments.Length; i++) {
+ segments[i] = Uri.EscapeDataString(Uri.UnescapeDataString(segments[i]));
+ }
+
+ return string.Join("/", segments);
+ }
+ }
+
+ /// <summary>
+ /// A URI parser that does not compress paths, such as trimming trailing periods from path segments.
+ /// </summary>
+ private class NonPathCompressingUriParser : GenericUriParser {
+ /// <summary>
+ /// The field that stores the scheme that this parser is registered under.
+ /// </summary>
+ private static FieldInfo schemeField;
+
+ /// <summary>
+ /// The standard "http" or "https" scheme that this parser is subverting.
+ /// </summary>
+ private string standardScheme;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="NonPathCompressingUriParser"/> class.
+ /// </summary>
+ /// <param name="standardScheme">The standard scheme that this parser will be subverting.</param>
+ public NonPathCompressingUriParser(string standardScheme)
+ : base(GenericUriParserOptions.DontCompressPath | GenericUriParserOptions.IriParsing | GenericUriParserOptions.Idn) {
+ Requires.NotNullOrEmpty(standardScheme, "standardScheme");
+ this.standardScheme = standardScheme;
+ }
+
+ /// <summary>
+ /// Gets the scheme this parser is registered under.
+ /// </summary>
+ /// <value>The registered scheme.</value>
+ internal string RegisteredScheme { get; private set; }
+
+ /// <summary>
+ /// Initializes this parser with the actual scheme it should appear to be.
+ /// </summary>
+ /// <param name="hideNonStandardScheme">if set to <c>true</c> Uris using this scheme will look like they're using the original standard scheme.</param>
+ [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Schemes are traditionally displayed in lowercase.")]
+ internal void Initialize(bool hideNonStandardScheme) {
+ if (schemeField == null) {
+ schemeField =
+ typeof(UriParser).GetField("m_Scheme", BindingFlags.NonPublic | BindingFlags.Instance) ?? // .NET
+ typeof(UriParser).GetField("scheme_name", BindingFlags.NonPublic | BindingFlags.Instance); // Mono
+ ErrorUtilities.VerifyInternal(schemeField != null, "Unable to find the private field UriParser.m_Scheme");
+ }
+
+ this.RegisteredScheme = (string)schemeField.GetValue(this);
+
+ if (hideNonStandardScheme) {
+ schemeField.SetValue(this, this.standardScheme.ToLowerInvariant());
+ }
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/XriDiscoveryProxyService.cs b/src/DotNetOpenAuth.OpenId/OpenId/XriDiscoveryProxyService.cs
new file mode 100644
index 0000000..8265c75
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/XriDiscoveryProxyService.cs
@@ -0,0 +1,108 @@
+//-----------------------------------------------------------------------
+// <copyright file="XriDiscoveryProxyService.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.Linq;
+ using System.Text;
+ using System.Xml;
+ using DotNetOpenAuth.Configuration;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+ using DotNetOpenAuth.Xrds;
+ using DotNetOpenAuth.Yadis;
+
+ /// <summary>
+ /// The discovery service for XRI identifiers that uses an XRI proxy resolver for discovery.
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Xri", Justification = "Acronym")]
+ public class XriDiscoveryProxyService : IIdentifierDiscoveryService {
+ /// <summary>
+ /// The magic URL that will provide us an XRDS document for a given XRI identifier.
+ /// </summary>
+ /// <remarks>
+ /// We use application/xrd+xml instead of application/xrds+xml because it gets
+ /// xri.net to automatically give us exactly the right XRD element for community i-names
+ /// automatically, saving us having to choose which one to use out of the result.
+ /// The ssl=true parameter tells the proxy resolver to accept only SSL connections
+ /// when resolving community i-names.
+ /// </remarks>
+ private const string XriResolverProxyTemplate = "https://{1}/{0}?_xrd_r=application/xrd%2Bxml;sep=false";
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="XriDiscoveryProxyService"/> class.
+ /// </summary>
+ public XriDiscoveryProxyService() {
+ }
+
+ #region IDiscoveryService Members
+
+ /// <summary>
+ /// Performs discovery on the specified identifier.
+ /// </summary>
+ /// <param name="identifier">The identifier to perform discovery on.</param>
+ /// <param name="requestHandler">The means to place outgoing HTTP requests.</param>
+ /// <param name="abortDiscoveryChain">if set to <c>true</c>, no further discovery services will be called for this identifier.</param>
+ /// <returns>
+ /// A sequence of service endpoints yielded by discovery. Must not be null, but may be empty.
+ /// </returns>
+ public IEnumerable<IdentifierDiscoveryResult> Discover(Identifier identifier, IDirectWebRequestHandler requestHandler, out bool abortDiscoveryChain) {
+ abortDiscoveryChain = false;
+ var xriIdentifier = identifier as XriIdentifier;
+ if (xriIdentifier == null) {
+ return Enumerable.Empty<IdentifierDiscoveryResult>();
+ }
+
+ return DownloadXrds(xriIdentifier, requestHandler).XrdElements.CreateServiceEndpoints(xriIdentifier);
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Downloads the XRDS document for this XRI.
+ /// </summary>
+ /// <param name="identifier">The identifier.</param>
+ /// <param name="requestHandler">The request handler.</param>
+ /// <returns>The XRDS document.</returns>
+ private static XrdsDocument DownloadXrds(XriIdentifier identifier, IDirectWebRequestHandler requestHandler) {
+ Requires.NotNull(identifier, "identifier");
+ Requires.NotNull(requestHandler, "requestHandler");
+ Contract.Ensures(Contract.Result<XrdsDocument>() != null);
+ XrdsDocument doc;
+ using (var xrdsResponse = Yadis.Request(requestHandler, GetXrdsUrl(identifier), identifier.IsDiscoverySecureEndToEnd)) {
+ doc = new XrdsDocument(XmlReader.Create(xrdsResponse.ResponseStream));
+ }
+ ErrorUtilities.VerifyProtocol(doc.IsXrdResolutionSuccessful, OpenIdStrings.XriResolutionFailed);
+ return doc;
+ }
+
+ /// <summary>
+ /// Gets the URL from which this XRI's XRDS document may be downloaded.
+ /// </summary>
+ /// <param name="identifier">The identifier.</param>
+ /// <returns>The URI to HTTP GET from to get the services.</returns>
+ private static Uri GetXrdsUrl(XriIdentifier identifier) {
+ ErrorUtilities.VerifyProtocol(OpenIdElement.Configuration.XriResolver.Enabled, OpenIdStrings.XriResolutionDisabled);
+ string xriResolverProxy = XriResolverProxyTemplate;
+ if (identifier.IsDiscoverySecureEndToEnd) {
+ // Indicate to xri.net that we require SSL to be used for delegated resolution
+ // of community i-names.
+ xriResolverProxy += ";https=true";
+ }
+
+ return new Uri(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ xriResolverProxy,
+ identifier,
+ OpenIdElement.Configuration.XriResolver.Proxy.Name));
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/XriIdentifier.cs b/src/DotNetOpenAuth.OpenId/OpenId/XriIdentifier.cs
new file mode 100644
index 0000000..f6a633f
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/XriIdentifier.cs
@@ -0,0 +1,207 @@
+//-----------------------------------------------------------------------
+// <copyright file="XriIdentifier.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.Xml;
+ using DotNetOpenAuth.Configuration;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Xrds;
+ using DotNetOpenAuth.Yadis;
+
+ /// <summary>
+ /// An XRI style of OpenID Identifier.
+ /// </summary>
+ [Serializable]
+ [ContractVerification(true)]
+ [Pure]
+ public sealed class XriIdentifier : Identifier {
+ /// <summary>
+ /// An XRI always starts with one of these symbols.
+ /// </summary>
+ internal static readonly char[] GlobalContextSymbols = { '=', '@', '+', '$', '!' };
+
+ /// <summary>
+ /// The scheme and separator "xri://"
+ /// </summary>
+ private const string XriScheme = "xri://";
+
+ /// <summary>
+ /// Backing store for the <see cref="CanonicalXri"/> property.
+ /// </summary>
+ private readonly string canonicalXri;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="XriIdentifier"/> class.
+ /// </summary>
+ /// <param name="xri">The string value of the XRI.</param>
+ internal XriIdentifier(string xri)
+ : this(xri, false) {
+ Requires.NotNullOrEmpty(xri, "xri");
+ Requires.Format(IsValidXri(xri), OpenIdStrings.InvalidXri);
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="XriIdentifier"/> class.
+ /// </summary>
+ /// <param name="xri">The XRI that this Identifier will represent.</param>
+ /// <param name="requireSsl">
+ /// If set to <c>true</c>, discovery and the initial authentication redirect will
+ /// only succeed if it can be done entirely using SSL.
+ /// </param>
+ internal XriIdentifier(string xri, bool requireSsl)
+ : base(xri, requireSsl) {
+ Requires.NotNullOrEmpty(xri, "xri");
+ Requires.Format(IsValidXri(xri), OpenIdStrings.InvalidXri);
+ Contract.Assume(xri != null); // Proven by IsValidXri
+ this.OriginalXri = xri;
+ this.canonicalXri = CanonicalizeXri(xri);
+ }
+
+ /// <summary>
+ /// Gets the original XRI supplied to the constructor.
+ /// </summary>
+ internal string OriginalXri { get; private set; }
+
+ /// <summary>
+ /// Gets the canonical form of the XRI string.
+ /// </summary>
+ internal string CanonicalXri {
+ get {
+ Contract.Ensures(Contract.Result<string>() != null);
+ return this.canonicalXri;
+ }
+ }
+
+ /// <summary>
+ /// Tests equality between this XRI and another XRI.
+ /// </summary>
+ /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
+ /// <returns>
+ /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
+ /// </returns>
+ /// <exception cref="T:System.NullReferenceException">
+ /// The <paramref name="obj"/> parameter is null.
+ /// </exception>
+ public override bool Equals(object obj) {
+ XriIdentifier other = obj as XriIdentifier;
+ if (obj != null && other == null && Identifier.EqualityOnStrings) { // test hook to enable MockIdentifier comparison
+ string objString = obj.ToString();
+ ErrorUtilities.VerifyInternal(!string.IsNullOrEmpty(objString), "Identifier.ToString() returned a null or empty string.");
+ other = Identifier.Parse(objString) as XriIdentifier;
+ }
+ if (other == null) {
+ return false;
+ }
+ return this.CanonicalXri == other.CanonicalXri;
+ }
+
+ /// <summary>
+ /// Returns the hash code of this XRI.
+ /// </summary>
+ /// <returns>
+ /// A hash code for the current <see cref="T:System.Object"/>.
+ /// </returns>
+ public override int GetHashCode() {
+ return this.CanonicalXri.GetHashCode();
+ }
+
+ /// <summary>
+ /// Returns the canonical string form of the XRI.
+ /// </summary>
+ /// <returns>
+ /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
+ /// </returns>
+ public override string ToString() {
+ return this.CanonicalXri;
+ }
+
+ /// <summary>
+ /// Tests whether a given string represents a valid XRI format.
+ /// </summary>
+ /// <param name="xri">The value to test for XRI validity.</param>
+ /// <returns>
+ /// <c>true</c> if the given string constitutes a valid XRI; otherwise, <c>false</c>.
+ /// </returns>
+ internal static bool IsValidXri(string xri) {
+ Requires.NotNullOrEmpty(xri, "xri");
+ xri = xri.Trim();
+
+ // TODO: better validation code here
+ return xri.IndexOfAny(GlobalContextSymbols) == 0
+ || xri.StartsWith("(", StringComparison.Ordinal)
+ || xri.StartsWith(XriScheme, StringComparison.OrdinalIgnoreCase);
+ }
+
+ /// <summary>
+ /// Returns an <see cref="Identifier"/> that has no URI fragment.
+ /// Quietly returns the original <see cref="Identifier"/> if it is not
+ /// a <see cref="UriIdentifier"/> or no fragment exists.
+ /// </summary>
+ /// <returns>
+ /// A new <see cref="Identifier"/> instance if there was a
+ /// fragment to remove, otherwise this same instance..
+ /// </returns>
+ /// <remarks>
+ /// XRI Identifiers never have a fragment part, and thus this method
+ /// always returns this same instance.
+ /// </remarks>
+ internal override Identifier TrimFragment() {
+ return this;
+ }
+
+ /// <summary>
+ /// Converts a given identifier to its secure equivalent.
+ /// UriIdentifiers originally created with an implied HTTP scheme change to HTTPS.
+ /// Discovery is made to require SSL for the entire resolution process.
+ /// </summary>
+ /// <param name="secureIdentifier">The newly created secure identifier.
+ /// If the conversion fails, <paramref name="secureIdentifier"/> retains
+ /// <i>this</i> identifiers identity, but will never discover any endpoints.</param>
+ /// <returns>
+ /// True if the secure conversion was successful.
+ /// False if the Identifier was originally created with an explicit HTTP scheme.
+ /// </returns>
+ [ContractVerification(false)] // bugs/limitations in CC static analysis
+ internal override bool TryRequireSsl(out Identifier secureIdentifier) {
+ secureIdentifier = IsDiscoverySecureEndToEnd ? this : new XriIdentifier(this, true);
+ return true;
+ }
+
+ /// <summary>
+ /// Takes any valid form of XRI string and returns the canonical form of the same XRI.
+ /// </summary>
+ /// <param name="xri">The xri to canonicalize.</param>
+ /// <returns>The canonicalized form of the XRI.</returns>
+ /// <remarks>The canonical form, per the OpenID spec, is no scheme and no whitespace on either end.</remarks>
+ private static string CanonicalizeXri(string xri) {
+ Requires.NotNull(xri, "xri");
+ Contract.Ensures(Contract.Result<string>() != null);
+ xri = xri.Trim();
+ if (xri.StartsWith(XriScheme, StringComparison.OrdinalIgnoreCase)) {
+ Contract.Assume(XriScheme.Length <= xri.Length); // should be implied by StartsWith
+ xri = xri.Substring(XriScheme.Length);
+ }
+ return xri;
+ }
+
+#if CONTRACTS_FULL
+ /// <summary>
+ /// Verifies conditions that should be true for any valid state of this object.
+ /// </summary>
+ [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")]
+ [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")]
+ [ContractInvariantMethod]
+ private void ObjectInvariant() {
+ Contract.Invariant(this.canonicalXri != null);
+ }
+#endif
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/OpenIdXrdsHelperRelyingParty.cs b/src/DotNetOpenAuth.OpenId/OpenIdXrdsHelperRelyingParty.cs
new file mode 100644
index 0000000..4e3221f
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenIdXrdsHelperRelyingParty.cs
@@ -0,0 +1,163 @@
+//-----------------------------------------------------------------------
+// <copyright file="OpenIdXrdsHelperRelyingParty.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+ using DotNetOpenAuth.Xrds;
+
+ /// <summary>
+ /// Adds OpenID-specific extension methods to the XrdsDocument class.
+ /// </summary>
+ internal static class OpenIdXrdsHelperRelyingParty {
+ /// <summary>
+ /// Creates the service endpoints described in this document, useful for requesting
+ /// authentication of one of the OpenID Providers that result from it.
+ /// </summary>
+ /// <param name="xrds">The XrdsDocument instance to use in this process.</param>
+ /// <param name="claimedIdentifier">The claimed identifier that was used to discover this XRDS document.</param>
+ /// <param name="userSuppliedIdentifier">The user supplied identifier.</param>
+ /// <returns>
+ /// A sequence of OpenID Providers that can assert ownership of the <paramref name="claimedIdentifier"/>.
+ /// </returns>
+ internal static IEnumerable<IdentifierDiscoveryResult> CreateServiceEndpoints(this IEnumerable<XrdElement> xrds, UriIdentifier claimedIdentifier, UriIdentifier userSuppliedIdentifier) {
+ Requires.NotNull(xrds, "xrds");
+ Requires.NotNull(claimedIdentifier, "claimedIdentifier");
+ Requires.NotNull(userSuppliedIdentifier, "userSuppliedIdentifier");
+ Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null);
+
+ var endpoints = new List<IdentifierDiscoveryResult>();
+ endpoints.AddRange(xrds.GenerateOPIdentifierServiceEndpoints(userSuppliedIdentifier));
+ endpoints.AddRange(xrds.GenerateClaimedIdentifierServiceEndpoints(claimedIdentifier, userSuppliedIdentifier));
+
+ Logger.Yadis.DebugFormat("Total services discovered in XRDS: {0}", endpoints.Count);
+ Logger.Yadis.Debug(endpoints.ToStringDeferred(true));
+ return endpoints;
+ }
+
+ /// <summary>
+ /// Creates the service endpoints described in this document, useful for requesting
+ /// authentication of one of the OpenID Providers that result from it.
+ /// </summary>
+ /// <param name="xrds">The XrdsDocument instance to use in this process.</param>
+ /// <param name="userSuppliedIdentifier">The user-supplied i-name that was used to discover this XRDS document.</param>
+ /// <returns>A sequence of OpenID Providers that can assert ownership of the canonical ID given in this document.</returns>
+ internal static IEnumerable<IdentifierDiscoveryResult> CreateServiceEndpoints(this IEnumerable<XrdElement> xrds, XriIdentifier userSuppliedIdentifier) {
+ Requires.NotNull(xrds, "xrds");
+ Requires.NotNull(userSuppliedIdentifier, "userSuppliedIdentifier");
+ Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null);
+
+ var endpoints = new List<IdentifierDiscoveryResult>();
+ endpoints.AddRange(xrds.GenerateOPIdentifierServiceEndpoints(userSuppliedIdentifier));
+ endpoints.AddRange(xrds.GenerateClaimedIdentifierServiceEndpoints(userSuppliedIdentifier));
+ Logger.Yadis.DebugFormat("Total services discovered in XRDS: {0}", endpoints.Count);
+ Logger.Yadis.Debug(endpoints.ToStringDeferred(true));
+ return endpoints;
+ }
+
+ /// <summary>
+ /// Generates OpenID Providers that can authenticate using directed identity.
+ /// </summary>
+ /// <param name="xrds">The XrdsDocument instance to use in this process.</param>
+ /// <param name="opIdentifier">The OP Identifier entered (and resolved) by the user. Essentially the user-supplied identifier.</param>
+ /// <returns>A sequence of the providers that can offer directed identity services.</returns>
+ private static IEnumerable<IdentifierDiscoveryResult> GenerateOPIdentifierServiceEndpoints(this IEnumerable<XrdElement> xrds, Identifier opIdentifier) {
+ Requires.NotNull(xrds, "xrds");
+ Requires.NotNull(opIdentifier, "opIdentifier");
+ Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null);
+ return from service in xrds.FindOPIdentifierServices()
+ from uri in service.UriElements
+ let protocol = Protocol.FindBestVersion(p => p.OPIdentifierServiceTypeURI, service.TypeElementUris)
+ let providerDescription = new ProviderEndpointDescription(uri.Uri, service.TypeElementUris)
+ select IdentifierDiscoveryResult.CreateForProviderIdentifier(opIdentifier, providerDescription, service.Priority, uri.Priority);
+ }
+
+ /// <summary>
+ /// Generates the OpenID Providers that are capable of asserting ownership
+ /// of a particular URI claimed identifier.
+ /// </summary>
+ /// <param name="xrds">The XrdsDocument instance to use in this process.</param>
+ /// <param name="claimedIdentifier">The claimed identifier.</param>
+ /// <param name="userSuppliedIdentifier">The user supplied identifier.</param>
+ /// <returns>
+ /// A sequence of the providers that can assert ownership of the given identifier.
+ /// </returns>
+ private static IEnumerable<IdentifierDiscoveryResult> GenerateClaimedIdentifierServiceEndpoints(this IEnumerable<XrdElement> xrds, UriIdentifier claimedIdentifier, UriIdentifier userSuppliedIdentifier) {
+ Requires.NotNull(xrds, "xrds");
+ Requires.NotNull(claimedIdentifier, "claimedIdentifier");
+ Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null);
+
+ return from service in xrds.FindClaimedIdentifierServices()
+ from uri in service.UriElements
+ where uri.Uri != null
+ let providerEndpoint = new ProviderEndpointDescription(uri.Uri, service.TypeElementUris)
+ select IdentifierDiscoveryResult.CreateForClaimedIdentifier(claimedIdentifier, userSuppliedIdentifier, service.ProviderLocalIdentifier, providerEndpoint, service.Priority, uri.Priority);
+ }
+
+ /// <summary>
+ /// Generates the OpenID Providers that are capable of asserting ownership
+ /// of a particular XRI claimed identifier.
+ /// </summary>
+ /// <param name="xrds">The XrdsDocument instance to use in this process.</param>
+ /// <param name="userSuppliedIdentifier">The i-name supplied by the user.</param>
+ /// <returns>A sequence of the providers that can assert ownership of the given identifier.</returns>
+ private static IEnumerable<IdentifierDiscoveryResult> GenerateClaimedIdentifierServiceEndpoints(this IEnumerable<XrdElement> xrds, XriIdentifier userSuppliedIdentifier) {
+ // Cannot use code contracts because this method uses yield return.
+ ////Requires.NotNull(xrds, "xrds");
+ ////Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null);
+ ErrorUtilities.VerifyArgumentNotNull(xrds, "xrds");
+
+ foreach (var service in xrds.FindClaimedIdentifierServices()) {
+ foreach (var uri in service.UriElements) {
+ // spec section 7.3.2.3 on Claimed Id -> CanonicalID substitution
+ if (service.Xrd.CanonicalID == null) {
+ Logger.Yadis.WarnFormat(XrdsStrings.MissingCanonicalIDElement, userSuppliedIdentifier);
+ break; // skip on to next service
+ }
+ ErrorUtilities.VerifyProtocol(service.Xrd.IsCanonicalIdVerified, XrdsStrings.CIDVerificationFailed, userSuppliedIdentifier);
+
+ // In the case of XRI names, the ClaimedId is actually the CanonicalID.
+ var claimedIdentifier = new XriIdentifier(service.Xrd.CanonicalID);
+ var providerEndpoint = new ProviderEndpointDescription(uri.Uri, service.TypeElementUris);
+ yield return IdentifierDiscoveryResult.CreateForClaimedIdentifier(claimedIdentifier, userSuppliedIdentifier, service.ProviderLocalIdentifier, providerEndpoint, service.Priority, uri.Priority);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Enumerates the XRDS service elements that describe OpenID Providers offering directed identity assertions.
+ /// </summary>
+ /// <param name="xrds">The XrdsDocument instance to use in this process.</param>
+ /// <returns>A sequence of service elements.</returns>
+ private static IEnumerable<ServiceElement> FindOPIdentifierServices(this IEnumerable<XrdElement> xrds) {
+ Requires.NotNull(xrds, "xrds");
+ Contract.Ensures(Contract.Result<IEnumerable<ServiceElement>>() != null);
+
+ return from xrd in xrds
+ from service in xrd.OpenIdProviderIdentifierServices
+ select service;
+ }
+
+ /// <summary>
+ /// Returns the OpenID-compatible services described by a given XRDS document,
+ /// in priority order.
+ /// </summary>
+ /// <param name="xrds">The XrdsDocument instance to use in this process.</param>
+ /// <returns>A sequence of the services offered.</returns>
+ private static IEnumerable<ServiceElement> FindClaimedIdentifierServices(this IEnumerable<XrdElement> xrds) {
+ Requires.NotNull(xrds, "xrds");
+ Contract.Ensures(Contract.Result<IEnumerable<ServiceElement>>() != null);
+
+ return from xrd in xrds
+ from service in xrd.OpenIdClaimedIdentifierServices
+ select service;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.OpenId/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..00fb93f
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/Properties/AssemblyInfo.cs
@@ -0,0 +1,66 @@
+//-----------------------------------------------------------------------
+// <copyright file="AssemblyInfo.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+// We DON'T put an AssemblyVersionAttribute in here because it is generated in the build.
+
+using System;
+using System.Diagnostics.Contracts;
+using System.Net;
+using System.Reflection;
+using System.Resources;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Security;
+using System.Security.Permissions;
+using System.Web.UI;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("DotNetOpenAuth OpenID")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("DotNetOpenAuth")]
+[assembly: AssemblyCopyright("Copyright © 2011 Outercurve Foundation")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: NeutralResourcesLanguage("en-US")]
+[assembly: CLSCompliant(true)]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("7d73990c-47c0-4256-9f20-a893add9e289")]
+
+[assembly: ContractVerification(true)]
+
+#if StrongNameSigned
+// See comment at top of this file. We need this so that strong-naming doesn't
+// keep this assembly from being useful to shared host (medium trust) web sites.
+[assembly: AllowPartiallyTrustedCallers]
+
+[assembly: InternalsVisibleTo("DotNetOpenAuth.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
+[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
+[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.UI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
+[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.RelyingParty, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
+[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.RelyingParty.UI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
+[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.Provider, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
+[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.Provider.UI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100AD093C3765257C89A7010E853F2C7C741FF92FA8ACE06D7B8254702CAD5CF99104447F63AB05F8BB6F51CE0D81C8C93D2FCE8C20AAFF7042E721CBA16EAAE98778611DED11C0ABC8900DC5667F99B50A9DADEC24DBD8F2C91E3E8AD300EF64F1B4B9536CEB16FB440AF939F57624A9B486F867807C649AE4830EAB88C6C03998")]
+[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
+#else
+[assembly: InternalsVisibleTo("DotNetOpenAuth.Test")]
+[assembly: InternalsVisibleTo("DotNetOpenAuth.OAuth")]
+[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.UI")]
+[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.RelyingParty")]
+[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.RelyingParty.UI")]
+[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.Provider")]
+[assembly: InternalsVisibleTo("DotNetOpenAuth.OpenId.Provider.UI")]
+[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
+#endif
diff --git a/src/DotNetOpenAuth.OpenId/Xrds/ServiceElement.cs b/src/DotNetOpenAuth.OpenId/Xrds/ServiceElement.cs
new file mode 100644
index 0000000..0886e78
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/Xrds/ServiceElement.cs
@@ -0,0 +1,133 @@
+//-----------------------------------------------------------------------
+// <copyright file="ServiceElement.cs" company="Outercurve Foundation, Scott Hanselman">
+// Copyright (c) Outercurve Foundation, Scott Hanselman. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Xrds {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Xml.XPath;
+ using DotNetOpenAuth.OpenId;
+
+ /// <summary>
+ /// The Service element in an XRDS document.
+ /// </summary>
+ internal class ServiceElement : XrdsNode, IComparable<ServiceElement> {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ServiceElement"/> class.
+ /// </summary>
+ /// <param name="serviceElement">The service element.</param>
+ /// <param name="parent">The parent.</param>
+ public ServiceElement(XPathNavigator serviceElement, XrdElement parent) :
+ base(serviceElement, parent) {
+ }
+
+ /// <summary>
+ /// Gets the XRD parent element.
+ /// </summary>
+ public XrdElement Xrd {
+ get { return (XrdElement)ParentNode; }
+ }
+
+ /// <summary>
+ /// Gets the priority.
+ /// </summary>
+ public int? Priority {
+ get {
+ XPathNavigator n = Node.SelectSingleNode("@priority", XmlNamespaceResolver);
+ return n != null ? n.ValueAsInt : (int?)null;
+ }
+ }
+
+ /// <summary>
+ /// Gets the URI child elements.
+ /// </summary>
+ public IEnumerable<UriElement> UriElements {
+ get {
+ List<UriElement> uris = new List<UriElement>();
+ foreach (XPathNavigator node in Node.Select("xrd:URI", XmlNamespaceResolver)) {
+ uris.Add(new UriElement(node, this));
+ }
+ uris.Sort();
+ return uris;
+ }
+ }
+
+ /// <summary>
+ /// Gets the type child elements.
+ /// </summary>
+ /// <value>The type elements.</value>
+ public IEnumerable<TypeElement> TypeElements {
+ get {
+ foreach (XPathNavigator node in Node.Select("xrd:Type", XmlNamespaceResolver)) {
+ yield return new TypeElement(node, this);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets the type child element's URIs.
+ /// </summary>
+ public string[] TypeElementUris {
+ get {
+ return this.TypeElements.Select(type => type.Uri).ToArray();
+ }
+ }
+
+ /// <summary>
+ /// Gets the OP Local Identifier.
+ /// </summary>
+ public Identifier ProviderLocalIdentifier {
+ get {
+ var n = Node.SelectSingleNode("xrd:LocalID", XmlNamespaceResolver)
+ ?? Node.SelectSingleNode("openid10:Delegate", XmlNamespaceResolver);
+ if (n != null && n.Value != null) {
+ string value = n.Value.Trim();
+ if (value.Length > 0) {
+ return n.Value;
+ }
+ }
+
+ return null;
+ }
+ }
+
+ #region IComparable<ServiceElement> Members
+
+ /// <summary>
+ /// Compares the current object with another object of the same type.
+ /// </summary>
+ /// <param name="other">An object to compare with this object.</param>
+ /// <returns>
+ /// A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has the following meanings:
+ /// Value
+ /// Meaning
+ /// Less than zero
+ /// This object is less than the <paramref name="other"/> parameter.
+ /// Zero
+ /// This object is equal to <paramref name="other"/>.
+ /// Greater than zero
+ /// This object is greater than <paramref name="other"/>.
+ /// </returns>
+ public int CompareTo(ServiceElement other) {
+ if (other == null) {
+ return -1;
+ }
+ if (this.Priority.HasValue && other.Priority.HasValue) {
+ return this.Priority.Value.CompareTo(other.Priority.Value);
+ } else {
+ if (this.Priority.HasValue) {
+ return -1;
+ } else if (other.Priority.HasValue) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/Xrds/TypeElement.cs b/src/DotNetOpenAuth.OpenId/Xrds/TypeElement.cs
new file mode 100644
index 0000000..717dfee
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/Xrds/TypeElement.cs
@@ -0,0 +1,34 @@
+//-----------------------------------------------------------------------
+// <copyright file="TypeElement.cs" company="Outercurve Foundation, Scott Hanselman">
+// Copyright (c) Outercurve Foundation, Scott Hanselman. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Xrds {
+ using System;
+ using System.Diagnostics.Contracts;
+ using System.Xml.XPath;
+
+ /// <summary>
+ /// The Type element in an XRDS document.
+ /// </summary>
+ internal class TypeElement : XrdsNode {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TypeElement"/> class.
+ /// </summary>
+ /// <param name="typeElement">The type element.</param>
+ /// <param name="parent">The parent.</param>
+ public TypeElement(XPathNavigator typeElement, ServiceElement parent) :
+ base(typeElement, parent) {
+ Requires.NotNull(typeElement, "typeElement");
+ Requires.NotNull(parent, "parent");
+ }
+
+ /// <summary>
+ /// Gets the URI.
+ /// </summary>
+ public string Uri {
+ get { return Node.Value; }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/Xrds/UriElement.cs b/src/DotNetOpenAuth.OpenId/Xrds/UriElement.cs
new file mode 100644
index 0000000..bb69211
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/Xrds/UriElement.cs
@@ -0,0 +1,98 @@
+//-----------------------------------------------------------------------
+// <copyright file="UriElement.cs" company="Outercurve Foundation, Scott Hanselman">
+// Copyright (c) Outercurve Foundation, Scott Hanselman. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Xrds {
+ using System;
+ using System.Xml.XPath;
+
+ /// <summary>
+ /// The Uri element in an XRDS document.
+ /// </summary>
+ internal class UriElement : XrdsNode, IComparable<UriElement> {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="UriElement"/> class.
+ /// </summary>
+ /// <param name="uriElement">The URI element.</param>
+ /// <param name="service">The service.</param>
+ public UriElement(XPathNavigator uriElement, ServiceElement service) :
+ base(uriElement, service) {
+ }
+
+ /// <summary>
+ /// Gets the priority.
+ /// </summary>
+ public int? Priority {
+ get {
+ XPathNavigator n = Node.SelectSingleNode("@priority", XmlNamespaceResolver);
+ return n != null ? n.ValueAsInt : (int?)null;
+ }
+ }
+
+ /// <summary>
+ /// Gets the URI.
+ /// </summary>
+ public Uri Uri {
+ get {
+ if (Node.Value != null) {
+ string value = Node.Value.Trim();
+ if (value.Length > 0) {
+ return new Uri(value);
+ }
+ }
+
+ return null;
+ }
+ }
+
+ /// <summary>
+ /// Gets the parent service.
+ /// </summary>
+ public ServiceElement Service {
+ get { return (ServiceElement)ParentNode; }
+ }
+
+ #region IComparable<UriElement> Members
+
+ /// <summary>
+ /// Compares the current object with another object of the same type.
+ /// </summary>
+ /// <param name="other">An object to compare with this object.</param>
+ /// <returns>
+ /// A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has the following meanings:
+ /// Value
+ /// Meaning
+ /// Less than zero
+ /// This object is less than the <paramref name="other"/> parameter.
+ /// Zero
+ /// This object is equal to <paramref name="other"/>.
+ /// Greater than zero
+ /// This object is greater than <paramref name="other"/>.
+ /// </returns>
+ public int CompareTo(UriElement other) {
+ if (other == null) {
+ return -1;
+ }
+ int compare = this.Service.CompareTo(other.Service);
+ if (compare != 0) {
+ return compare;
+ }
+
+ if (this.Priority.HasValue && other.Priority.HasValue) {
+ return this.Priority.Value.CompareTo(other.Priority.Value);
+ } else {
+ if (this.Priority.HasValue) {
+ return -1;
+ } else if (other.Priority.HasValue) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/Xrds/XrdElement.cs b/src/DotNetOpenAuth.OpenId/Xrds/XrdElement.cs
new file mode 100644
index 0000000..4645ad1
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/Xrds/XrdElement.cs
@@ -0,0 +1,160 @@
+//-----------------------------------------------------------------------
+// <copyright file="XrdElement.cs" company="Outercurve Foundation, Scott Hanselman">
+// Copyright (c) Outercurve Foundation, Scott Hanselman. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Xrds {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using System.Xml.XPath;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId;
+
+ /// <summary>
+ /// The Xrd element in an XRDS document.
+ /// </summary>
+ internal class XrdElement : XrdsNode {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="XrdElement"/> class.
+ /// </summary>
+ /// <param name="xrdElement">The XRD element.</param>
+ /// <param name="parent">The parent.</param>
+ public XrdElement(XPathNavigator xrdElement, XrdsDocument parent) :
+ base(xrdElement, parent) {
+ }
+
+ /// <summary>
+ /// Gets the child service elements.
+ /// </summary>
+ /// <value>The services.</value>
+ public IEnumerable<ServiceElement> Services {
+ get {
+ // We should enumerate them in priority order
+ List<ServiceElement> services = new List<ServiceElement>();
+ foreach (XPathNavigator node in Node.Select("xrd:Service", XmlNamespaceResolver)) {
+ services.Add(new ServiceElement(node, this));
+ }
+ services.Sort();
+ return services;
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this XRD element's resolution at the XRI resolver was successful.
+ /// </summary>
+ /// <value>
+ /// <c>true</c> if this XRD's resolution was successful; otherwise, <c>false</c>.
+ /// </value>
+ public bool IsXriResolutionSuccessful {
+ get {
+ return this.XriResolutionStatusCode == 100;
+ }
+ }
+
+ /// <summary>
+ /// Gets the canonical ID (i-number) for this element.
+ /// </summary>
+ public string CanonicalID {
+ get {
+ var n = Node.SelectSingleNode("xrd:CanonicalID", XmlNamespaceResolver);
+ return n != null ? n.Value : null;
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether the <see cref="CanonicalID"/> was verified.
+ /// </summary>
+ public bool IsCanonicalIdVerified {
+ get {
+ var n = Node.SelectSingleNode("xrd:Status", XmlNamespaceResolver);
+ return n != null && string.Equals(n.GetAttribute("cid", string.Empty), "verified", StringComparison.Ordinal);
+ }
+ }
+
+ /// <summary>
+ /// Gets the services for OP Identifiers.
+ /// </summary>
+ public IEnumerable<ServiceElement> OpenIdProviderIdentifierServices {
+ get { return this.SearchForServiceTypeUris(p => p.OPIdentifierServiceTypeURI); }
+ }
+
+ /// <summary>
+ /// Gets the services for Claimed Identifiers.
+ /// </summary>
+ public IEnumerable<ServiceElement> OpenIdClaimedIdentifierServices {
+ get { return this.SearchForServiceTypeUris(p => p.ClaimedIdentifierServiceTypeURI); }
+ }
+
+ /// <summary>
+ /// Gets the services that would be discoverable at an RP for return_to verification.
+ /// </summary>
+ public IEnumerable<ServiceElement> OpenIdRelyingPartyReturnToServices {
+ get { return this.SearchForServiceTypeUris(p => p.RPReturnToTypeURI); }
+ }
+
+ /// <summary>
+ /// Gets the services that would be discoverable at an RP for the UI extension icon.
+ /// </summary>
+ public IEnumerable<ServiceElement> OpenIdRelyingPartyIcons {
+ get { return this.SearchForServiceTypeUris(p => "http://specs.openid.net/extensions/ui/icon"); }
+ }
+
+ /// <summary>
+ /// Gets an enumeration of all Service/URI elements, sorted in priority order.
+ /// </summary>
+ public IEnumerable<UriElement> ServiceUris {
+ get {
+ return from service in this.Services
+ from uri in service.UriElements
+ select uri;
+ }
+ }
+
+ /// <summary>
+ /// Gets the XRI resolution status code.
+ /// </summary>
+ private int XriResolutionStatusCode {
+ get {
+ var n = Node.SelectSingleNode("xrd:Status", XmlNamespaceResolver);
+ string codeString = null;
+ ErrorUtilities.VerifyProtocol(n != null && !string.IsNullOrEmpty(codeString = n.GetAttribute("code", string.Empty)), XrdsStrings.XriResolutionStatusMissing);
+ int code;
+ ErrorUtilities.VerifyProtocol(int.TryParse(codeString, out code) && code >= 100 && code < 400, XrdsStrings.XriResolutionStatusMissing);
+ return code;
+ }
+ }
+
+ /// <summary>
+ /// Searches for service sub-elements that have Type URI sub-elements that match
+ /// one that we have for a known OpenID protocol version.
+ /// </summary>
+ /// <param name="p">A function that selects what element of the OpenID Protocol we're interested in finding.</param>
+ /// <returns>A sequence of service elements that match the search criteria, sorted in XRDS @priority attribute order.</returns>
+ internal IEnumerable<ServiceElement> SearchForServiceTypeUris(Func<Protocol, string> p) {
+ var xpath = new StringBuilder();
+ xpath.Append("xrd:Service[");
+ foreach (var protocol in Protocol.AllVersions) {
+ string typeUri = p(protocol);
+ if (typeUri == null) {
+ continue;
+ }
+ xpath.Append("xrd:Type/text()='");
+ xpath.Append(typeUri);
+ xpath.Append("' or ");
+ }
+ xpath.Length -= 4;
+ xpath.Append("]");
+ var services = new List<ServiceElement>();
+ foreach (XPathNavigator service in Node.Select(xpath.ToString(), XmlNamespaceResolver)) {
+ services.Add(new ServiceElement(service, this));
+ }
+
+ // Put the services in their own defined priority order
+ services.Sort();
+ return services;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/Xrds/XrdsDocument.cs b/src/DotNetOpenAuth.OpenId/Xrds/XrdsDocument.cs
new file mode 100644
index 0000000..0e1ecc1
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/Xrds/XrdsDocument.cs
@@ -0,0 +1,87 @@
+//-----------------------------------------------------------------------
+// <copyright file="XrdsDocument.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Xrds {
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.IO;
+ using System.Linq;
+ using System.Xml;
+ using System.Xml.XPath;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId;
+
+ /// <summary>
+ /// An XRDS document.
+ /// </summary>
+ internal class XrdsDocument : XrdsNode {
+ /// <summary>
+ /// The namespace used by XML digital signatures.
+ /// </summary>
+ private const string XmlDSigNamespace = "http://www.w3.org/2000/09/xmldsig#";
+
+ /// <summary>
+ /// The namespace used by Google Apps for Domains for OpenID URI templates.
+ /// </summary>
+ private const string GoogleOpenIdNamespace = "http://namespace.google.com/openid/xmlns";
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="XrdsDocument"/> class.
+ /// </summary>
+ /// <param name="xrdsNavigator">The root node of the XRDS document.</param>
+ public XrdsDocument(XPathNavigator xrdsNavigator)
+ : base(xrdsNavigator) {
+ XmlNamespaceResolver.AddNamespace("xrd", XrdsNode.XrdNamespace);
+ XmlNamespaceResolver.AddNamespace("xrds", XrdsNode.XrdsNamespace);
+ XmlNamespaceResolver.AddNamespace("openid10", Protocol.V10.XmlNamespace);
+ XmlNamespaceResolver.AddNamespace("ds", XmlDSigNamespace);
+ XmlNamespaceResolver.AddNamespace("google", GoogleOpenIdNamespace);
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="XrdsDocument"/> class.
+ /// </summary>
+ /// <param name="reader">The Xml reader positioned at the root node of the XRDS document.</param>
+ public XrdsDocument(XmlReader reader)
+ : this(new XPathDocument(reader).CreateNavigator()) { }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="XrdsDocument"/> class.
+ /// </summary>
+ /// <param name="xml">The text that is the XRDS document.</param>
+ [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Fixing would decrease readability, and not likely avoid any finalizer on a StringReader anyway.")]
+ public XrdsDocument(string xml)
+ : this(new XPathDocument(new StringReader(xml)).CreateNavigator()) { }
+
+ /// <summary>
+ /// Gets the XRD child elements of the document.
+ /// </summary>
+ public IEnumerable<XrdElement> XrdElements {
+ get {
+ // We may be looking at a full XRDS document (in the case of YADIS discovery)
+ // or we may be looking at just an individual XRD element from a larger document
+ // if we asked xri.net for just one.
+ if (Node.SelectSingleNode("/xrds:XRDS", XmlNamespaceResolver) != null) {
+ foreach (XPathNavigator node in Node.Select("/xrds:XRDS/xrd:XRD", XmlNamespaceResolver)) {
+ yield return new XrdElement(node, this);
+ }
+ } else {
+ XPathNavigator node = Node.SelectSingleNode("/xrd:XRD", XmlNamespaceResolver);
+ if (node != null) {
+ yield return new XrdElement(node, this);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether all child XRD elements were resolved successfully.
+ /// </summary>
+ internal bool IsXrdResolutionSuccessful {
+ get { return this.XrdElements.All(xrd => xrd.IsXriResolutionSuccessful); }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/Xrds/XrdsNode.cs b/src/DotNetOpenAuth.OpenId/Xrds/XrdsNode.cs
new file mode 100644
index 0000000..3e163f7
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/Xrds/XrdsNode.cs
@@ -0,0 +1,69 @@
+//-----------------------------------------------------------------------
+// <copyright file="XrdsNode.cs" company="Outercurve Foundation, Scott Hanselman">
+// Copyright (c) Outercurve Foundation, Scott Hanselman. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Xrds {
+ using System;
+ using System.Diagnostics.Contracts;
+ using System.Xml;
+ using System.Xml.XPath;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// A node in an XRDS document.
+ /// </summary>
+ internal class XrdsNode {
+ /// <summary>
+ /// The XRD namespace xri://$xrd*($v*2.0)
+ /// </summary>
+ internal const string XrdNamespace = "xri://$xrd*($v*2.0)";
+
+ /// <summary>
+ /// The XRDS namespace xri://$xrds
+ /// </summary>
+ internal const string XrdsNamespace = "xri://$xrds";
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="XrdsNode"/> class.
+ /// </summary>
+ /// <param name="node">The node represented by this instance.</param>
+ /// <param name="parentNode">The parent node.</param>
+ protected XrdsNode(XPathNavigator node, XrdsNode parentNode) {
+ Requires.NotNull(node, "node");
+ Requires.NotNull(parentNode, "parentNode");
+
+ this.Node = node;
+ this.ParentNode = parentNode;
+ this.XmlNamespaceResolver = this.ParentNode.XmlNamespaceResolver;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="XrdsNode"/> class.
+ /// </summary>
+ /// <param name="document">The document's root node, which this instance represents.</param>
+ protected XrdsNode(XPathNavigator document) {
+ Requires.NotNull(document, "document");
+ Requires.True(document.NameTable != null, null);
+
+ this.Node = document;
+ this.XmlNamespaceResolver = new XmlNamespaceManager(document.NameTable);
+ }
+
+ /// <summary>
+ /// Gets the node.
+ /// </summary>
+ internal XPathNavigator Node { get; private set; }
+
+ /// <summary>
+ /// Gets the parent node, or null if this is the root node.
+ /// </summary>
+ protected internal XrdsNode ParentNode { get; private set; }
+
+ /// <summary>
+ /// Gets the XML namespace resolver to use in XPath expressions.
+ /// </summary>
+ protected internal XmlNamespaceManager XmlNamespaceResolver { get; private set; }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/Xrds/XrdsStrings.Designer.cs b/src/DotNetOpenAuth.OpenId/Xrds/XrdsStrings.Designer.cs
new file mode 100644
index 0000000..2279b5f
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/Xrds/XrdsStrings.Designer.cs
@@ -0,0 +1,99 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:4.0.30104.0
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Xrds {
+ using System;
+
+
+ /// <summary>
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ /// </summary>
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class XrdsStrings {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal XrdsStrings() {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DotNetOpenAuth.Xrds.XrdsStrings", typeof(XrdsStrings).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to XRI CanonicalID verification failed..
+ /// </summary>
+ internal static string CIDVerificationFailed {
+ get {
+ return ResourceManager.GetString("CIDVerificationFailed", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Failure parsing XRDS document..
+ /// </summary>
+ internal static string InvalidXRDSDocument {
+ get {
+ return ResourceManager.GetString("InvalidXRDSDocument", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The XRDS document for XRI {0} is missing the required CanonicalID element..
+ /// </summary>
+ internal static string MissingCanonicalIDElement {
+ get {
+ return ResourceManager.GetString("MissingCanonicalIDElement", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Could not find XRI resolution Status tag or code attribute was invalid..
+ /// </summary>
+ internal static string XriResolutionStatusMissing {
+ get {
+ return ResourceManager.GetString("XriResolutionStatusMissing", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/Xrds/XrdsStrings.resx b/src/DotNetOpenAuth.OpenId/Xrds/XrdsStrings.resx
new file mode 100644
index 0000000..acb43f2
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/Xrds/XrdsStrings.resx
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="CIDVerificationFailed" xml:space="preserve">
+ <value>XRI CanonicalID verification failed.</value>
+ </data>
+ <data name="InvalidXRDSDocument" xml:space="preserve">
+ <value>Failure parsing XRDS document.</value>
+ </data>
+ <data name="MissingCanonicalIDElement" xml:space="preserve">
+ <value>The XRDS document for XRI {0} is missing the required CanonicalID element.</value>
+ </data>
+ <data name="XriResolutionStatusMissing" xml:space="preserve">
+ <value>Could not find XRI resolution Status tag or code attribute was invalid.</value>
+ </data>
+</root> \ No newline at end of file
diff --git a/src/DotNetOpenAuth.OpenId/Xrds/XrdsStrings.sr.resx b/src/DotNetOpenAuth.OpenId/Xrds/XrdsStrings.sr.resx
new file mode 100644
index 0000000..8e2d09a
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/Xrds/XrdsStrings.sr.resx
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="CIDVerificationFailed" xml:space="preserve">
+ <value>XRI CanonicalID provera neuspešna.</value>
+ </data>
+ <data name="InvalidXRDSDocument" xml:space="preserve">
+ <value>Greška u obradi XRDS dokumenta.</value>
+ </data>
+ <data name="MissingCanonicalIDElement" xml:space="preserve">
+ <value>XRDS dokumentu za XRI {0} nedostaje neophodni CanonicalID element.</value>
+ </data>
+ <data name="XriResolutionStatusMissing" xml:space="preserve">
+ <value>Ne može se pronaći XRI resolution Status tag ili je code attribute neispravan.</value>
+ </data>
+</root> \ No newline at end of file
diff --git a/src/DotNetOpenAuth.OpenId/Yadis/ContentTypes.cs b/src/DotNetOpenAuth.OpenId/Yadis/ContentTypes.cs
new file mode 100644
index 0000000..26bc4a4
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/Yadis/ContentTypes.cs
@@ -0,0 +1,32 @@
+//-----------------------------------------------------------------------
+// <copyright file="ContentTypes.cs" company="Outercurve Foundation">
+// Copyright (c) Outercurve Foundation. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Yadis {
+ /// <summary>
+ /// String constants for various content-type header values used in YADIS discovery.
+ /// </summary>
+ internal static class ContentTypes {
+ /// <summary>
+ /// The text/html content-type
+ /// </summary>
+ public const string Html = "text/html";
+
+ /// <summary>
+ /// The application/xhtml+xml content-type
+ /// </summary>
+ public const string XHtml = "application/xhtml+xml";
+
+ /// <summary>
+ /// The application/xrds+xml content-type
+ /// </summary>
+ public const string Xrds = "application/xrds+xml";
+
+ /// <summary>
+ /// The text/xml content type
+ /// </summary>
+ public const string Xml = "text/xml";
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/Yadis/DiscoveryResult.cs b/src/DotNetOpenAuth.OpenId/Yadis/DiscoveryResult.cs
new file mode 100644
index 0000000..06c6fc7
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/Yadis/DiscoveryResult.cs
@@ -0,0 +1,106 @@
+//-----------------------------------------------------------------------
+// <copyright file="DiscoveryResult.cs" company="Scott Hanselman, Andrew Arnott">
+// Copyright (c) Scott Hanselman, Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Yadis {
+ using System;
+ using System.IO;
+ using System.Net.Mime;
+ using System.Web.UI.HtmlControls;
+ using System.Xml;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// Contains the result of YADIS discovery.
+ /// </summary>
+ internal class DiscoveryResult {
+ /// <summary>
+ /// The original web response, backed up here if the final web response is the preferred response to use
+ /// in case it turns out to not work out.
+ /// </summary>
+ private CachedDirectWebResponse htmlFallback;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="DiscoveryResult"/> class.
+ /// </summary>
+ /// <param name="requestUri">The user-supplied identifier.</param>
+ /// <param name="initialResponse">The initial response.</param>
+ /// <param name="finalResponse">The final response.</param>
+ public DiscoveryResult(Uri requestUri, CachedDirectWebResponse initialResponse, CachedDirectWebResponse finalResponse) {
+ this.RequestUri = requestUri;
+ this.NormalizedUri = initialResponse.FinalUri;
+ if (finalResponse == null || finalResponse.Status != System.Net.HttpStatusCode.OK) {
+ this.ApplyHtmlResponse(initialResponse);
+ } else {
+ this.ContentType = finalResponse.ContentType;
+ this.ResponseText = finalResponse.GetResponseString();
+ this.IsXrds = true;
+ if (initialResponse != finalResponse) {
+ this.YadisLocation = finalResponse.RequestUri;
+ }
+
+ // Back up the initial HTML response in case the XRDS is not useful.
+ this.htmlFallback = initialResponse;
+ }
+ }
+
+ /// <summary>
+ /// Gets the URI of the original YADIS discovery request.
+ /// This is the user supplied Identifier as given in the original
+ /// YADIS discovery request.
+ /// </summary>
+ public Uri RequestUri { get; private set; }
+
+ /// <summary>
+ /// Gets the fully resolved (after redirects) URL of the user supplied Identifier.
+ /// This becomes the ClaimedIdentifier.
+ /// </summary>
+ public Uri NormalizedUri { get; private set; }
+
+ /// <summary>
+ /// Gets the location the XRDS document was downloaded from, if different
+ /// from the user supplied Identifier.
+ /// </summary>
+ public Uri YadisLocation { get; private set; }
+
+ /// <summary>
+ /// Gets the Content-Type associated with the <see cref="ResponseText"/>.
+ /// </summary>
+ public ContentType ContentType { get; private set; }
+
+ /// <summary>
+ /// Gets the text in the final response.
+ /// This may be an XRDS document or it may be an HTML document,
+ /// as determined by the <see cref="IsXrds"/> property.
+ /// </summary>
+ public string ResponseText { get; private set; }
+
+ /// <summary>
+ /// Gets a value indicating whether the <see cref="ResponseText"/>
+ /// represents an XRDS document. False if the response is an HTML document.
+ /// </summary>
+ public bool IsXrds { get; private set; }
+
+ /// <summary>
+ /// Reverts to the HTML response after the XRDS response didn't work out.
+ /// </summary>
+ internal void TryRevertToHtmlResponse() {
+ if (this.htmlFallback != null) {
+ this.ApplyHtmlResponse(this.htmlFallback);
+ this.htmlFallback = null;
+ }
+ }
+
+ /// <summary>
+ /// Applies the HTML response to the object.
+ /// </summary>
+ /// <param name="initialResponse">The initial response.</param>
+ private void ApplyHtmlResponse(CachedDirectWebResponse initialResponse) {
+ this.ContentType = initialResponse.ContentType;
+ this.ResponseText = initialResponse.GetResponseString();
+ this.IsXrds = this.ContentType != null && this.ContentType.MediaType == ContentTypes.Xrds;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/Yadis/HtmlParser.cs b/src/DotNetOpenAuth.OpenId/Yadis/HtmlParser.cs
new file mode 100644
index 0000000..481d6a7
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/Yadis/HtmlParser.cs
@@ -0,0 +1,142 @@
+//-----------------------------------------------------------------------
+// <copyright file="HtmlParser.cs" company="Outercurve Foundation, Scott Hanselman, Jason Alexander">
+// Copyright (c) Outercurve Foundation, Scott Hanselman, Jason Alexander. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Yadis {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.Linq;
+ using System.Text;
+ using System.Text.RegularExpressions;
+ using System.Web;
+ using System.Web.UI.HtmlControls;
+
+ /// <summary>
+ /// An HTML HEAD tag parser.
+ /// </summary>
+ internal static class HtmlParser {
+ /// <summary>
+ /// Common flags to use on regex tests.
+ /// </summary>
+ private const RegexOptions Flags = RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase;
+
+ /// <summary>
+ /// A regular expression designed to select tags (?)
+ /// </summary>
+ private const string TagExpr = "\n# Starts with the tag name at a word boundary, where the tag name is\n# not a namespace\n<{0}\\b(?!:)\n \n# All of the stuff up to a \">\", hopefully attributes.\n(?<attrs>[^>]*?)\n \n(?: # Match a short tag\n />\n \n| # Match a full tag\n >\n \n (?<contents>.*?)\n \n # Closed by\n (?: # One of the specified close tags\n </?{1}\\s*>\n \n # End of the string\n | \\Z\n \n )\n \n)\n ";
+
+ /// <summary>
+ /// A regular expression designed to select start tags (?)
+ /// </summary>
+ private const string StartTagExpr = "\n# Starts with the tag name at a word boundary, where the tag name is\n# not a namespace\n<{0}\\b(?!:)\n \n# All of the stuff up to a \">\", hopefully attributes.\n(?<attrs>[^>]*?)\n \n(?: # Match a short tag\n />\n \n| # Match a full tag\n >\n )\n ";
+
+ /// <summary>
+ /// A regular expression designed to select attributes within a tag.
+ /// </summary>
+ private static readonly Regex attrRe = new Regex("\n# Must start with a sequence of word-characters, followed by an equals sign\n(?<attrname>(\\w|-)+)=\n\n# Then either a quoted or unquoted attribute\n(?:\n\n # Match everything that's between matching quote marks\n (?<qopen>[\"\\'])(?<attrval>.*?)\\k<qopen>\n|\n\n # If the value is not quoted, match up to whitespace\n (?<attrval>(?:[^\\s<>/]|/(?!>))+)\n)\n\n|\n\n(?<endtag>[<>])\n ", Flags);
+
+ /// <summary>
+ /// A regular expression designed to select the HEAD tag.
+ /// </summary>
+ private static readonly Regex headRe = TagMatcher("head", new[] { "body" });
+
+ /// <summary>
+ /// A regular expression designed to select the HTML tag.
+ /// </summary>
+ private static readonly Regex htmlRe = TagMatcher("html", new string[0]);
+
+ /// <summary>
+ /// A regular expression designed to remove all comments and scripts from a string.
+ /// </summary>
+ private static readonly Regex removedRe = new Regex(@"<!--.*?-->|<!\[CDATA\[.*?\]\]>|<script\b[^>]*>.*?</script>", Flags);
+
+ /// <summary>
+ /// Finds all the HTML HEAD tag child elements that match the tag name of a given type.
+ /// </summary>
+ /// <typeparam name="T">The HTML tag of interest.</typeparam>
+ /// <param name="html">The HTML to scan.</param>
+ /// <returns>A sequence of the matching elements.</returns>
+ public static IEnumerable<T> HeadTags<T>(string html) where T : HtmlControl, new() {
+ html = removedRe.Replace(html, string.Empty);
+ Match match = htmlRe.Match(html);
+ string tagName = (new T()).TagName;
+ if (match.Success) {
+ Match match2 = headRe.Match(html, match.Index, match.Length);
+ if (match2.Success) {
+ string text = null;
+ string text2 = null;
+ Regex regex = StartTagMatcher(tagName);
+ for (Match match3 = regex.Match(html, match2.Index, match2.Length); match3.Success; match3 = match3.NextMatch()) {
+ int beginning = (match3.Index + tagName.Length) + 1;
+ int length = (match3.Index + match3.Length) - beginning;
+ Match match4 = attrRe.Match(html, beginning, length);
+ var headTag = new T();
+ while (match4.Success) {
+ if (match4.Groups["endtag"].Success) {
+ break;
+ }
+ text = match4.Groups["attrname"].Value;
+ text2 = HttpUtility.HtmlDecode(match4.Groups["attrval"].Value);
+ headTag.Attributes.Add(text, text2);
+ match4 = match4.NextMatch();
+ }
+ yield return headTag;
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Filters a list of controls based on presence of an attribute.
+ /// </summary>
+ /// <typeparam name="T">The type of HTML controls being filtered.</typeparam>
+ /// <param name="sequence">The sequence.</param>
+ /// <param name="attribute">The attribute.</param>
+ /// <returns>A filtered sequence of attributes.</returns>
+ internal static IEnumerable<T> WithAttribute<T>(this IEnumerable<T> sequence, string attribute) where T : HtmlControl {
+ Requires.NotNull(sequence, "sequence");
+ Requires.NotNullOrEmpty(attribute, "attribute");
+ return sequence.Where(tag => tag.Attributes[attribute] != null);
+ }
+
+ /// <summary>
+ /// Generates a regular expression that will find a given HTML tag.
+ /// </summary>
+ /// <param name="tagName">Name of the tag.</param>
+ /// <param name="closeTags">The close tags (?).</param>
+ /// <returns>The created regular expression.</returns>
+ private static Regex TagMatcher(string tagName, params string[] closeTags) {
+ string text2;
+ if (closeTags.Length > 0) {
+ StringBuilder builder = new StringBuilder();
+ builder.AppendFormat("(?:{0}", tagName);
+ int index = 0;
+ string[] textArray = closeTags;
+ int length = textArray.Length;
+ while (index < length) {
+ string text = textArray[index];
+ index++;
+ builder.AppendFormat("|{0}", text);
+ }
+ builder.Append(")");
+ text2 = builder.ToString();
+ } else {
+ text2 = tagName;
+ }
+ return new Regex(string.Format(CultureInfo.InvariantCulture, TagExpr, tagName, text2), Flags);
+ }
+
+ /// <summary>
+ /// Generates a regular expression designed to find a given tag.
+ /// </summary>
+ /// <param name="tagName">The tag to find.</param>
+ /// <returns>The created regular expression.</returns>
+ private static Regex StartTagMatcher(string tagName) {
+ return new Regex(string.Format(CultureInfo.InvariantCulture, StartTagExpr, tagName), Flags);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId/Yadis/Yadis.cs b/src/DotNetOpenAuth.OpenId/Yadis/Yadis.cs
new file mode 100644
index 0000000..f71ad46
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/Yadis/Yadis.cs
@@ -0,0 +1,205 @@
+//-----------------------------------------------------------------------
+// <copyright file="Yadis.cs" company="Outercurve Foundation, Scott Hanselman">
+// Copyright (c) Outercurve Foundation, Scott Hanselman. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Yadis {
+ using System;
+ using System.Diagnostics.Contracts;
+ using System.IO;
+ using System.Net;
+ using System.Net.Cache;
+ using System.Web.UI.HtmlControls;
+ using System.Xml;
+ using DotNetOpenAuth.Configuration;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId;
+ using DotNetOpenAuth.Xrds;
+
+ /// <summary>
+ /// YADIS discovery manager.
+ /// </summary>
+ internal class Yadis {
+ /// <summary>
+ /// The HTTP header to look for in responses to declare where the XRDS document should be found.
+ /// </summary>
+ internal const string HeaderName = "X-XRDS-Location";
+
+ /// <summary>
+ /// Gets or sets the cache that can be used for HTTP requests made during identifier discovery.
+ /// </summary>
+#if DEBUG
+ internal static readonly RequestCachePolicy IdentifierDiscoveryCachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.BypassCache);
+#else
+ internal static readonly RequestCachePolicy IdentifierDiscoveryCachePolicy = new HttpRequestCachePolicy(OpenIdElement.Configuration.CacheDiscovery ? HttpRequestCacheLevel.CacheIfAvailable : HttpRequestCacheLevel.BypassCache);
+#endif
+
+ /// <summary>
+ /// The maximum number of bytes to read from an HTTP response
+ /// in searching for a link to a YADIS document.
+ /// </summary>
+ internal const int MaximumResultToScan = 1024 * 1024;
+
+ /// <summary>
+ /// Performs YADIS discovery on some identifier.
+ /// </summary>
+ /// <param name="requestHandler">The mechanism to use for sending HTTP requests.</param>
+ /// <param name="uri">The URI to perform discovery on.</param>
+ /// <param name="requireSsl">Whether discovery should fail if any step of it is not encrypted.</param>
+ /// <returns>
+ /// The result of discovery on the given URL.
+ /// Null may be returned if an error occurs,
+ /// or if <paramref name="requireSsl"/> is true but part of discovery
+ /// is not protected by SSL.
+ /// </returns>
+ public static DiscoveryResult Discover(IDirectWebRequestHandler requestHandler, UriIdentifier uri, bool requireSsl) {
+ CachedDirectWebResponse response;
+ try {
+ if (requireSsl && !string.Equals(uri.Uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) {
+ Logger.Yadis.WarnFormat("Discovery on insecure identifier '{0}' aborted.", uri);
+ return null;
+ }
+ response = Request(requestHandler, uri, requireSsl, ContentTypes.Html, ContentTypes.XHtml, ContentTypes.Xrds).GetSnapshot(MaximumResultToScan);
+ if (response.Status != System.Net.HttpStatusCode.OK) {
+ Logger.Yadis.ErrorFormat("HTTP error {0} {1} while performing discovery on {2}.", (int)response.Status, response.Status, uri);
+ return null;
+ }
+ } catch (ArgumentException ex) {
+ // Unsafe URLs generate this
+ Logger.Yadis.WarnFormat("Unsafe OpenId URL detected ({0}). Request aborted. {1}", uri, ex);
+ return null;
+ }
+ CachedDirectWebResponse response2 = null;
+ if (IsXrdsDocument(response)) {
+ Logger.Yadis.Debug("An XRDS response was received from GET at user-supplied identifier.");
+ Reporting.RecordEventOccurrence("Yadis", "XRDS in initial response");
+ response2 = response;
+ } else {
+ string uriString = response.Headers.Get(HeaderName);
+ Uri url = null;
+ if (uriString != null) {
+ if (Uri.TryCreate(uriString, UriKind.Absolute, out url)) {
+ Logger.Yadis.DebugFormat("{0} found in HTTP header. Preparing to pull XRDS from {1}", HeaderName, url);
+ Reporting.RecordEventOccurrence("Yadis", "XRDS referenced in HTTP header");
+ }
+ }
+ if (url == null && response.ContentType != null && (response.ContentType.MediaType == ContentTypes.Html || response.ContentType.MediaType == ContentTypes.XHtml)) {
+ url = FindYadisDocumentLocationInHtmlMetaTags(response.GetResponseString());
+ if (url != null) {
+ Logger.Yadis.DebugFormat("{0} found in HTML Http-Equiv tag. Preparing to pull XRDS from {1}", HeaderName, url);
+ Reporting.RecordEventOccurrence("Yadis", "XRDS referenced in HTML");
+ }
+ }
+ if (url != null) {
+ if (!requireSsl || string.Equals(url.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) {
+ response2 = Request(requestHandler, url, requireSsl, ContentTypes.Xrds).GetSnapshot(MaximumResultToScan);
+ if (response2.Status != HttpStatusCode.OK) {
+ Logger.Yadis.ErrorFormat("HTTP error {0} {1} while performing discovery on {2}.", (int)response2.Status, response2.Status, uri);
+ }
+ } else {
+ Logger.Yadis.WarnFormat("XRDS document at insecure location '{0}'. Aborting YADIS discovery.", url);
+ }
+ }
+ }
+ return new DiscoveryResult(uri, response, response2);
+ }
+
+ /// <summary>
+ /// Searches an HTML document for a
+ /// &lt;meta http-equiv="X-XRDS-Location" content="{YadisURL}"&gt;
+ /// tag and returns the content of YadisURL.
+ /// </summary>
+ /// <param name="html">The HTML to search.</param>
+ /// <returns>The URI of the XRDS document if found; otherwise <c>null</c>.</returns>
+ public static Uri FindYadisDocumentLocationInHtmlMetaTags(string html) {
+ foreach (var metaTag in HtmlParser.HeadTags<HtmlMeta>(html)) {
+ if (HeaderName.Equals(metaTag.HttpEquiv, StringComparison.OrdinalIgnoreCase)) {
+ if (metaTag.Content != null) {
+ Uri uri;
+ if (Uri.TryCreate(metaTag.Content, UriKind.Absolute, out uri)) {
+ return uri;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /// <summary>
+ /// Sends a YADIS HTTP request as part of identifier discovery.
+ /// </summary>
+ /// <param name="requestHandler">The request handler to use to actually submit the request.</param>
+ /// <param name="uri">The URI to GET.</param>
+ /// <param name="requireSsl">Whether only HTTPS URLs should ever be retrieved.</param>
+ /// <param name="acceptTypes">The value of the Accept HTTP header to include in the request.</param>
+ /// <returns>The HTTP response retrieved from the request.</returns>
+ internal static IncomingWebResponse Request(IDirectWebRequestHandler requestHandler, Uri uri, bool requireSsl, params string[] acceptTypes) {
+ Requires.NotNull(requestHandler, "requestHandler");
+ Requires.NotNull(uri, "uri");
+ Contract.Ensures(Contract.Result<IncomingWebResponse>() != null);
+ Contract.Ensures(Contract.Result<IncomingWebResponse>().ResponseStream != null);
+
+ HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
+ request.CachePolicy = IdentifierDiscoveryCachePolicy;
+ if (acceptTypes != null) {
+ request.Accept = string.Join(",", acceptTypes);
+ }
+
+ DirectWebRequestOptions options = DirectWebRequestOptions.None;
+ if (requireSsl) {
+ options |= DirectWebRequestOptions.RequireSsl;
+ }
+
+ try {
+ return requestHandler.GetResponse(request, options);
+ } catch (ProtocolException ex) {
+ var webException = ex.InnerException as WebException;
+ if (webException != null) {
+ var response = webException.Response as HttpWebResponse;
+ if (response != null && response.IsFromCache) {
+ // We don't want to report error responses from the cache, since the server may have fixed
+ // whatever was causing the problem. So try again with cache disabled.
+ Logger.Messaging.Error("An HTTP error response was obtained from the cache. Retrying with cache disabled.", ex);
+ var nonCachingRequest = request.Clone();
+ nonCachingRequest.CachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.Reload);
+ return requestHandler.GetResponse(nonCachingRequest, options);
+ }
+ }
+
+ throw;
+ }
+ }
+
+ /// <summary>
+ /// Determines whether a given HTTP response constitutes an XRDS document.
+ /// </summary>
+ /// <param name="response">The response to test.</param>
+ /// <returns>
+ /// <c>true</c> if the response constains an XRDS document; otherwise, <c>false</c>.
+ /// </returns>
+ private static bool IsXrdsDocument(CachedDirectWebResponse response) {
+ if (response.ContentType == null) {
+ return false;
+ }
+
+ if (response.ContentType.MediaType == ContentTypes.Xrds) {
+ return true;
+ }
+
+ if (response.ContentType.MediaType == ContentTypes.Xml) {
+ // This COULD be an XRDS document with an imprecise content-type.
+ response.ResponseStream.Seek(0, SeekOrigin.Begin);
+ XmlReader reader = XmlReader.Create(response.ResponseStream);
+ while (reader.Read() && reader.NodeType != XmlNodeType.Element) {
+ // intentionally blank
+ }
+ if (reader.NamespaceURI == XrdsNode.XrdsNamespace && reader.Name == "XRDS") {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+}