diff options
Diffstat (limited to 'src/DotNetOpenAuth')
33 files changed, 803 insertions, 745 deletions
diff --git a/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd b/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd index a637d1f..7d10b21 100644 --- a/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd +++ b/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd @@ -158,6 +158,27 @@ </xs:choice> </xs:complexType> </xs:element> + <xs:element name="discoveryServices"> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="add"> + <xs:complexType> + <xs:attribute name="name" type="xs:string" use="required" /> + </xs:complexType> + </xs:element> + <xs:element name="remove"> + <xs:complexType> + <xs:attribute name="name" type="xs:string" use="required" /> + </xs:complexType> + </xs:element> + <xs:element name="clear"> + <xs:complexType> + <!--tag is empty--> + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> <xs:element name="store"> <xs:complexType> <xs:attribute name="type" type="xs:string"/> diff --git a/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartyElement.cs b/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartyElement.cs index cdf4fd3..2ee2e91 100644 --- a/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartyElement.cs +++ b/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartyElement.cs @@ -5,8 +5,10 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.Configuration { + using System; using System.Configuration; using System.Diagnostics.Contracts; + using DotNetOpenAuth.OpenId; using DotNetOpenAuth.OpenId.RelyingParty; /// <summary> @@ -25,11 +27,21 @@ namespace DotNetOpenAuth.Configuration { private const string SecuritySettingsConfigName = "security"; /// <summary> - /// Gets the name of the <behaviors> sub-element. + /// The name of the <behaviors> sub-element. /// </summary> private const string BehaviorsElementName = "behaviors"; /// <summary> + /// The name of the <discoveryServices> sub-element. + /// </summary> + private const string DiscoveryServicesElementName = "discoveryServices"; + + /// <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() { @@ -62,5 +74,25 @@ namespace DotNetOpenAuth.Configuration { get { return (TypeConfigurationElement<IRelyingPartyApplicationStore>)this[StoreConfigName] ?? new TypeConfigurationElement<IRelyingPartyApplicationStore>(); } set { this[StoreConfigName] = 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/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj index f339d97..5246b96 100644 --- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj +++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj @@ -437,6 +437,7 @@ http://opensource.org/licenses/ms-pl.html <Compile Include="OpenId\Interop\AuthenticationResponseShim.cs" /> <Compile Include="OpenId\Interop\ClaimsResponseShim.cs" /> <Compile Include="OpenId\Interop\OpenIdRelyingPartyShim.cs" /> + <Compile Include="OpenId\IIdentifierDiscoveryService.cs" /> <Compile Include="OpenId\Messages\CheckAuthenticationRequest.cs" /> <Compile Include="OpenId\Messages\CheckAuthenticationResponse.cs" /> <Compile Include="OpenId\Messages\CheckIdRequest.cs" /> @@ -503,6 +504,7 @@ http://opensource.org/licenses/ms-pl.html <Compile Include="OpenId\RelyingParty\AssociationPreference.cs" /> <Compile Include="OpenId\RelyingParty\AuthenticationRequest.cs" /> <Compile Include="OpenId\RelyingParty\AuthenticationRequestMode.cs" /> + <Compile Include="OpenId\RelyingParty\IProviderEndpoint.cs" /> <Compile Include="OpenId\RelyingParty\SelectorButtonContract.cs" /> <Compile Include="OpenId\RelyingParty\SelectorProviderButton.cs" /> <Compile Include="OpenId\RelyingParty\SelectorOpenIdButton.cs" /> @@ -510,7 +512,6 @@ http://opensource.org/licenses/ms-pl.html <Compile Include="OpenId\RelyingParty\IRelyingPartyBehavior.cs" /> <Compile Include="OpenId\RelyingParty\OpenIdSelector.cs" /> <Compile Include="OpenId\RelyingParty\OpenIdRelyingPartyAjaxControlBase.cs" /> - <Compile Include="OpenId\RelyingParty\IXrdsProviderEndpointContract.cs" /> <Compile Include="OpenId\RelyingParty\IAuthenticationRequestContract.cs" /> <Compile Include="OpenId\RelyingParty\NegativeAuthenticationResponse.cs" /> <Compile Include="OpenId\RelyingParty\OpenIdAjaxTextBox.cs" /> @@ -527,9 +528,7 @@ http://opensource.org/licenses/ms-pl.html <Compile Include="OpenId\RelyingParty\FailedAuthenticationResponse.cs" /> <Compile Include="OpenId\RelyingParty\IAuthenticationRequest.cs" /> <Compile Include="OpenId\RelyingParty\IAuthenticationResponse.cs" /> - <Compile Include="OpenId\RelyingParty\IProviderEndpoint.cs" /> <Compile Include="OpenId\RelyingParty\ISetupRequiredAuthenticationResponse.cs" /> - <Compile Include="OpenId\RelyingParty\IXrdsProviderEndpoint.cs" /> <Compile Include="OpenId\RelyingParty\OpenIdRelyingParty.cs" /> <Compile Include="OpenId\OpenIdStrings.Designer.cs"> <DependentUpon>OpenIdStrings.resx</DependentUpon> @@ -544,7 +543,7 @@ http://opensource.org/licenses/ms-pl.html <Compile Include="OpenId\RelyingParty\PrivateSecretManager.cs" /> <Compile Include="OpenId\RelyingParty\RelyingPartySecuritySettings.cs" /> <Compile Include="OpenId\RelyingParty\SelectorButton.cs" /> - <Compile Include="OpenId\RelyingParty\ServiceEndpoint.cs" /> + <Compile Include="OpenId\IdentifierDiscoveryResult.cs" /> <Compile Include="OpenId\OpenIdXrdsHelper.cs" /> <Compile Include="OpenId\RelyingParty\SimpleXrdsProviderEndpoint.cs" /> <Compile Include="OpenId\RelyingParty\StandardRelyingPartyApplicationStore.cs" /> @@ -552,7 +551,9 @@ http://opensource.org/licenses/ms-pl.html <Compile Include="OpenId\RelyingParty\WellKnownProviders.cs" /> <Compile Include="OpenId\SecuritySettings.cs" /> <Compile Include="Messaging\UntrustedWebRequestHandler.cs" /> + <Compile Include="OpenId\UriDiscoveryService.cs" /> <Compile Include="OpenId\UriIdentifier.cs" /> + <Compile Include="OpenId\XriDiscoveryProxyService.cs" /> <Compile Include="OpenId\XriIdentifier.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="OAuth\Messages\UnauthorizedTokenRequest.cs" /> diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ExtensionsInteropHelper.cs b/src/DotNetOpenAuth/OpenId/Extensions/ExtensionsInteropHelper.cs index cd7575e..fd9bb7b 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/ExtensionsInteropHelper.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/ExtensionsInteropHelper.cs @@ -51,7 +51,7 @@ namespace DotNetOpenAuth.OpenId.Extensions { return; } - if (req.Provider.IsExtensionSupported<ClaimsRequest>()) { + if (req.DiscoveryResult.IsExtensionSupported<ClaimsRequest>()) { Logger.OpenId.Info("Skipping generation of AX request because the Identifier advertises the Provider supports the Sreg extension."); return; } @@ -276,8 +276,7 @@ namespace DotNetOpenAuth.OpenId.Extensions { /// <returns>The AX format(s) to use based on the Provider's advertised AX support.</returns> private static bool TryDetectOPAttributeFormat(RelyingParty.IAuthenticationRequest request, out AXAttributeFormats attributeFormat) { Contract.Requires<ArgumentNullException>(request != null); - var provider = (RelyingParty.ServiceEndpoint)request.Provider; - attributeFormat = DetectAXFormat(provider.ProviderDescription.Capabilities); + attributeFormat = DetectAXFormat(request.DiscoveryResult.Capabilities); return attributeFormat != AXAttributeFormats.None; } diff --git a/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs b/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs index 61e6f85..67efe8e 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs @@ -28,8 +28,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.UI { /// <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="IProviderEndpoint.IsExtensionSupported"/> method on the <see cref="DotNetOpenAuth.OpenId.RelyingParty.IAuthenticationRequest.Provider"/> - /// object.</para> + /// <see cref="IdentifierDiscoveryResult.IsExtensionSupported<T>()"/> method.</para> /// </remarks> public sealed class UIRequest : IOpenIdMessageExtension, IMessageWithEvents { /// <summary> diff --git a/src/DotNetOpenAuth/OpenId/IIdentifierDiscoveryService.cs b/src/DotNetOpenAuth/OpenId/IIdentifierDiscoveryService.cs new file mode 100644 index 0000000..f637b37 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/IIdentifierDiscoveryService.cs @@ -0,0 +1,59 @@ +//----------------------------------------------------------------------- +// <copyright file="IIdentifierDiscoveryService.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. 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.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> + [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 class IIdentifierDiscoveryServiceContract : IIdentifierDiscoveryService { + #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) { + Contract.Requires<ArgumentNullException>(identifier != null); + Contract.Requires<ArgumentNullException>(requestHandler != null); + Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null); + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OpenId/Identifier.cs b/src/DotNetOpenAuth/OpenId/Identifier.cs index e32251b..548a673 100644 --- a/src/DotNetOpenAuth/OpenId/Identifier.cs +++ b/src/DotNetOpenAuth/OpenId/Identifier.cs @@ -207,16 +207,6 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> - /// Performs discovery on the Identifier. - /// </summary> - /// <param name="requestHandler">The web request handler to use for discovery.</param> - /// <returns> - /// An initialized structure containing the discovered provider endpoint information. - /// </returns> - [Pure] - internal abstract IEnumerable<ServiceEndpoint> Discover(IDirectWebRequestHandler requestHandler); - - /// <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. diff --git a/src/DotNetOpenAuth/OpenId/IdentifierContract.cs b/src/DotNetOpenAuth/OpenId/IdentifierContract.cs index 498ae45..4af18e1 100644 --- a/src/DotNetOpenAuth/OpenId/IdentifierContract.cs +++ b/src/DotNetOpenAuth/OpenId/IdentifierContract.cs @@ -24,19 +24,6 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> - /// Performs discovery on the Identifier. - /// </summary> - /// <param name="requestHandler">The web request handler to use for discovery.</param> - /// <returns> - /// An initialized structure containing the discovered provider endpoint information. - /// </returns> - internal override IEnumerable<ServiceEndpoint> Discover(IDirectWebRequestHandler requestHandler) { - Contract.Requires<ArgumentNullException>(requestHandler != null); - Contract.Ensures(Contract.Result<IEnumerable<ServiceEndpoint>>() != null); - throw new NotImplementedException(); - } - - /// <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. diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/ServiceEndpoint.cs b/src/DotNetOpenAuth/OpenId/IdentifierDiscoveryResult.cs index f8744d0..93a5f33 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/ServiceEndpoint.cs +++ b/src/DotNetOpenAuth/OpenId/IdentifierDiscoveryResult.cs @@ -1,13 +1,15 @@ //----------------------------------------------------------------------- -// <copyright file="ServiceEndpoint.cs" company="Andrew Arnott"> +// <copyright file="IdentifierDiscoveryResult.cs" company="Andrew Arnott"> // Copyright (c) Andrew Arnott. All rights reserved. // </copyright> //----------------------------------------------------------------------- -namespace DotNetOpenAuth.OpenId.RelyingParty { +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; @@ -15,17 +17,17 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { 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}")] - internal class ServiceEndpoint : IXrdsProviderEndpoint { + public sealed class IdentifierDiscoveryResult : IProviderEndpoint { /// <summary> - /// The i-name identifier the user actually typed in - /// or the url identifier with the scheme stripped off. + /// Backing field for the <see cref="Protocol"/> property. /// </summary> - private string friendlyIdentifierForDisplay; + private Protocol protocol; /// <summary> /// Backing field for the <see cref="ClaimedIdentifier"/> property. @@ -33,23 +35,12 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { private Identifier claimedIdentifier; /// <summary> - /// The OpenID protocol version used at the identity Provider. - /// </summary> - private Protocol protocol; - - /// <summary> - /// The @priority given in the XRDS document for this specific OP endpoint. - /// </summary> - private int? uriPriority; - - /// <summary> - /// The @priority given in the XRDS document for this service - /// (which may consist of several endpoints). + /// Backing field for the <see cref="FriendlyIdentifierForDisplay"/> property. /// </summary> - private int? servicePriority; + private string friendlyIdentifierForDisplay; /// <summary> - /// Initializes a new instance of the <see cref="ServiceEndpoint"/> class. + /// 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> @@ -57,47 +48,23 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <param name="providerLocalIdentifier">The Provider Local Identifier.</param> /// <param name="servicePriority">The service priority.</param> /// <param name="uriPriority">The URI priority.</param> - private ServiceEndpoint(ProviderEndpointDescription providerEndpoint, Identifier claimedIdentifier, Identifier userSuppliedIdentifier, Identifier providerLocalIdentifier, int? servicePriority, int? uriPriority) { - Contract.Requires<ArgumentNullException>(claimedIdentifier != null); - Contract.Requires<ArgumentNullException>(providerEndpoint != null); - this.ProviderDescription = providerEndpoint; - this.ClaimedIdentifier = claimedIdentifier; - this.UserSuppliedIdentifier = userSuppliedIdentifier; - this.ProviderLocalIdentifier = providerLocalIdentifier ?? claimedIdentifier; - this.servicePriority = servicePriority; - this.uriPriority = uriPriority; - } - - /// <summary> - /// Initializes a new instance of the <see cref="ServiceEndpoint"/> 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="protocol">The protocol.</param> - /// <remarks> - /// Used for deserializing <see cref="ServiceEndpoint"/> from authentication responses. - /// </remarks> - private ServiceEndpoint(Uri providerEndpoint, Identifier claimedIdentifier, Identifier userSuppliedIdentifier, Identifier providerLocalIdentifier, Protocol protocol) { + private IdentifierDiscoveryResult(ProviderEndpointDescription providerEndpoint, Identifier claimedIdentifier, Identifier userSuppliedIdentifier, Identifier providerLocalIdentifier, int? servicePriority, int? uriPriority) { Contract.Requires<ArgumentNullException>(providerEndpoint != null); Contract.Requires<ArgumentNullException>(claimedIdentifier != null); - Contract.Requires<ArgumentNullException>(providerLocalIdentifier != null); - Contract.Requires<ArgumentNullException>(protocol != null); - + this.ProviderEndpoint = providerEndpoint.Uri; + this.Capabilities = new ReadOnlyCollection<string>(providerEndpoint.Capabilities); + this.Version = providerEndpoint.Version; this.ClaimedIdentifier = claimedIdentifier; - this.UserSuppliedIdentifier = userSuppliedIdentifier; - this.ProviderDescription = new ProviderEndpointDescription(providerEndpoint, protocol.Version); this.ProviderLocalIdentifier = providerLocalIdentifier ?? claimedIdentifier; - this.protocol = protocol; + this.UserSuppliedIdentifier = userSuppliedIdentifier; + this.ServicePriority = servicePriority; + this.ProviderEndpointPriority = uriPriority; } /// <summary> - /// Gets the URL that the OpenID Provider receives authentication requests at. + /// Gets the detected version of OpenID implemented by the Provider. /// </summary> - Uri IProviderEndpoint.Uri { - get { return this.ProviderDescription.Endpoint; } - } + public Version Version { get; private set; } /// <summary> /// Gets the Identifier that was presented by the end user to the Relying Party, @@ -110,14 +77,14 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { public Identifier UserSuppliedIdentifier { get; private set; } /// <summary> - /// Gets or sets the Identifier that the end user claims to own. + /// Gets the Identifier that the end user claims to control. /// </summary> public Identifier ClaimedIdentifier { get { return this.claimedIdentifier; } - set { + 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 @@ -134,8 +101,9 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { public Identifier ProviderLocalIdentifier { get; private set; } /// <summary> - /// Gets the value for the <see cref="IAuthenticationResponse.FriendlyIdentifierForDisplay"/> property. + /// 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) { @@ -162,57 +130,45 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { this.friendlyIdentifierForDisplay = this.ClaimedIdentifier; } } + return this.friendlyIdentifierForDisplay; } } /// <summary> - /// Gets the list of services available at this OP Endpoint for the - /// claimed Identifier. May be null. + /// Gets the provider endpoint. /// </summary> - public ReadOnlyCollection<string> ProviderSupportedServiceTypeUris { - get { return this.ProviderDescription.Capabilities; } - } + public Uri ProviderEndpoint { get; private set; } /// <summary> - /// Gets the OpenID protocol used by the Provider. + /// Gets the @priority given in the XRDS document for this specific OP endpoint. /// </summary> - public Protocol Protocol { - get { - if (this.protocol == null) { - this.protocol = Protocol.Lookup(this.ProviderDescription.ProtocolVersion); - } - - return this.protocol; - } - } - - #region IXrdsProviderEndpoint Members + public int? ProviderEndpointPriority { get; private set; } /// <summary> - /// Gets the priority associated with this service that may have been given - /// in the XRDS document. + /// Gets the @priority given in the XRDS document for this service + /// (which may consist of several endpoints). /// </summary> - int? IXrdsProviderEndpoint.ServicePriority { - get { return this.servicePriority; } - } + public int? ServicePriority { get; private set; } /// <summary> - /// Gets the priority associated with the service endpoint URL. + /// Gets the collection of service type URIs found in the XRDS document describing this Provider. /// </summary> - int? IXrdsProviderEndpoint.UriPriority { - get { return this.uriPriority; } - } + /// <value>Should never be null, but may be empty.</value> + public ReadOnlyCollection<string> Capabilities { get; private set; } - #endregion + #region IProviderEndpoint Members /// <summary> - /// Gets the detected version of OpenID implemented by the Provider. + /// Gets the URL that the OpenID Provider receives authentication requests at. /// </summary> - Version IProviderEndpoint.Version { - get { return this.ProviderDescription.ProtocolVersion; } + /// <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. @@ -220,7 +176,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <remarks> /// Endpoints lacking any priority value are sorted to the end of the list. /// </remarks> - internal static Comparison<IXrdsProviderEndpoint> EndpointOrder { + 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 @@ -234,11 +190,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { if (result != 0) { return result; } - if (se1.UriPriority.HasValue && se2.UriPriority.HasValue) { - return se1.UriPriority.Value.CompareTo(se2.UriPriority.Value); - } else if (se1.UriPriority.HasValue) { + 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.UriPriority.HasValue) { + } else if (se2.ProviderEndpointPriority.HasValue) { return 1; } else { return 0; @@ -250,11 +206,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { return 1; } else { // neither service defines a priority, so base ordering by uri priority. - if (se1.UriPriority.HasValue && se2.UriPriority.HasValue) { - return se1.UriPriority.Value.CompareTo(se2.UriPriority.Value); - } else if (se1.UriPriority.HasValue) { + 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.UriPriority.HasValue) { + } else if (se2.ProviderEndpointPriority.HasValue) { return 1; } else { return 0; @@ -266,35 +222,25 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> - /// Gets the URL which accepts OpenID Authentication protocol messages. + /// Gets the protocol used by the OpenID Provider. /// </summary> - /// <remarks> - /// Obtained by performing discovery on the User-Supplied Identifier. - /// This value MUST be an absolute HTTP or HTTPS URL. - /// </remarks> - internal Uri ProviderEndpoint { - get { return this.ProviderDescription.Endpoint; } - } + internal Protocol Protocol { + get { + if (this.protocol == null) { + this.protocol = Protocol.Lookup(this.Version); + } - /// <summary> - /// Gets a value indicating whether the <see cref="ProviderEndpoint"/> is using an encrypted channel. - /// </summary> - internal bool IsSecure { - get { return string.Equals(this.ProviderEndpoint.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase); } + return this.protocol; + } } /// <summary> - /// Gets the provider description. - /// </summary> - internal ProviderEndpointDescription ProviderDescription { get; private set; } - - /// <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 ==(ServiceEndpoint se1, ServiceEndpoint se2) { + public static bool operator ==(IdentifierDiscoveryResult se1, IdentifierDiscoveryResult se2) { return se1.EqualsNullSafe(se2); } @@ -304,44 +250,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <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 !=(ServiceEndpoint se1, ServiceEndpoint se2) { + public static bool operator !=(IdentifierDiscoveryResult se1, IdentifierDiscoveryResult se2) { return !(se1 == se2); } /// <summary> - /// Checks for the presence of a given Type URI in an XRDS service. - /// </summary> - /// <param name="typeUri">The type URI to check for.</param> - /// <returns> - /// <c>true</c> if the service type uri is present; <c>false</c> otherwise. - /// </returns> - public bool IsTypeUriPresent(string typeUri) { - return this.ProviderDescription.IsExtensionSupported(typeUri); - } - - /// <summary> - /// Determines whether a given extension is supported by this endpoint. - /// </summary> - /// <typeparam name="T">The type of extension to check support for on this endpoint.</typeparam> - /// <returns> - /// <c>true</c> if the extension is supported by this endpoint; otherwise, <c>false</c>. - /// </returns> - public bool IsExtensionSupported<T>() where T : IOpenIdMessageExtension, new() { - return this.ProviderDescription.IsExtensionSupported<T>(); - } - - /// <summary> - /// Determines whether a given extension is supported by this endpoint. - /// </summary> - /// <param name="extensionType">The type of extension to check support for on this endpoint.</param> - /// <returns> - /// <c>true</c> if the extension is supported by this endpoint; otherwise, <c>false</c>. - /// </returns> - public bool IsExtensionSupported(Type extensionType) { - return this.ProviderDescription.IsExtensionSupported(extensionType); - } - - /// <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> @@ -352,7 +265,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// The <paramref name="obj"/> parameter is null. /// </exception> public override bool Equals(object obj) { - ServiceEndpoint other = obj as ServiceEndpoint; + var other = obj as IdentifierDiscoveryResult; if (other == null) { return false; } @@ -388,57 +301,95 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { StringBuilder builder = new StringBuilder(); builder.AppendLine("ClaimedIdentifier: " + this.ClaimedIdentifier); builder.AppendLine("ProviderLocalIdentifier: " + this.ProviderLocalIdentifier); - builder.AppendLine("ProviderEndpoint: " + this.ProviderEndpoint.AbsoluteUri); - builder.AppendLine("OpenID version: " + this.Protocol.Version); + builder.AppendLine("ProviderEndpoint: " + this.ProviderEndpoint); + builder.AppendLine("OpenID version: " + this.Version); builder.AppendLine("Service Type URIs:"); - if (this.ProviderSupportedServiceTypeUris != null) { - foreach (string serviceTypeUri in this.ProviderSupportedServiceTypeUris) { - builder.Append("\t"); - builder.AppendLine(serviceTypeUri); - } - } else { - builder.AppendLine("\t(unavailable)"); + foreach (string serviceTypeUri in this.Capabilities) { + builder.Append("\t"); + builder.AppendLine(serviceTypeUri); } builder.Length -= Environment.NewLine.Length; // trim last newline return builder.ToString(); } /// <summary> - /// Reads previously discovered information about an endpoint - /// from a solicited authentication assertion for validation. + /// 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="reader">The reader from which to deserialize the <see cref="ServiceEndpoint"/>.</param> + /// <param name="extension">An instance of the extension to check support for.</param> /// <returns> - /// A <see cref="ServiceEndpoint"/> object that has everything - /// except the <see cref="ProviderSupportedServiceTypeUris"/> - /// deserialized. + /// <c>true</c> if the extension is supported by this endpoint; otherwise, <c>false</c>. /// </returns> - internal static ServiceEndpoint Deserialize(TextReader reader) { - var claimedIdentifier = Identifier.Parse(reader.ReadLine()); - var providerLocalIdentifier = Identifier.Parse(reader.ReadLine()); - string userSuppliedIdentifier = reader.ReadLine(); - if (userSuppliedIdentifier.Length == 0) { - userSuppliedIdentifier = null; + public bool IsExtensionSupported(IOpenIdMessageExtension extension) { + Contract.Requires<ArgumentNullException>(extension != null); + + // 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; + } } - var providerEndpoint = new Uri(reader.ReadLine()); - var protocol = Protocol.FindBestVersion(p => p.Version, new[] { new Version(reader.ReadLine()) }); - return new ServiceEndpoint(providerEndpoint, claimedIdentifier, userSuppliedIdentifier, providerLocalIdentifier, protocol); + + return false; } /// <summary> - /// Creates a <see cref="ServiceEndpoint"/> instance to represent some OP Identifier. + /// 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="ServiceEndpoint"/> instance</returns> - internal static ServiceEndpoint CreateForProviderIdentifier(Identifier providerIdentifier, ProviderEndpointDescription providerEndpoint, int? servicePriority, int? uriPriority) { + /// <returns>The created <see cref="IdentifierDiscoveryResult"/> instance</returns> + internal static IdentifierDiscoveryResult CreateForProviderIdentifier(Identifier providerIdentifier, ProviderEndpointDescription providerEndpoint, int? servicePriority, int? uriPriority) { Contract.Requires<ArgumentNullException>(providerEndpoint != null); - Protocol protocol = Protocol.Detect(providerEndpoint.Capabilities); + Protocol protocol = Protocol.Lookup(providerEndpoint.Version); - return new ServiceEndpoint( + return new IdentifierDiscoveryResult( providerEndpoint, protocol.ClaimedIdentifierForOPIdentifier, providerIdentifier, @@ -448,20 +399,20 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> - /// Creates a <see cref="ServiceEndpoint"/> instance to represent some Claimed Identifier. + /// 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="ServiceEndpoint"/> instance</returns> - internal static ServiceEndpoint CreateForClaimedIdentifier(Identifier claimedIdentifier, Identifier providerLocalIdentifier, ProviderEndpointDescription providerEndpoint, int? servicePriority, int? uriPriority) { + /// <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="ServiceEndpoint"/> instance to represent some Claimed Identifier. + /// 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> @@ -469,24 +420,21 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <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="ServiceEndpoint"/> instance</returns> - internal static ServiceEndpoint CreateForClaimedIdentifier(Identifier claimedIdentifier, Identifier userSuppliedIdentifier, Identifier providerLocalIdentifier, ProviderEndpointDescription providerEndpoint, int? servicePriority, int? uriPriority) { - return new ServiceEndpoint(providerEndpoint, claimedIdentifier, userSuppliedIdentifier, providerLocalIdentifier, servicePriority, uriPriority); + /// <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> - /// Saves the discovered information about this endpoint - /// for later comparison to validate assertions. + /// Determines whether a given type URI is present on the specified provider endpoint. /// </summary> - /// <param name="writer">The writer to use for serializing out the fields.</param> - internal void Serialize(TextWriter writer) { - writer.WriteLine(this.ClaimedIdentifier); - writer.WriteLine(this.ProviderLocalIdentifier); - writer.WriteLine(this.UserSuppliedIdentifier); - writer.WriteLine(this.ProviderEndpoint); - writer.WriteLine(this.Protocol.Version); - - // No reason to serialize priority. We only needed priority to decide whether to use this endpoint. + /// <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) { + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(typeUri)); + return this.Capabilities.Contains(typeUri); } /// <summary> @@ -495,22 +443,39 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </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(IXrdsProviderEndpoint endpoint) { + 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.IsTypeUriPresent(Protocol.V20.OPIdentifierServiceTypeURI)) { + if (endpoint.Capabilities.Contains(Protocol.V20.OPIdentifierServiceTypeURI)) { return 0; } - if (endpoint.IsTypeUriPresent(Protocol.V20.ClaimedIdentifierServiceTypeURI)) { + if (endpoint.Capabilities.Contains(Protocol.V20.ClaimedIdentifierServiceTypeURI)) { return 1; } - if (endpoint.IsTypeUriPresent(Protocol.V11.ClaimedIdentifierServiceTypeURI)) { + if (endpoint.Capabilities.Contains(Protocol.V11.ClaimedIdentifierServiceTypeURI)) { return 2; } - if (endpoint.IsTypeUriPresent(Protocol.V10.ClaimedIdentifierServiceTypeURI)) { + 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/Messages/AssociateRequest.cs b/src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs index 5215022..3fd9424 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs @@ -76,16 +76,16 @@ namespace DotNetOpenAuth.OpenId.Messages { /// Null if no association could be created that meet the security requirements /// and the provider OpenID version. /// </returns> - internal static AssociateRequest Create(SecuritySettings securityRequirements, ProviderEndpointDescription provider) { + internal static AssociateRequest Create(SecuritySettings securityRequirements, IProviderEndpoint provider) { Contract.Requires<ArgumentNullException>(securityRequirements != null); Contract.Requires<ArgumentNullException>(provider != null); // Apply our knowledge of the endpoint's transport, OpenID version, and // security requirements to decide the best association. - bool unencryptedAllowed = provider.Endpoint.IsTransportSecure(); + bool unencryptedAllowed = provider.Uri.IsTransportSecure(); bool useDiffieHellman = !unencryptedAllowed; string associationType, sessionType; - if (!HmacShaAssociation.TryFindBestAssociation(Protocol.Lookup(provider.ProtocolVersion), true, securityRequirements, useDiffieHellman, out associationType, out sessionType)) { + if (!HmacShaAssociation.TryFindBestAssociation(Protocol.Lookup(provider.Version), true, securityRequirements, useDiffieHellman, out associationType, out sessionType)) { // There are no associations that meet all requirements. Logger.OpenId.Warn("Security requirements and protocol combination knock out all possible association types. Dumb mode forced."); return null; @@ -106,19 +106,19 @@ namespace DotNetOpenAuth.OpenId.Messages { /// Null if no association could be created that meet the security requirements /// and the provider OpenID version. /// </returns> - internal static AssociateRequest Create(SecuritySettings securityRequirements, ProviderEndpointDescription provider, string associationType, string sessionType) { + internal static AssociateRequest Create(SecuritySettings securityRequirements, IProviderEndpoint provider, string associationType, string sessionType) { Contract.Requires<ArgumentNullException>(securityRequirements != null); Contract.Requires<ArgumentNullException>(provider != null); Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(associationType)); Contract.Requires<ArgumentNullException>(sessionType != null); - bool unencryptedAllowed = provider.Endpoint.IsTransportSecure(); + bool unencryptedAllowed = provider.Uri.IsTransportSecure(); if (unencryptedAllowed) { - var associateRequest = new AssociateUnencryptedRequest(provider.ProtocolVersion, provider.Endpoint); + var associateRequest = new AssociateUnencryptedRequest(provider.Version, provider.Uri); associateRequest.AssociationType = associationType; return associateRequest; } else { - var associateRequest = new AssociateDiffieHellmanRequest(provider.ProtocolVersion, provider.Endpoint); + var associateRequest = new AssociateDiffieHellmanRequest(provider.Version, provider.Uri); associateRequest.AssociationType = associationType; associateRequest.SessionType = sessionType; associateRequest.InitializeRequest(); diff --git a/src/DotNetOpenAuth/OpenId/NoDiscoveryIdentifier.cs b/src/DotNetOpenAuth/OpenId/NoDiscoveryIdentifier.cs index 636df67..1a6e7e9 100644 --- a/src/DotNetOpenAuth/OpenId/NoDiscoveryIdentifier.cs +++ b/src/DotNetOpenAuth/OpenId/NoDiscoveryIdentifier.cs @@ -70,17 +70,6 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> - /// Performs discovery on the Identifier. - /// </summary> - /// <param name="requestHandler">The web request handler to use for discovery.</param> - /// <returns> - /// An initialized structure containing the discovered provider endpoint information. - /// </returns> - internal override IEnumerable<ServiceEndpoint> Discover(IDirectWebRequestHandler requestHandler) { - return Enumerable.Empty<ServiceEndpoint>(); - } - - /// <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. diff --git a/src/DotNetOpenAuth/OpenId/OpenIdUtilities.cs b/src/DotNetOpenAuth/OpenId/OpenIdUtilities.cs index 3e75e61..04eb23c 100644 --- a/src/DotNetOpenAuth/OpenId/OpenIdUtilities.cs +++ b/src/DotNetOpenAuth/OpenId/OpenIdUtilities.cs @@ -16,13 +16,14 @@ namespace DotNetOpenAuth.OpenId { using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.ChannelElements; using DotNetOpenAuth.OpenId.Extensions; + using DotNetOpenAuth.OpenId.Messages; using DotNetOpenAuth.OpenId.Provider; using DotNetOpenAuth.OpenId.RelyingParty; /// <summary> /// A set of utilities especially useful to OpenID. /// </summary> - internal static class OpenIdUtilities { + public static class OpenIdUtilities { /// <summary> /// The prefix to designate this library's proprietary parameters added to the protocol. /// </summary> diff --git a/src/DotNetOpenAuth/OpenId/OpenIdXrdsHelper.cs b/src/DotNetOpenAuth/OpenId/OpenIdXrdsHelper.cs index 664a127..8e11700 100644 --- a/src/DotNetOpenAuth/OpenId/OpenIdXrdsHelper.cs +++ b/src/DotNetOpenAuth/OpenId/OpenIdXrdsHelper.cs @@ -55,15 +55,19 @@ namespace DotNetOpenAuth.OpenId { /// <returns> /// A sequence of OpenID Providers that can assert ownership of the <paramref name="claimedIdentifier"/>. /// </returns> - internal static IEnumerable<ServiceEndpoint> CreateServiceEndpoints(this XrdsDocument xrds, UriIdentifier claimedIdentifier, UriIdentifier userSuppliedIdentifier) { - var endpoints = new List<ServiceEndpoint>(); - endpoints.AddRange(xrds.GenerateOPIdentifierServiceEndpoints(userSuppliedIdentifier)); + internal static IEnumerable<IdentifierDiscoveryResult> CreateServiceEndpoints(this XrdsDocument xrds, UriIdentifier claimedIdentifier, UriIdentifier userSuppliedIdentifier) { + var endpoints = new List<IdentifierDiscoveryResult>(); - // If any OP Identifier service elements were found, we must not proceed - // to return any Claimed Identifier services. - if (endpoints.Count == 0) { - endpoints.AddRange(xrds.GenerateClaimedIdentifierServiceEndpoints(claimedIdentifier, userSuppliedIdentifier)); - } + // We used to be particular here about NOT returning any claimed identifier services + // when OP Identifier services were found, consistent with OpenID 2.0 sections 7.3.2.2 and 11.2. + // But now to enable one XRDS document to describe both the originating OP Identifier and also + // fill the role (with appropriate query strings) of a claimed identifier, we return both service + // types. The AuthenticationRequest class is now responsible to ignore claimed identifier services + // when OP Identifier services are present when formulating authentication requests. + // For a discussion on this topic and the reason for this change, see + // http://groups.google.com/group/dotnetopenid/browse_thread/thread/4b5a8c6b2210f387/5e25910e4d2252c8 + 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; @@ -76,11 +80,11 @@ namespace DotNetOpenAuth.OpenId { /// <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<ServiceEndpoint> CreateServiceEndpoints(this XrdsDocument xrds, XriIdentifier userSuppliedIdentifier) { + internal static IEnumerable<IdentifierDiscoveryResult> CreateServiceEndpoints(this XrdsDocument xrds, XriIdentifier userSuppliedIdentifier) { Contract.Requires<ArgumentNullException>(xrds != null); Contract.Requires<ArgumentNullException>(userSuppliedIdentifier != null); - Contract.Ensures(Contract.Result<IEnumerable<ServiceEndpoint>>() != null); - var endpoints = new List<ServiceEndpoint>(); + Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null); + var endpoints = new List<IdentifierDiscoveryResult>(); endpoints.AddRange(xrds.GenerateOPIdentifierServiceEndpoints(userSuppliedIdentifier)); // If any OP Identifier service elements were found, we must not proceed @@ -99,15 +103,15 @@ namespace DotNetOpenAuth.OpenId { /// <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<ServiceEndpoint> GenerateOPIdentifierServiceEndpoints(this XrdsDocument xrds, Identifier opIdentifier) { + private static IEnumerable<IdentifierDiscoveryResult> GenerateOPIdentifierServiceEndpoints(this XrdsDocument xrds, Identifier opIdentifier) { Contract.Requires<ArgumentNullException>(xrds != null); Contract.Requires<ArgumentNullException>(opIdentifier != null); - Contract.Ensures(Contract.Result<IEnumerable<ServiceEndpoint>>() != null); + 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 ServiceEndpoint.CreateForProviderIdentifier(opIdentifier, providerDescription, service.Priority, uri.Priority); + select IdentifierDiscoveryResult.CreateForProviderIdentifier(opIdentifier, providerDescription, service.Priority, uri.Priority); } /// <summary> @@ -120,12 +124,12 @@ namespace DotNetOpenAuth.OpenId { /// <returns> /// A sequence of the providers that can assert ownership of the given identifier. /// </returns> - private static IEnumerable<ServiceEndpoint> GenerateClaimedIdentifierServiceEndpoints(this XrdsDocument xrds, UriIdentifier claimedIdentifier, UriIdentifier userSuppliedIdentifier) { + private static IEnumerable<IdentifierDiscoveryResult> GenerateClaimedIdentifierServiceEndpoints(this XrdsDocument xrds, UriIdentifier claimedIdentifier, UriIdentifier userSuppliedIdentifier) { return from service in xrds.FindClaimedIdentifierServices() from uri in service.UriElements where uri.Uri != null let providerEndpoint = new ProviderEndpointDescription(uri.Uri, service.TypeElementUris) - select ServiceEndpoint.CreateForClaimedIdentifier(claimedIdentifier, userSuppliedIdentifier, service.ProviderLocalIdentifier, providerEndpoint, service.Priority, uri.Priority); + select IdentifierDiscoveryResult.CreateForClaimedIdentifier(claimedIdentifier, userSuppliedIdentifier, service.ProviderLocalIdentifier, providerEndpoint, service.Priority, uri.Priority); } /// <summary> @@ -135,7 +139,7 @@ namespace DotNetOpenAuth.OpenId { /// <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<ServiceEndpoint> GenerateClaimedIdentifierServiceEndpoints(this XrdsDocument xrds, XriIdentifier userSuppliedIdentifier) { + private static IEnumerable<IdentifierDiscoveryResult> GenerateClaimedIdentifierServiceEndpoints(this XrdsDocument xrds, XriIdentifier userSuppliedIdentifier) { foreach (var service in xrds.FindClaimedIdentifierServices()) { foreach (var uri in service.UriElements) { // spec section 7.3.2.3 on Claimed Id -> CanonicalID substitution @@ -148,7 +152,7 @@ namespace DotNetOpenAuth.OpenId { // 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 ServiceEndpoint.CreateForClaimedIdentifier(claimedIdentifier, userSuppliedIdentifier, service.ProviderLocalIdentifier, providerEndpoint, service.Priority, uri.Priority); + yield return IdentifierDiscoveryResult.CreateForClaimedIdentifier(claimedIdentifier, userSuppliedIdentifier, service.ProviderLocalIdentifier, providerEndpoint, service.Priority, uri.Priority); } } } diff --git a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs index 3eb24d4..d24873b 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs @@ -20,6 +20,7 @@ namespace DotNetOpenAuth.OpenId.Provider { using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.OpenId.ChannelElements; using DotNetOpenAuth.OpenId.Messages; + using RP = DotNetOpenAuth.OpenId.RelyingParty; /// <summary> /// Offers services for a web page that is acting as an OpenID identity server. @@ -43,6 +44,12 @@ namespace DotNetOpenAuth.OpenId.Provider { private ProviderSecuritySettings securitySettings; /// <summary> + /// The relying party used to perform discovery on identifiers being sent in + /// unsolicited positive assertions. + /// </summary> + private RP.OpenIdRelyingParty relyingParty; + + /// <summary> /// Initializes a new instance of the <see cref="OpenIdProvider"/> class. /// </summary> public OpenIdProvider() @@ -158,6 +165,13 @@ namespace DotNetOpenAuth.OpenId.Provider { } /// <summary> + /// Gets the list of services that can perform discovery on identifiers given to this relying party. + /// </summary> + internal IList<IIdentifierDiscoveryService> DiscoveryServices { + get { return this.RelyingParty.DiscoveryServices; } + } + + /// <summary> /// Gets the association store. /// </summary> internal IAssociationStore<AssociationRelyingPartyType> AssociationStore { get; private set; } @@ -171,6 +185,25 @@ namespace DotNetOpenAuth.OpenId.Provider { } /// <summary> + /// Gets the relying party used for discovery of identifiers sent in unsolicited assertions. + /// </summary> + private RP.OpenIdRelyingParty RelyingParty { + get { + if (this.relyingParty == null) { + lock (this) { + if (this.relyingParty == null) { + // we just need an RP that's capable of discovery, so stateless mode is fine. + this.relyingParty = new RP.OpenIdRelyingParty(null); + } + } + } + + this.relyingParty.Channel.WebRequestHandler = this.WebRequestHandler; + return this.relyingParty; + } + } + + /// <summary> /// Gets the incoming OpenID request if there is one, or null if none was detected. /// </summary> /// <returns>The request that the hosting Provider should possibly process and then transmit the response for.</returns> @@ -312,7 +345,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// web site and log him/her in immediately in one uninterrupted step. /// </summary> /// <param name="providerEndpoint">The absolute URL on the Provider site that receives OpenID messages.</param> - /// <param name="relyingParty">The URL of the Relying Party web site. + /// <param name="relyingPartyRealm">The URL of the Relying Party web site. /// This will typically be the home page, but may be a longer URL if /// that Relying Party considers the scope of its realm to be more specific. /// The URL provided here must allow discovery of the Relying Party's @@ -321,15 +354,15 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <param name="localIdentifier">The Identifier you know your user by internally. This will typically /// be the same as <paramref name="claimedIdentifier"/>.</param> /// <param name="extensions">The extensions.</param> - public void SendUnsolicitedAssertion(Uri providerEndpoint, Realm relyingParty, Identifier claimedIdentifier, Identifier localIdentifier, params IExtensionMessage[] extensions) { + public void SendUnsolicitedAssertion(Uri providerEndpoint, Realm relyingPartyRealm, Identifier claimedIdentifier, Identifier localIdentifier, params IExtensionMessage[] extensions) { Contract.Requires<InvalidOperationException>(HttpContext.Current != null, MessagingStrings.HttpContextRequired); Contract.Requires<ArgumentNullException>(providerEndpoint != null); Contract.Requires<ArgumentException>(providerEndpoint.IsAbsoluteUri); - Contract.Requires<ArgumentNullException>(relyingParty != null); + Contract.Requires<ArgumentNullException>(relyingPartyRealm != null); Contract.Requires<ArgumentNullException>(claimedIdentifier != null); Contract.Requires<ArgumentNullException>(localIdentifier != null); - this.PrepareUnsolicitedAssertion(providerEndpoint, relyingParty, claimedIdentifier, localIdentifier, extensions).Send(); + this.PrepareUnsolicitedAssertion(providerEndpoint, relyingPartyRealm, claimedIdentifier, localIdentifier, extensions).Send(); } /// <summary> @@ -338,7 +371,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// web site and log him/her in immediately in one uninterrupted step. /// </summary> /// <param name="providerEndpoint">The absolute URL on the Provider site that receives OpenID messages.</param> - /// <param name="relyingParty">The URL of the Relying Party web site. + /// <param name="relyingPartyRealm">The URL of the Relying Party web site. /// This will typically be the home page, but may be a longer URL if /// that Relying Party considers the scope of its realm to be more specific. /// The URL provided here must allow discovery of the Relying Party's @@ -351,10 +384,10 @@ namespace DotNetOpenAuth.OpenId.Provider { /// A <see cref="OutgoingWebResponse"/> object describing the HTTP response to send /// the user agent to allow the redirect with assertion to happen. /// </returns> - public OutgoingWebResponse PrepareUnsolicitedAssertion(Uri providerEndpoint, Realm relyingParty, Identifier claimedIdentifier, Identifier localIdentifier, params IExtensionMessage[] extensions) { + public OutgoingWebResponse PrepareUnsolicitedAssertion(Uri providerEndpoint, Realm relyingPartyRealm, Identifier claimedIdentifier, Identifier localIdentifier, params IExtensionMessage[] extensions) { Contract.Requires<ArgumentNullException>(providerEndpoint != null); Contract.Requires<ArgumentException>(providerEndpoint.IsAbsoluteUri); - Contract.Requires<ArgumentNullException>(relyingParty != null); + Contract.Requires<ArgumentNullException>(relyingPartyRealm != null); Contract.Requires<ArgumentNullException>(claimedIdentifier != null); Contract.Requires<ArgumentNullException>(localIdentifier != null); Contract.Requires<InvalidOperationException>(this.Channel.WebRequestHandler != null); @@ -364,8 +397,8 @@ namespace DotNetOpenAuth.OpenId.Provider { // do due diligence by performing our own discovery on the claimed identifier // and make sure that it is tied to this OP and OP local identifier. if (this.SecuritySettings.UnsolicitedAssertionVerification != ProviderSecuritySettings.UnsolicitedAssertionVerificationLevel.NeverVerify) { - var serviceEndpoint = DotNetOpenAuth.OpenId.RelyingParty.ServiceEndpoint.CreateForClaimedIdentifier(claimedIdentifier, localIdentifier, new ProviderEndpointDescription(providerEndpoint, Protocol.Default.Version), null, null); - var discoveredEndpoints = claimedIdentifier.Discover(this.WebRequestHandler); + var serviceEndpoint = IdentifierDiscoveryResult.CreateForClaimedIdentifier(claimedIdentifier, localIdentifier, new ProviderEndpointDescription(providerEndpoint, Protocol.Default.Version), null, null); + var discoveredEndpoints = this.RelyingParty.Discover(claimedIdentifier); if (!discoveredEndpoints.Contains(serviceEndpoint)) { Logger.OpenId.WarnFormat( "Failed to send unsolicited assertion for {0} because its discovered services did not include this endpoint: {1}{2}{1}Discovered endpoints: {1}{3}", @@ -383,11 +416,11 @@ namespace DotNetOpenAuth.OpenId.Provider { Logger.OpenId.InfoFormat("Preparing unsolicited assertion for {0}", claimedIdentifier); RelyingPartyEndpointDescription returnToEndpoint = null; - var returnToEndpoints = relyingParty.DiscoverReturnToEndpoints(this.WebRequestHandler, true); + var returnToEndpoints = relyingPartyRealm.DiscoverReturnToEndpoints(this.WebRequestHandler, true); if (returnToEndpoints != null) { returnToEndpoint = returnToEndpoints.FirstOrDefault(); } - ErrorUtilities.VerifyProtocol(returnToEndpoint != null, OpenIdStrings.NoRelyingPartyEndpointDiscovered, relyingParty); + ErrorUtilities.VerifyProtocol(returnToEndpoint != null, OpenIdStrings.NoRelyingPartyEndpointDiscovered, relyingPartyRealm); var positiveAssertion = new PositiveAssertionResponse(returnToEndpoint) { ProviderEndpoint = providerEndpoint, @@ -425,6 +458,10 @@ namespace DotNetOpenAuth.OpenId.Provider { if (channel != null) { channel.Dispose(); } + + if (this.relyingParty != null) { + this.relyingParty.Dispose(); + } } } diff --git a/src/DotNetOpenAuth/OpenId/ProviderEndpointDescription.cs b/src/DotNetOpenAuth/OpenId/ProviderEndpointDescription.cs index 4cfbac5..bb4a9ba 100644 --- a/src/DotNetOpenAuth/OpenId/ProviderEndpointDescription.cs +++ b/src/DotNetOpenAuth/OpenId/ProviderEndpointDescription.cs @@ -8,6 +8,7 @@ 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; @@ -20,8 +21,7 @@ namespace DotNetOpenAuth.OpenId { /// <remarks> /// This is an immutable type. /// </remarks> - [Serializable] - internal class ProviderEndpointDescription : IProviderEndpoint { + internal sealed class ProviderEndpointDescription : IProviderEndpoint { /// <summary> /// Initializes a new instance of the <see cref="ProviderEndpointDescription"/> class. /// </summary> @@ -31,8 +31,9 @@ namespace DotNetOpenAuth.OpenId { Contract.Requires<ArgumentNullException>(providerEndpoint != null); Contract.Requires<ArgumentNullException>(openIdVersion != null); - this.Endpoint = providerEndpoint; - this.ProtocolVersion = openIdVersion; + this.Uri = providerEndpoint; + this.Version = openIdVersion; + this.Capabilities = new ReadOnlyCollection<string>(EmptyList<string>.Instance); } /// <summary> @@ -44,42 +45,24 @@ namespace DotNetOpenAuth.OpenId { Contract.Requires<ArgumentNullException>(providerEndpoint != null); Contract.Requires<ArgumentNullException>(serviceTypeURIs != null); - this.Endpoint = providerEndpoint; + 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.ProtocolVersion = opIdentifierProtocol.Version; + this.Version = opIdentifierProtocol.Version; } else if (claimedIdentifierProviderVersion != null) { - this.ProtocolVersion = claimedIdentifierProviderVersion.Version; + this.Version = claimedIdentifierProviderVersion.Version; + } else { + ErrorUtilities.ThrowProtocol(OpenIdStrings.ProviderVersionUnrecognized, this.Uri); } - - ErrorUtilities.VerifyProtocol(this.ProtocolVersion != null, OpenIdStrings.ProviderVersionUnrecognized, this.Endpoint); } - #region IProviderEndpoint Properties - - /// <summary> - /// Gets the detected version of OpenID implemented by the Provider. - /// </summary> - Version IProviderEndpoint.Version { - get { return this.ProtocolVersion; } - } - - /// <summary> - /// Gets the URL that the OpenID Provider receives authentication requests at. - /// </summary> - Uri IProviderEndpoint.Uri { - get { return this.Endpoint; } - } - - #endregion - /// <summary> /// Gets the URL that the OpenID Provider listens for incoming OpenID messages on. /// </summary> - internal Uri Endpoint { get; private set; } + public Uri Uri { get; private set; } /// <summary> /// Gets the OpenID protocol version this endpoint supports. @@ -88,14 +71,14 @@ namespace DotNetOpenAuth.OpenId { /// If an endpoint supports multiple versions, each version must be represented /// by its own <see cref="ProviderEndpointDescription"/> object. /// </remarks> - internal Version ProtocolVersion { get; private set; } + 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 Methods + #region IProviderEndpoint Members /// <summary> /// Checks whether the OpenId Identifier claims support for a given extension. @@ -111,10 +94,8 @@ namespace DotNetOpenAuth.OpenId { /// 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<T>() where T : IOpenIdMessageExtension, new() { - ErrorUtilities.VerifyOperation(this.Capabilities != null, OpenIdStrings.ExtensionLookupSupportUnavailable); - T extension = new T(); - return this.IsExtensionSupported(extension); + bool IProviderEndpoint.IsExtensionSupported<T>() { + throw new NotImplementedException(); } /// <summary> @@ -131,52 +112,22 @@ namespace DotNetOpenAuth.OpenId { /// 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) { - ErrorUtilities.VerifyOperation(this.Capabilities != null, OpenIdStrings.ExtensionLookupSupportUnavailable); - var extension = (IOpenIdMessageExtension)Activator.CreateInstance(extensionType); - return this.IsExtensionSupported(extension); + bool IProviderEndpoint.IsExtensionSupported(Type extensionType) { + throw new NotImplementedException(); } #endregion +#if CONTRACTS_FULL /// <summary> - /// Determines whether some extension is supported by the Provider. + /// Verifies conditions that should be true for any valid state of this object. /// </summary> - /// <param name="extensionUri">The extension URI.</param> - /// <returns> - /// <c>true</c> if the extension is supported; otherwise, <c>false</c>. - /// </returns> - protected internal bool IsExtensionSupported(string extensionUri) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(extensionUri)); - Contract.Requires<InvalidOperationException>(this.Capabilities != null, OpenIdStrings.ExtensionLookupSupportUnavailable); - return this.Capabilities.Contains(extensionUri); - } - - /// <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> - protected internal bool IsExtensionSupported(IOpenIdMessageExtension extension) { - Contract.Requires<ArgumentNullException>(extension != null); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(extension.TypeUri)); - Contract.Requires<InvalidOperationException>(this.Capabilities != null, OpenIdStrings.ExtensionLookupSupportUnavailable); - - // Consider the primary case. - if (this.IsExtensionSupported(extension.TypeUri)) { - return true; - } - - // Consider the secondary cases. - if (extension.AdditionalSupportedTypeUris != null) { - if (extension.AdditionalSupportedTypeUris.Any(typeUri => this.IsExtensionSupported(typeUri))) { - return true; - } - } - - return false; + [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/RelyingParty/AssociationManager.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/AssociationManager.cs index d3e0686..7d30a3f 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/AssociationManager.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/AssociationManager.cs @@ -95,7 +95,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> /// <param name="provider">The provider to create an association with.</param> /// <returns>The association if one exists and has useful life remaining. Otherwise <c>null</c>.</returns> - internal Association GetExistingAssociation(ProviderEndpointDescription provider) { + internal Association GetExistingAssociation(IProviderEndpoint provider) { Contract.Requires<ArgumentNullException>(provider != null); // If the RP has no application store for associations, there's no point in creating one. @@ -103,7 +103,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { return null; } - Association association = this.associationStore.GetAssociation(provider.Endpoint, this.SecuritySettings); + Association association = this.associationStore.GetAssociation(provider.Uri, this.SecuritySettings); // If the returned association does not fulfill security requirements, ignore it. if (association != null && !this.SecuritySettings.IsAssociationInPermittedRange(association)) { @@ -123,7 +123,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> /// <param name="provider">The provider to get an association for.</param> /// <returns>The existing or new association; <c>null</c> if none existed and one could not be created.</returns> - internal Association GetOrCreateAssociation(ProviderEndpointDescription provider) { + internal Association GetOrCreateAssociation(IProviderEndpoint provider) { return this.GetExistingAssociation(provider) ?? this.CreateNewAssociation(provider); } @@ -140,7 +140,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// association store. /// Any new association is automatically added to the <see cref="associationStore"/>. /// </remarks> - private Association CreateNewAssociation(ProviderEndpointDescription provider) { + private Association CreateNewAssociation(IProviderEndpoint provider) { Contract.Requires<ArgumentNullException>(provider != null); // If there is no association store, there is no point in creating an association. @@ -164,7 +164,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// The newly created association, or null if no association can be created with /// the given Provider given the current security settings. /// </returns> - private Association CreateNewAssociation(ProviderEndpointDescription provider, AssociateRequest associateRequest, int retriesRemaining) { + private Association CreateNewAssociation(IProviderEndpoint provider, AssociateRequest associateRequest, int retriesRemaining) { Contract.Requires<ArgumentNullException>(provider != null); if (associateRequest == null || retriesRemaining < 0) { @@ -179,7 +179,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { var associateUnsuccessfulResponse = associateResponse as AssociateUnsuccessfulResponse; if (associateSuccessfulResponse != null) { Association association = associateSuccessfulResponse.CreateAssociation(associateRequest, null); - this.associationStore.StoreAssociation(provider.Endpoint, association); + this.associationStore.StoreAssociation(provider.Uri, association); return association; } else if (associateUnsuccessfulResponse != null) { if (string.IsNullOrEmpty(associateUnsuccessfulResponse.AssociationType)) { @@ -187,7 +187,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { return null; } - if (!this.securitySettings.IsAssociationInPermittedRange(Protocol.Lookup(provider.ProtocolVersion), associateUnsuccessfulResponse.AssociationType)) { + if (!this.securitySettings.IsAssociationInPermittedRange(Protocol.Lookup(provider.Version), associateUnsuccessfulResponse.AssociationType)) { Logger.OpenId.DebugFormat("Provider rejected an association request and suggested '{0}' as an association to try, which this Relying Party does not support. Giving up.", associateUnsuccessfulResponse.AssociationType); return null; } @@ -198,7 +198,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } // Make sure the Provider isn't suggesting an incompatible pair of association/session types. - Protocol protocol = Protocol.Lookup(provider.ProtocolVersion); + Protocol protocol = Protocol.Lookup(provider.Version); ErrorUtilities.VerifyProtocol( HmacShaAssociation.IsDHSessionCompatible(protocol, associateUnsuccessfulResponse.AssociationType, associateUnsuccessfulResponse.SessionType), OpenIdStrings.IncompatibleAssociationAndSessionTypes, @@ -220,7 +220,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { // Since having associations with OPs is not totally critical, we'll log and eat // the exception so that auth may continue in dumb mode. - Logger.OpenId.ErrorFormat("An error occurred while trying to create an association with {0}. {1}", provider.Endpoint, ex); + Logger.OpenId.ErrorFormat("An error occurred while trying to create an association with {0}. {1}", provider.Uri, ex); return null; } } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs index f1851a0..b7e0555 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs @@ -32,12 +32,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { private readonly OpenIdRelyingParty RelyingParty; /// <summary> - /// The endpoint that describes the particular OpenID Identifier and Provider that - /// will be used to create the authentication request. - /// </summary> - private readonly ServiceEndpoint endpoint; - - /// <summary> /// How an association may or should be created or used in the formulation of the /// authentication request. /// </summary> @@ -67,17 +61,17 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// Initializes a new instance of the <see cref="AuthenticationRequest"/> class. /// </summary> - /// <param name="endpoint">The endpoint that describes the OpenID Identifier and Provider that will complete the authentication.</param> + /// <param name="discoveryResult">The endpoint that describes the OpenID Identifier and Provider that will complete the authentication.</param> /// <param name="realm">The realm, or root URL, of the host web site.</param> /// <param name="returnToUrl">The base return_to URL that the Provider should return the user to to complete authentication. This should not include callback parameters as these should be added using the <see cref="AddCallbackArguments(string, string)"/> method.</param> /// <param name="relyingParty">The relying party that created this instance.</param> - private AuthenticationRequest(ServiceEndpoint endpoint, Realm realm, Uri returnToUrl, OpenIdRelyingParty relyingParty) { - Contract.Requires<ArgumentNullException>(endpoint != null); + private AuthenticationRequest(IdentifierDiscoveryResult discoveryResult, Realm realm, Uri returnToUrl, OpenIdRelyingParty relyingParty) { + Contract.Requires<ArgumentNullException>(discoveryResult != null); Contract.Requires<ArgumentNullException>(realm != null); Contract.Requires<ArgumentNullException>(returnToUrl != null); Contract.Requires<ArgumentNullException>(relyingParty != null); - this.endpoint = endpoint; + this.DiscoveryResult = discoveryResult; this.RelyingParty = relyingParty; this.Realm = realm; this.ReturnToUrl = returnToUrl; @@ -137,7 +131,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// property for a null value. /// </remarks> public Identifier ClaimedIdentifier { - get { return this.IsDirectedIdentity ? null : this.endpoint.ClaimedIdentifier; } + get { return this.IsDirectedIdentity ? null : this.DiscoveryResult.ClaimedIdentifier; } } /// <summary> @@ -145,7 +139,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// determine and send the ClaimedIdentifier after authentication. /// </summary> public bool IsDirectedIdentity { - get { return this.endpoint.ClaimedIdentifier == this.endpoint.Protocol.ClaimedIdentifierForOPIdentifier; } + get { return this.DiscoveryResult.ClaimedIdentifier == this.DiscoveryResult.Protocol.ClaimedIdentifierForOPIdentifier; } } /// <summary> @@ -164,9 +158,15 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// location. /// </summary> public IProviderEndpoint Provider { - get { return this.endpoint; } + get { return this.DiscoveryResult; } } + /// <summary> + /// Gets the discovery result leading to the formulation of this request. + /// </summary> + /// <value>The discovery result.</value> + public IdentifierDiscoveryResult DiscoveryResult { get; private set; } + #endregion /// <summary> @@ -192,13 +192,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { get { return this.extensions; } } - /// <summary> - /// Gets the service endpoint. - /// </summary> - internal ServiceEndpoint Endpoint { - get { return this.endpoint; } - } - #region IAuthenticationRequest methods /// <summary> @@ -364,12 +357,20 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { ErrorUtilities.VerifyProtocol(realm.Contains(returnToUrl), OpenIdStrings.ReturnToNotUnderRealm, returnToUrl, realm); // Perform discovery right now (not deferred). - IEnumerable<ServiceEndpoint> serviceEndpoints; + IEnumerable<IdentifierDiscoveryResult> serviceEndpoints; try { - serviceEndpoints = userSuppliedIdentifier.Discover(relyingParty.WebRequestHandler); + var results = relyingParty.Discover(userSuppliedIdentifier).CacheGeneratedResults(); + + // 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 + var opIdentifiers = results.Where(result => result.ClaimedIdentifier == result.Protocol.ClaimedIdentifierForOPIdentifier).CacheGeneratedResults(); + var claimedIdentifiers = results.Where(result => result.ClaimedIdentifier != result.Protocol.ClaimedIdentifierForOPIdentifier); + serviceEndpoints = opIdentifiers.Any() ? opIdentifiers : claimedIdentifiers; } catch (ProtocolException ex) { Logger.Yadis.ErrorFormat("Error while performing discovery on: \"{0}\": {1}", userSuppliedIdentifier, ex); - serviceEndpoints = EmptyList<ServiceEndpoint>.Instance; + serviceEndpoints = Enumerable.Empty<IdentifierDiscoveryResult>(); } // Filter disallowed endpoints. @@ -380,6 +381,18 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> + /// Creates an instance of <see cref="AuthenticationRequest"/> FOR TESTING PURPOSES ONLY. + /// </summary> + /// <param name="discoveryResult">The discovery result.</param> + /// <param name="realm">The realm.</param> + /// <param name="returnTo">The return to.</param> + /// <param name="rp">The relying party.</param> + /// <returns>The instantiated <see cref="AuthenticationRequest"/>.</returns> + internal static AuthenticationRequest CreateForTest(IdentifierDiscoveryResult discoveryResult, Realm realm, Uri returnTo, OpenIdRelyingParty rp) { + return new AuthenticationRequest(discoveryResult, realm, returnTo, rp); + } + + /// <summary> /// Performs deferred request generation for the <see cref="Create"/> method. /// </summary> /// <param name="userSuppliedIdentifier">The user supplied identifier.</param> @@ -396,7 +409,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// All data validation and cleansing steps must have ALREADY taken place /// before calling this method. /// </remarks> - private static IEnumerable<AuthenticationRequest> CreateInternal(Identifier userSuppliedIdentifier, OpenIdRelyingParty relyingParty, Realm realm, Uri returnToUrl, IEnumerable<ServiceEndpoint> serviceEndpoints, bool createNewAssociationsAsNeeded) { + private static IEnumerable<AuthenticationRequest> CreateInternal(Identifier userSuppliedIdentifier, OpenIdRelyingParty relyingParty, Realm realm, Uri returnToUrl, IEnumerable<IdentifierDiscoveryResult> serviceEndpoints, bool createNewAssociationsAsNeeded) { // DO NOT USE CODE CONTRACTS IN THIS METHOD, since it uses yield return ErrorUtilities.VerifyArgumentNotNull(userSuppliedIdentifier, "userSuppliedIdentifier"); ErrorUtilities.VerifyArgumentNotNull(relyingParty, "relyingParty"); @@ -408,12 +421,12 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { ErrorUtilities.VerifyOperation(!relyingParty.SecuritySettings.RequireAssociation || relyingParty.AssociationManager.HasAssociationStore, OpenIdStrings.AssociationStoreRequired); Logger.Yadis.InfoFormat("Performing discovery on user-supplied identifier: {0}", userSuppliedIdentifier); - IEnumerable<ServiceEndpoint> endpoints = FilterAndSortEndpoints(serviceEndpoints, relyingParty); + IEnumerable<IdentifierDiscoveryResult> endpoints = FilterAndSortEndpoints(serviceEndpoints, relyingParty); // Maintain a list of endpoints that we could not form an association with. // We'll fallback to generating requests to these if the ones we CAN create // an association with run out. - var failedAssociationEndpoints = new List<ServiceEndpoint>(0); + var failedAssociationEndpoints = new List<IdentifierDiscoveryResult>(0); foreach (var endpoint in endpoints) { Logger.OpenId.InfoFormat("Creating authentication request for user supplied Identifier: {0}", userSuppliedIdentifier); @@ -424,7 +437,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { // In some scenarios (like the AJAX control wanting ALL auth requests possible), // we don't want to create associations with every Provider. But we'll use // associations where they are already formed from previous authentications. - association = createNewAssociationsAsNeeded ? relyingParty.AssociationManager.GetOrCreateAssociation(endpoint.ProviderDescription) : relyingParty.AssociationManager.GetExistingAssociation(endpoint.ProviderDescription); + association = createNewAssociationsAsNeeded ? relyingParty.AssociationManager.GetOrCreateAssociation(endpoint) : relyingParty.AssociationManager.GetExistingAssociation(endpoint); if (association == null && createNewAssociationsAsNeeded) { Logger.OpenId.WarnFormat("Failed to create association with {0}. Skipping to next endpoint.", endpoint.ProviderEndpoint); @@ -467,17 +480,17 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <param name="endpoints">The endpoints.</param> /// <param name="relyingParty">The relying party.</param> /// <returns>A filtered and sorted list of endpoints; may be empty if the input was empty or the filter removed all endpoints.</returns> - private static List<ServiceEndpoint> FilterAndSortEndpoints(IEnumerable<ServiceEndpoint> endpoints, OpenIdRelyingParty relyingParty) { + private static List<IdentifierDiscoveryResult> FilterAndSortEndpoints(IEnumerable<IdentifierDiscoveryResult> endpoints, OpenIdRelyingParty relyingParty) { Contract.Requires<ArgumentNullException>(endpoints != null); Contract.Requires<ArgumentNullException>(relyingParty != null); // Construct the endpoints filters based on criteria given by the host web site. - EndpointSelector versionFilter = ep => ((ServiceEndpoint)ep).Protocol.Version >= Protocol.Lookup(relyingParty.SecuritySettings.MinimumRequiredOpenIdVersion).Version; + EndpointSelector versionFilter = ep => ep.Version >= Protocol.Lookup(relyingParty.SecuritySettings.MinimumRequiredOpenIdVersion).Version; EndpointSelector hostingSiteFilter = relyingParty.EndpointFilter ?? (ep => true); bool anyFilteredOut = false; - var filteredEndpoints = new List<IXrdsProviderEndpoint>(); - foreach (ServiceEndpoint endpoint in endpoints) { + var filteredEndpoints = new List<IdentifierDiscoveryResult>(); + foreach (var endpoint in endpoints) { if (versionFilter(endpoint) && hostingSiteFilter(endpoint)) { filteredEndpoints.Add(endpoint); } else { @@ -486,10 +499,10 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } // Sort endpoints so that the first one in the list is the most preferred one. - filteredEndpoints.Sort(relyingParty.EndpointOrder); + filteredEndpoints.OrderBy(ep => ep, relyingParty.EndpointOrder); - List<ServiceEndpoint> endpointList = new List<ServiceEndpoint>(filteredEndpoints.Count); - foreach (ServiceEndpoint endpoint in filteredEndpoints) { + var endpointList = new List<IdentifierDiscoveryResult>(filteredEndpoints.Count); + foreach (var endpoint in filteredEndpoints) { endpointList.Add(endpoint); } @@ -518,20 +531,20 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { SignedResponseRequest request; if (!this.IsExtensionOnly) { - CheckIdRequest authRequest = new CheckIdRequest(this.endpoint.Protocol.Version, this.endpoint.ProviderEndpoint, this.Mode); - authRequest.ClaimedIdentifier = this.endpoint.ClaimedIdentifier; - authRequest.LocalIdentifier = this.endpoint.ProviderLocalIdentifier; + CheckIdRequest authRequest = new CheckIdRequest(this.DiscoveryResult.Version, this.DiscoveryResult.ProviderEndpoint, this.Mode); + authRequest.ClaimedIdentifier = this.DiscoveryResult.ClaimedIdentifier; + authRequest.LocalIdentifier = this.DiscoveryResult.ProviderLocalIdentifier; request = authRequest; } else { - request = new SignedResponseRequest(this.endpoint.Protocol.Version, this.endpoint.ProviderEndpoint, this.Mode); + request = new SignedResponseRequest(this.DiscoveryResult.Version, this.DiscoveryResult.ProviderEndpoint, this.Mode); } request.Realm = this.Realm; request.ReturnTo = this.ReturnToUrl; request.AssociationHandle = association != null ? association.Handle : null; request.SignReturnTo = this.returnToArgsMustBeSigned; request.AddReturnToArguments(this.returnToArgs); - if (this.endpoint.UserSuppliedIdentifier != null) { - request.AddReturnToArguments(UserSuppliedIdentifierParameterName, this.endpoint.UserSuppliedIdentifier.OriginalString); + if (this.DiscoveryResult.UserSuppliedIdentifier != null) { + request.AddReturnToArguments(UserSuppliedIdentifierParameterName, this.DiscoveryResult.UserSuppliedIdentifier.OriginalString); } foreach (IOpenIdMessageExtension extension in this.extensions) { request.Extensions.Add(extension); @@ -548,7 +561,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { Association association = null; switch (this.associationPreference) { case AssociationPreference.IfPossible: - association = this.RelyingParty.AssociationManager.GetOrCreateAssociation(this.endpoint.ProviderDescription); + association = this.RelyingParty.AssociationManager.GetOrCreateAssociation(this.DiscoveryResult); if (association == null) { // Avoid trying to create the association again if the redirecting response // is generated again. @@ -556,7 +569,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } break; case AssociationPreference.IfAlreadyEstablished: - association = this.RelyingParty.AssociationManager.GetExistingAssociation(this.endpoint.ProviderDescription); + association = this.RelyingParty.AssociationManager.GetExistingAssociation(this.DiscoveryResult); break; case AssociationPreference.Never: break; diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequest.cs index 3808c85..65db0bd 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequest.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequest.cs @@ -97,6 +97,12 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { 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> diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequestContract.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequestContract.cs index 41cc4e9..cd36cc7 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequestContract.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequestContract.cs @@ -32,11 +32,16 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } Realm IAuthenticationRequest.Realm { - get { throw new NotImplementedException(); } + get { + Contract.Ensures(Contract.Result<Realm>() != null); + throw new NotImplementedException(); + } } Identifier IAuthenticationRequest.ClaimedIdentifier { - get { throw new NotImplementedException(); } + get { + throw new NotImplementedException(); + } } bool IAuthenticationRequest.IsDirectedIdentity { @@ -54,7 +59,17 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } IProviderEndpoint IAuthenticationRequest.Provider { - get { throw new NotImplementedException(); } + 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) { diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IProviderEndpoint.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IProviderEndpoint.cs index a90ddd4..5d8918d 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/IProviderEndpoint.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/IProviderEndpoint.cs @@ -6,6 +6,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { using System; + using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Globalization; @@ -30,6 +31,9 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <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> @@ -45,6 +49,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// 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> @@ -59,6 +64,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// 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); } @@ -67,20 +73,32 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </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 { throw new NotImplementedException(); } + 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 { throw new NotImplementedException(); } + get { + Contract.Ensures(Contract.Result<Uri>() != null); + throw new System.NotImplementedException(); + } } /// <summary> @@ -118,7 +136,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { bool IProviderEndpoint.IsExtensionSupported(Type extensionType) { Contract.Requires<ArgumentNullException>(extensionType != null); Contract.Requires<ArgumentException>(typeof(IOpenIdMessageExtension).IsAssignableFrom(extensionType)); - ////ErrorUtilities.VerifyArgument(typeof(IOpenIdMessageExtension).IsAssignableFrom(extensionType), string.Format(CultureInfo.CurrentCulture, OpenIdStrings.TypeMustImplementX, typeof(IOpenIdMessageExtension).FullName)); throw new NotImplementedException(); } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IXrdsProviderEndpoint.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IXrdsProviderEndpoint.cs deleted file mode 100644 index 89b4ef0..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/IXrdsProviderEndpoint.cs +++ /dev/null @@ -1,41 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IXrdsProviderEndpoint.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - - /// <summary> - /// An <see cref="IProviderEndpoint"/> interface with additional members for use - /// in sorting for most preferred endpoint. - /// </summary> - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Xrds", Justification = "Xrds is an acronym.")] - [ContractClass(typeof(IXrdsProviderEndpointContract))] - public interface IXrdsProviderEndpoint : IProviderEndpoint { - /// <summary> - /// Gets the priority associated with this service that may have been given - /// in the XRDS document. - /// </summary> - int? ServicePriority { get; } - - /// <summary> - /// Gets the priority associated with the service endpoint URL. - /// </summary> - /// <remarks> - /// When sorting by priority, this property should be considered second after - /// <see cref="ServicePriority"/>. - /// </remarks> - int? UriPriority { get; } - - /// <summary> - /// Checks for the presence of a given Type URI in an XRDS service. - /// </summary> - /// <param name="typeUri">The type URI to check for.</param> - /// <returns><c>true</c> if the service type uri is present; <c>false</c> otherwise.</returns> - bool IsTypeUriPresent(string typeUri); - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IXrdsProviderEndpointContract.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IXrdsProviderEndpointContract.cs deleted file mode 100644 index e0e2b0b..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/IXrdsProviderEndpointContract.cs +++ /dev/null @@ -1,59 +0,0 @@ -// <auto-generated /> - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Diagnostics.CodeAnalysis; - using System.Diagnostics.Contracts; - using System.Globalization; - using DotNetOpenAuth.OpenId.Messages; - - [ContractClassFor(typeof(IXrdsProviderEndpoint))] - internal abstract class IXrdsProviderEndpointContract : IXrdsProviderEndpoint { - #region IXrdsProviderEndpoint Properties - - int? IXrdsProviderEndpoint.ServicePriority { - get { throw new System.NotImplementedException(); } - } - - int? IXrdsProviderEndpoint.UriPriority { - get { throw new System.NotImplementedException(); } - } - - #endregion - - #region IProviderEndpoint Properties - - Version IProviderEndpoint.Version { - get { throw new System.NotImplementedException(); } - } - - Uri IProviderEndpoint.Uri { - get { throw new System.NotImplementedException(); } - } - - #endregion - - #region IXrdsProviderEndpoint Methods - - bool IXrdsProviderEndpoint.IsTypeUriPresent(string typeUri) { - throw new System.NotImplementedException(); - } - - #endregion - - #region IProviderEndpoint Methods - - bool IProviderEndpoint.IsExtensionSupported<T>() { - throw new System.NotImplementedException(); - } - - bool IProviderEndpoint.IsExtensionSupported(System.Type extensionType) { - Contract.Requires<ArgumentNullException>(extensionType != null); - Contract.Requires<ArgumentException>(typeof(IOpenIdMessageExtension).IsAssignableFrom(extensionType)); - ////ErrorUtilities.VerifyArgument(typeof(IOpenIdMessageExtension).IsAssignableFrom(extensionType), string.Format(CultureInfo.CurrentCulture, OpenIdStrings.TypeMustImplementX, typeof(IOpenIdMessageExtension).FullName)); - throw new System.NotImplementedException(); - } - - #endregion - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs index bdf6c5b..76b0f10 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs @@ -30,7 +30,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <c>True</c> if the endpoint should be considered. /// <c>False</c> to remove it from the pool of acceptable providers. /// </returns> - public delegate bool EndpointSelector(IXrdsProviderEndpoint endpoint); + public delegate bool EndpointSelector(IProviderEndpoint endpoint); /// <summary> /// Provides the programmatic facilities to act as an OpenId consumer. @@ -49,6 +49,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { private readonly ObservableCollection<IRelyingPartyBehavior> behaviors = new ObservableCollection<IRelyingPartyBehavior>(); /// <summary> + /// Backing field for the <see cref="DiscoveryServices"/> property. + /// </summary> + private readonly IList<IIdentifierDiscoveryService> discoveryServices = new List<IIdentifierDiscoveryService>(2); + + /// <summary> /// Backing field for the <see cref="SecuritySettings"/> property. /// </summary> private RelyingPartySecuritySettings securitySettings; @@ -56,7 +61,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// Backing store for the <see cref="EndpointOrder"/> property. /// </summary> - private Comparison<IXrdsProviderEndpoint> endpointOrder = DefaultEndpointOrder; + private Comparison<IdentifierDiscoveryResult> endpointOrder = DefaultEndpointOrder; /// <summary> /// Backing field for the <see cref="Channel"/> property. @@ -90,6 +95,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { Contract.Requires<ArgumentException>(associationStore == null || nonceStore != null, OpenIdStrings.AssociationStoreRequiresNonceStore); this.securitySettings = DotNetOpenAuthSection.Configuration.OpenId.RelyingParty.SecuritySettings.CreateSecuritySettings(); + + foreach (var discoveryService in DotNetOpenAuthSection.Configuration.OpenId.RelyingParty.DiscoveryServices.CreateInstances(true)) { + this.discoveryServices.Add(discoveryService); + } + this.behaviors.CollectionChanged += this.OnBehaviorsChanged; foreach (var behavior in DotNetOpenAuthSection.Configuration.OpenId.RelyingParty.Behaviors.CreateInstances(false)) { this.behaviors.Add(behavior); @@ -114,8 +124,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// Endpoints lacking any priority value are sorted to the end of the list. /// </remarks> [EditorBrowsable(EditorBrowsableState.Advanced)] - public static Comparison<IXrdsProviderEndpoint> DefaultEndpointOrder { - get { return ServiceEndpoint.EndpointOrder; } + public static Comparison<IdentifierDiscoveryResult> DefaultEndpointOrder { + get { return IdentifierDiscoveryResult.EndpointOrder; } } /// <summary> @@ -197,7 +207,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// can be set to the value of <see cref="DefaultEndpointOrder"/>. /// </remarks> [EditorBrowsable(EditorBrowsableState.Advanced)] - public Comparison<IXrdsProviderEndpoint> EndpointOrder { + public Comparison<IdentifierDiscoveryResult> EndpointOrder { get { return this.endpointOrder; } @@ -227,6 +237,13 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> + /// Gets the list of services that can perform discovery on identifiers given to this relying party. + /// </summary> + internal IList<IIdentifierDiscoveryService> DiscoveryServices { + get { return this.discoveryServices; } + } + + /// <summary> /// Gets a value indicating whether this Relying Party can sign its return_to /// parameter in outgoing authentication requests. /// </summary> @@ -569,6 +586,29 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <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> + internal IEnumerable<IdentifierDiscoveryResult> Discover(Identifier identifier) { + Contract.Requires<ArgumentNullException>(identifier != null); + 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.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; + } + } + + return results; + } + + /// <summary> /// Releases unmanaged and - optionally - managed resources /// </summary> /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs index 44a5b41..8a33f3b 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs @@ -557,7 +557,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { // If the ReturnToUrl was explicitly set, we'll need to reset our first parameter if (string.IsNullOrEmpty(HttpUtility.ParseQueryString(req.ReturnToUrl.Query)[AuthenticationRequest.UserSuppliedIdentifierParameterName])) { - Identifier userSuppliedIdentifier = ((AuthenticationRequest)req).Endpoint.UserSuppliedIdentifier; + Identifier userSuppliedIdentifier = ((AuthenticationRequest)req).DiscoveryResult.UserSuppliedIdentifier; req.SetUntrustedCallbackArgument(AuthenticationRequest.UserSuppliedIdentifierParameterName, userSuppliedIdentifier.OriginalString); } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs index 4cf2648..578d40d 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs @@ -793,7 +793,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { case PopupBehavior.Always: return true; case PopupBehavior.IfProviderSupported: - return request.Provider.IsExtensionSupported<UIRequest>(); + return request.DiscoveryResult.IsExtensionSupported<UIRequest>(); default: throw ErrorUtilities.ThrowInternal("Unexpected value for Popup property."); } @@ -920,7 +920,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { // Inform ourselves in return_to that we're in a popup. req.SetUntrustedCallbackArgument(UIPopupCallbackKey, "1"); - if (req.Provider.IsExtensionSupported<UIRequest>()) { + if (req.DiscoveryResult.IsExtensionSupported<UIRequest>()) { // Inform the OP that we'll be using a popup window consistent with the UI extension. req.AddExtension(new UIRequest()); diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs index 1bc306c..8a62d68 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs @@ -26,7 +26,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// Information about the OP endpoint that issued this assertion. /// </summary> - private readonly ProviderEndpointDescription provider; + private readonly IProviderEndpoint provider; /// <summary> /// Initializes a new instance of the <see cref="PositiveAnonymousResponse"/> class. diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs index e809205..44f01bc 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs @@ -28,7 +28,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { : base(response) { Contract.Requires<ArgumentNullException>(relyingParty != null); - this.Endpoint = ServiceEndpoint.CreateForClaimedIdentifier( + this.Endpoint = IdentifierDiscoveryResult.CreateForClaimedIdentifier( this.Response.ClaimedIdentifier, this.Response.GetReturnToArgument(AuthenticationRequest.UserSuppliedIdentifierParameterName), this.Response.LocalIdentifier, @@ -112,7 +112,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// the claimed identifier to avoid a Provider asserting an Identifier /// for which it has no authority. /// </remarks> - internal ServiceEndpoint Endpoint { get; private set; } + internal IdentifierDiscoveryResult Endpoint { get; private set; } /// <summary> /// Gets the positive assertion response message. @@ -153,7 +153,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { // is signed by the RP before it's considered reliable. In 1.x stateless mode, this RP // doesn't (and can't) sign its own return_to URL, so its cached discovery information // is merely a hint that must be verified by performing discovery again here. - var discoveryResults = claimedId.Discover(relyingParty.WebRequestHandler); + var discoveryResults = relyingParty.Discover(claimedId); ErrorUtilities.VerifyProtocol( discoveryResults.Contains(this.Endpoint), OpenIdStrings.IssuedAssertionFailsIdentifierDiscovery, diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs index ff29498..00213d8 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs @@ -115,7 +115,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> /// <param name="endpoints">The endpoints discovered on an Identifier.</param> /// <returns>A sequence of endpoints that satisfy all security requirements.</returns> - internal IEnumerable<ServiceEndpoint> FilterEndpoints(IEnumerable<ServiceEndpoint> endpoints) { + 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/RelyingParty/SimpleXrdsProviderEndpoint.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/SimpleXrdsProviderEndpoint.cs index 912b8f4..678f69a 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/SimpleXrdsProviderEndpoint.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/SimpleXrdsProviderEndpoint.cs @@ -6,6 +6,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { using System; + using System.Collections.ObjectModel; + using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.Messages; /// <summary> @@ -13,7 +15,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// assertions (particularly unsolicited ones) are received from OP endpoints that /// are deemed permissible by the host RP. /// </summary> - internal class SimpleXrdsProviderEndpoint : IXrdsProviderEndpoint { + internal class SimpleXrdsProviderEndpoint : IProviderEndpoint { /// <summary> /// Initializes a new instance of the <see cref="SimpleXrdsProviderEndpoint"/> class. /// </summary> @@ -23,29 +25,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { this.Version = positiveAssertion.Version; } - #region IXrdsProviderEndpoint Properties - - /// <summary> - /// Gets the priority associated with this service that may have been given - /// in the XRDS document. - /// </summary> - public int? ServicePriority { - get { return null; } - } - - /// <summary> - /// Gets the priority associated with the service endpoint URL. - /// </summary> - /// <remarks> - /// When sorting by priority, this property should be considered second after - /// <see cref="ServicePriority"/>. - /// </remarks> - public int? UriPriority { - get { return null; } - } - - #endregion - #region IProviderEndpoint Members /// <summary> @@ -56,7 +35,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// Gets the URL that the OpenID Provider receives authentication requests at. /// </summary> - /// <value></value> public Uri Uri { get; private set; } /// <summary> @@ -73,8 +51,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// 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<T>() where T : DotNetOpenAuth.OpenId.Messages.IOpenIdMessageExtension, new() { - throw new NotSupportedException(); + bool IProviderEndpoint.IsExtensionSupported<T>() { + throw new NotImplementedException(); } /// <summary> @@ -91,23 +69,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// 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) { - throw new NotSupportedException(); - } - - #endregion - - #region IXrdsProviderEndpoint Methods - - /// <summary> - /// Checks for the presence of a given Type URI in an XRDS service. - /// </summary> - /// <param name="typeUri">The type URI to check for.</param> - /// <returns> - /// <c>true</c> if the service type uri is present; <c>false</c> otherwise. - /// </returns> - public bool IsTypeUriPresent(string typeUri) { - throw new NotSupportedException(); + bool IProviderEndpoint.IsExtensionSupported(Type extensionType) { + throw new NotImplementedException(); } #endregion diff --git a/src/DotNetOpenAuth/OpenId/UriDiscoveryService.cs b/src/DotNetOpenAuth/OpenId/UriDiscoveryService.cs new file mode 100644 index 0000000..b42cf67 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/UriDiscoveryService.cs @@ -0,0 +1,138 @@ +//----------------------------------------------------------------------- +// <copyright file="UriDiscoveryService.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. 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.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) { + 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/UriIdentifier.cs b/src/DotNetOpenAuth/OpenId/UriIdentifier.cs index 28d8b37..0b7a0e3 100644 --- a/src/DotNetOpenAuth/OpenId/UriIdentifier.cs +++ b/src/DotNetOpenAuth/OpenId/UriIdentifier.cs @@ -207,54 +207,6 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> - /// Performs discovery on the Identifier. - /// </summary> - /// <param name="requestHandler">The web request handler to use for discovery.</param> - /// <returns> - /// An initialized structure containing the discovered provider endpoint information. - /// </returns> - internal override IEnumerable<ServiceEndpoint> Discover(IDirectWebRequestHandler requestHandler) { - List<ServiceEndpoint> endpoints = new List<ServiceEndpoint>(); - - // Attempt YADIS discovery - DiscoveryResult yadisResult = Yadis.Discover(requestHandler, this, IsDiscoverySecureEndToEnd); - if (yadisResult != null) { - if (yadisResult.IsXrds) { - try { - XrdsDocument xrds = new XrdsDocument(yadisResult.ResponseText); - var xrdsEndpoints = xrds.CreateServiceEndpoints(yadisResult.NormalizedUri, this); - - // Filter out insecure endpoints if high security is required. - if (IsDiscoverySecureEndToEnd) { - xrdsEndpoints = xrdsEndpoints.Where(se => se.IsSecure); - } - 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) { - var htmlEndpoints = new List<ServiceEndpoint>(DiscoverFromHtml(yadisResult.NormalizedUri, this, 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 => !IsDiscoverySecureEndToEnd || ep.IsSecure)); - 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; - } - - /// <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. @@ -318,54 +270,6 @@ namespace DotNetOpenAuth.OpenId { } /// <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<ServiceEndpoint> 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 ServiceEndpoint.CreateForClaimedIdentifier( - claimedIdentifier, - userSuppliedIdentifier, - providerLocalIdentifier, - new ProviderEndpointDescription(providerEndpoint, typeURIs), - (int?)null, - (int?)null); - } - } - } - - /// <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> diff --git a/src/DotNetOpenAuth/OpenId/XriDiscoveryProxyService.cs b/src/DotNetOpenAuth/OpenId/XriDiscoveryProxyService.cs new file mode 100644 index 0000000..2192445 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/XriDiscoveryProxyService.cs @@ -0,0 +1,106 @@ +//----------------------------------------------------------------------- +// <copyright file="XriDiscoveryProxyService.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. 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.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> + 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).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) { + Contract.Requires<ArgumentNullException>(identifier != null); + Contract.Requires<ArgumentNullException>(requestHandler != null); + 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(DotNetOpenAuthSection.Configuration.OpenId.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, + DotNetOpenAuthSection.Configuration.OpenId.XriResolver.Proxy.Name)); + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/XriIdentifier.cs b/src/DotNetOpenAuth/OpenId/XriIdentifier.cs index 9bcb9cf..6af41fa 100644 --- a/src/DotNetOpenAuth/OpenId/XriIdentifier.cs +++ b/src/DotNetOpenAuth/OpenId/XriIdentifier.cs @@ -35,23 +35,6 @@ namespace DotNetOpenAuth.OpenId { private const string XriScheme = "xri://"; /// <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> - /// The XRI proxy resolver to use for finding XRDS documents from an XRI. - /// </summary> - private readonly string xriResolverProxy; - - /// <summary> /// Backing store for the <see cref="CanonicalXri"/> property. /// </summary> private readonly string canonicalXri; @@ -79,12 +62,6 @@ namespace DotNetOpenAuth.OpenId { Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(xri)); Contract.Requires<FormatException>(IsValidXri(xri), OpenIdStrings.InvalidXri); Contract.Assume(xri != null); // Proven by IsValidXri - this.xriResolverProxy = XriResolverProxyTemplate; - if (requireSsl) { - // Indicate to xri.net that we require SSL to be used for delegated resolution - // of community i-names. - this.xriResolverProxy += ";https=true"; - } this.OriginalXri = xri; this.canonicalXri = CanonicalizeXri(xri); } @@ -105,21 +82,6 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> - /// Gets the URL from which this XRI's XRDS document may be downloaded. - /// </summary> - private Uri XrdsUrl { - get { - ErrorUtilities.VerifyProtocol(DotNetOpenAuthSection.Configuration.OpenId.XriResolver.Enabled, OpenIdStrings.XriResolutionDisabled); - return new Uri( - string.Format( - CultureInfo.InvariantCulture, - this.xriResolverProxy, - this, - DotNetOpenAuthSection.Configuration.OpenId.XriResolver.Proxy.Name)); - } - } - - /// <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> @@ -178,30 +140,6 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> - /// Performs discovery on the Identifier. - /// </summary> - /// <param name="requestHandler">The web request handler to use for discovery.</param> - /// <returns> - /// An initialized structure containing the discovered provider endpoint information. - /// </returns> - internal override IEnumerable<ServiceEndpoint> Discover(IDirectWebRequestHandler requestHandler) { - return this.DownloadXrds(requestHandler).CreateServiceEndpoints(this); - } - - /// <summary> - /// Performs discovery on THIS identifier, but generates <see cref="ServiceEndpoint"/> - /// instances that treat another given identifier as the user-supplied identifier. - /// </summary> - /// <param name="requestHandler">The request handler to use in discovery.</param> - /// <param name="userSuppliedIdentifier">The user supplied identifier, which may differ from this XRI instance due to multiple discovery steps.</param> - /// <returns>A list of service endpoints offered for this identifier.</returns> - internal IEnumerable<ServiceEndpoint> Discover(IDirectWebRequestHandler requestHandler, XriIdentifier userSuppliedIdentifier) { - Contract.Requires<ArgumentNullException>(requestHandler != null); - Contract.Requires<ArgumentNullException>(userSuppliedIdentifier != null); - return this.DownloadXrds(requestHandler).CreateServiceEndpoints(userSuppliedIdentifier); - } - - /// <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. @@ -253,22 +191,6 @@ namespace DotNetOpenAuth.OpenId { return xri; } - /// <summary> - /// Downloads the XRDS document for this XRI. - /// </summary> - /// <param name="requestHandler">The request handler.</param> - /// <returns>The XRDS document.</returns> - private XrdsDocument DownloadXrds(IDirectWebRequestHandler requestHandler) { - Contract.Requires<ArgumentNullException>(requestHandler != null); - Contract.Ensures(Contract.Result<XrdsDocument>() != null); - XrdsDocument doc; - using (var xrdsResponse = Yadis.Request(requestHandler, this.XrdsUrl, this.IsDiscoverySecureEndToEnd)) { - doc = new XrdsDocument(XmlReader.Create(xrdsResponse.ResponseStream)); - } - ErrorUtilities.VerifyProtocol(doc.IsXrdResolutionSuccessful, OpenIdStrings.XriResolutionFailed); - return doc; - } - #if CONTRACTS_FULL /// <summary> /// Verifies conditions that should be true for any valid state of this object. @@ -277,7 +199,6 @@ namespace DotNetOpenAuth.OpenId { [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] [ContractInvariantMethod] private void ObjectInvariant() { - Contract.Invariant(this.xriResolverProxy != null); Contract.Invariant(this.canonicalXri != null); } #endif |