summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenAuth')
-rw-r--r--src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd22
-rw-r--r--src/DotNetOpenAuth/Configuration/OpenIdRelyingPartyElement.cs34
-rw-r--r--src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs19
-rw-r--r--src/DotNetOpenAuth/Configuration/ReportingElement.cs2
-rw-r--r--src/DotNetOpenAuth/DotNetOpenAuth.csproj39
-rw-r--r--src/DotNetOpenAuth/DotNetOpenAuth.icobin0 -> 1150 bytes
-rw-r--r--src/DotNetOpenAuth/Messaging/Channel.cs26
-rw-r--r--src/DotNetOpenAuth/Messaging/IMessageWithBinaryData.cs152
-rw-r--r--src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs9
-rw-r--r--src/DotNetOpenAuth/Messaging/MessagingStrings.resx3
-rw-r--r--src/DotNetOpenAuth/Messaging/MessagingUtilities.cs67
-rw-r--r--src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs27
-rw-r--r--src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs22
-rw-r--r--src/DotNetOpenAuth/OAuth/ConsumerBase.cs22
-rw-r--r--src/DotNetOpenAuth/OAuth/Messages/AccessProtectedResourceRequest.cs27
-rw-r--r--src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs29
-rw-r--r--src/DotNetOpenAuth/OAuth/OAuthStrings.resx3
-rw-r--r--src/DotNetOpenAuth/OpenId/Extensions/ExtensionsInteropHelper.cs5
-rw-r--r--src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs3
-rw-r--r--src/DotNetOpenAuth/OpenId/HostMetaDiscoveryService.cs504
-rw-r--r--src/DotNetOpenAuth/OpenId/IIdentifierDiscoveryService.cs61
-rw-r--r--src/DotNetOpenAuth/OpenId/Identifier.cs10
-rw-r--r--src/DotNetOpenAuth/OpenId/IdentifierContract.cs13
-rw-r--r--src/DotNetOpenAuth/OpenId/IdentifierDiscoveryResult.cs (renamed from src/DotNetOpenAuth/OpenId/RelyingParty/ServiceEndpoint.cs)387
-rw-r--r--src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs14
-rw-r--r--src/DotNetOpenAuth/OpenId/NoDiscoveryIdentifier.cs11
-rw-r--r--src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs9
-rw-r--r--src/DotNetOpenAuth/OpenId/OpenIdStrings.resx3
-rw-r--r--src/DotNetOpenAuth/OpenId/OpenIdUtilities.cs3
-rw-r--r--src/DotNetOpenAuth/OpenId/OpenIdXrdsHelper.cs75
-rw-r--r--src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs60
-rw-r--r--src/DotNetOpenAuth/OpenId/ProviderEndpointDescription.cs99
-rw-r--r--src/DotNetOpenAuth/OpenId/Realm.cs33
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/AssociationManager.cs18
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs100
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequest.cs6
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequestContract.cs21
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/IProviderEndpoint.cs23
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/IXrdsProviderEndpoint.cs41
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/IXrdsProviderEndpointContract.cs59
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs79
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs2
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs4
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs2
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs6
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs17
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/SimpleXrdsProviderEndpoint.cs51
-rw-r--r--src/DotNetOpenAuth/OpenId/UriDiscoveryService.cs138
-rw-r--r--src/DotNetOpenAuth/OpenId/UriIdentifier.cs96
-rw-r--r--src/DotNetOpenAuth/OpenId/XriDiscoveryProxyService.cs108
-rw-r--r--src/DotNetOpenAuth/OpenId/XriIdentifier.cs79
-rw-r--r--src/DotNetOpenAuth/Xrds/XrdElement.cs2
-rw-r--r--src/DotNetOpenAuth/Xrds/XrdsDocument.cs12
-rw-r--r--src/DotNetOpenAuth/Xrds/XrdsNode.cs6
54 files changed, 1822 insertions, 841 deletions
diff --git a/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd b/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd
index a462f83..18e1c5c 100644
--- a/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd
+++ b/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd
@@ -319,6 +319,7 @@
</xs:documentation>
</xs:annotation>
</xs:attribute>
+ <xs:attribute name="allowDualPurposeIdentifiers" type="xs:boolean" />
</xs:complexType>
</xs:element>
<xs:element name="behaviors">
@@ -361,6 +362,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:annotation>
<xs:documentation>
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 &lt;behaviors&gt; sub-element.
+ /// The name of the &lt;behaviors&gt; sub-element.
/// </summary>
private const string BehaviorsElementName = "behaviors";
/// <summary>
+ /// The name of the &lt;discoveryServices&gt; sub-element.
+ /// </summary>
+ private const string DiscoveryServicesElementName = "discoveryServices";
+
+ /// <summary>
+ /// The 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/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs b/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs
index d10d9bd..0d8b768 100644
--- a/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs
+++ b/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs
@@ -66,6 +66,11 @@ namespace DotNetOpenAuth.Configuration {
private const string PrivateSecretMaximumAgeConfigName = "privateSecretMaximumAge";
/// <summary>
+ /// Gets the name of the @allowDualPurposeIdentifiers attribute.
+ /// </summary>
+ private const string AllowDualPurposeIdentifiersConfigName = "allowDualPurposeIdentifiers";
+
+ /// <summary>
/// Initializes a new instance of the <see cref="OpenIdRelyingPartySecuritySettingsElement"/> class.
/// </summary>
public OpenIdRelyingPartySecuritySettingsElement() {
@@ -183,6 +188,19 @@ namespace DotNetOpenAuth.Configuration {
}
/// <summary>
+ /// Gets or sets a value indicating whether identifiers that are both OP Identifiers and Claimed Identifiers
+ /// should ever be recognized as claimed identifiers.
+ /// </summary>
+ /// <value>
+ /// The default value is <c>false</c>, per the OpenID 2.0 spec.
+ /// </value>
+ [ConfigurationProperty(AllowDualPurposeIdentifiersConfigName, DefaultValue = false)]
+ public bool AllowDualPurposeIdentifiers {
+ get { return (bool)this[AllowDualPurposeIdentifiersConfigName]; }
+ set { this[AllowDualPurposeIdentifiersConfigName] = value; }
+ }
+
+ /// <summary>
/// Initializes a programmatically manipulatable bag of these security settings with the settings from the config file.
/// </summary>
/// <returns>The newly created security settings object.</returns>
@@ -200,6 +218,7 @@ namespace DotNetOpenAuth.Configuration {
settings.RejectUnsolicitedAssertions = this.RejectUnsolicitedAssertions;
settings.RejectDelegatingIdentifiers = this.RejectDelegatingIdentifiers;
settings.IgnoreUnsignedExtensions = this.IgnoreUnsignedExtensions;
+ settings.AllowDualPurposeIdentifiers = this.AllowDualPurposeIdentifiers;
return settings;
}
diff --git a/src/DotNetOpenAuth/Configuration/ReportingElement.cs b/src/DotNetOpenAuth/Configuration/ReportingElement.cs
index ab1437f..2374448 100644
--- a/src/DotNetOpenAuth/Configuration/ReportingElement.cs
+++ b/src/DotNetOpenAuth/Configuration/ReportingElement.cs
@@ -69,7 +69,7 @@ namespace DotNetOpenAuth.Configuration {
/// Gets or sets a value indicating whether this reporting is enabled.
/// </summary>
/// <value><c>true</c> if enabled; otherwise, <c>false</c>.</value>
- [ConfigurationProperty(EnabledAttributeName, DefaultValue = false)]
+ [ConfigurationProperty(EnabledAttributeName, DefaultValue = true)]
internal bool Enabled {
get { return (bool)this[EnabledAttributeName]; }
set { this[EnabledAttributeName] = value; }
diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj
index f904c45..f06ab49 100644
--- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj
+++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj
@@ -1,8 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
+ <ProjectRoot Condition="'$(ProjectRoot)' == ''">$(MSBuildProjectDirectory)\..\..\</ProjectRoot>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ </PropertyGroup>
+ <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.props" />
+ <PropertyGroup>
<ProductVersion>9.0.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}</ProjectGuid>
@@ -17,17 +21,17 @@ Copyright (c) 2009, Andrew Arnott. All rights reserved.
Code licensed under the Ms-PL License:
http://opensource.org/licenses/ms-pl.html
</StandardCopyright>
+ <ApplicationIcon>DotNetOpenAuth.ico</ApplicationIcon>
+ <DocumentationFile>$(OutputPath)DotNetOpenAuth.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
- <OutputPath>..\..\bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
- <DocumentationFile>..\..\bin\Debug\DotNetOpenAuth.xml</DocumentationFile>
<RunCodeAnalysis>false</RunCodeAnalysis>
<CodeAnalysisRules>-Microsoft.Design#CA1054;-Microsoft.Design#CA1056;-Microsoft.Design#CA1055</CodeAnalysisRules>
<CodeContractsEnableRuntimeChecking>True</CodeContractsEnableRuntimeChecking>
@@ -62,12 +66,10 @@ http://opensource.org/licenses/ms-pl.html
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
- <OutputPath>..\..\bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
- <DocumentationFile>..\..\bin\Release\DotNetOpenAuth.xml</DocumentationFile>
<RunCodeAnalysis>true</RunCodeAnalysis>
<CodeAnalysisRules>-Microsoft.Design#CA1054;-Microsoft.Design#CA1056;-Microsoft.Design#CA1055</CodeAnalysisRules>
<CodeContractsEnableRuntimeChecking>True</CodeContractsEnableRuntimeChecking>
@@ -99,14 +101,9 @@ http://opensource.org/licenses/ms-pl.html
<CodeContractsRedundantAssumptions>False</CodeContractsRedundantAssumptions>
<CodeContractsReferenceAssembly>Build</CodeContractsReferenceAssembly>
</PropertyGroup>
- <PropertyGroup>
- <SignAssembly>true</SignAssembly>
- </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'CodeAnalysis|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
- <OutputPath>..\..\bin\CodeAnalysis\</OutputPath>
<DefineConstants>$(DefineConstants);CONTRACTS_FULL;DEBUG;TRACE</DefineConstants>
- <DocumentationFile>..\..\bin\CodeAnalysis\DotNetOpenAuth.xml</DocumentationFile>
<DebugType>full</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<CodeAnalysisRules>-Microsoft.Design#CA1054;-Microsoft.Design#CA1056;-Microsoft.Design#CA1055</CodeAnalysisRules>
@@ -302,6 +299,7 @@ http://opensource.org/licenses/ms-pl.html
<Compile Include="OAuth\ConsumerSecuritySettings.cs" />
<Compile Include="OAuth\DesktopConsumer.cs" />
<Compile Include="GlobalSuppressions.cs" />
+ <Compile Include="Messaging\IMessageWithBinaryData.cs" />
<Compile Include="OAuth\Messages\ITokenSecretContainingMessage.cs" />
<Compile Include="Messaging\ChannelEventArgs.cs" />
<Compile Include="Messaging\ITamperProtectionChannelBindingElement.cs" />
@@ -429,12 +427,14 @@ http://opensource.org/licenses/ms-pl.html
<Compile Include="OpenId\Extensions\UI\UIUtilities.cs" />
<Compile Include="OpenId\Extensions\UI\UIModes.cs" />
<Compile Include="OpenId\Extensions\UI\UIRequest.cs" />
+ <Compile Include="OpenId\HostMetaDiscoveryService.cs" />
<Compile Include="OpenId\Identifier.cs" />
<Compile Include="OpenId\IdentifierContract.cs" />
<Compile Include="OpenId\Extensions\ExtensionsInteropHelper.cs" />
<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" />
@@ -501,6 +501,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" />
@@ -508,7 +509,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" />
@@ -525,9 +525,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>
@@ -542,7 +540,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" />
@@ -550,7 +548,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" />
@@ -681,14 +681,9 @@ http://opensource.org/licenses/ms-pl.html
<ItemGroup>
<EmbeddedResource Include="OpenId\RelyingParty\OpenIdSelector.css" />
</ItemGroup>
+ <ItemGroup>
+ <Content Include="DotNetOpenAuth.ico" />
+ </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
- <Import Project="..\..\tools\DotNetOpenAuth.Versioning.targets" />
- <Import Project="..\..\tools\JavascriptPacker.targets" />
- <PropertyGroup>
- <CompileDependsOn>$(CompileDependsOn);CheckForCodeContracts</CompileDependsOn>
- </PropertyGroup>
- <Target Name="CheckForCodeContracts">
- <Error Condition=" '$(CodeContractsImported)' != 'true' "
- Text="This project requires Code Contracts. Please install from: http://msdn.microsoft.com/en-us/devlabs/dd491992.aspx"/>
- </Target>
+ <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.targets" />
</Project>
diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.ico b/src/DotNetOpenAuth/DotNetOpenAuth.ico
new file mode 100644
index 0000000..e227dbe
--- /dev/null
+++ b/src/DotNetOpenAuth/DotNetOpenAuth.ico
Binary files differ
diff --git a/src/DotNetOpenAuth/Messaging/Channel.cs b/src/DotNetOpenAuth/Messaging/Channel.cs
index 42b932e..1bddf02 100644
--- a/src/DotNetOpenAuth/Messaging/Channel.cs
+++ b/src/DotNetOpenAuth/Messaging/Channel.cs
@@ -864,7 +864,18 @@ namespace DotNetOpenAuth.Messaging {
HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(requestMessage.Recipient);
httpRequest.CachePolicy = this.CachePolicy;
httpRequest.Method = "POST";
- this.SendParametersInEntity(httpRequest, fields);
+
+ var requestMessageWithBinaryData = requestMessage as IMessageWithBinaryData;
+ if (requestMessageWithBinaryData != null && requestMessageWithBinaryData.SendAsMultipart) {
+ var multiPartFields = new List<MultipartPostPart>(requestMessageWithBinaryData.BinaryData);
+
+ // When sending multi-part, all data gets send as multi-part -- even the non-binary data.
+ multiPartFields.AddRange(fields.Select(field => MultipartPostPart.CreateFormPart(field.Key, field.Value)));
+ this.SendParametersInEntityAsMultipart(httpRequest, multiPartFields);
+ } else {
+ ErrorUtilities.VerifyProtocol(requestMessageWithBinaryData == null || requestMessageWithBinaryData.BinaryData.Count == 0, MessagingStrings.BinaryDataRequiresMultipart);
+ this.SendParametersInEntity(httpRequest, fields);
+ }
return httpRequest;
}
@@ -940,6 +951,19 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
+ /// Sends the given parameters in the entity stream of an HTTP request in multi-part format.
+ /// </summary>
+ /// <param name="httpRequest">The HTTP request.</param>
+ /// <param name="fields">The parameters to send.</param>
+ /// <remarks>
+ /// This method calls <see cref="HttpWebRequest.GetRequestStream()"/> and closes
+ /// the request stream, but does not call <see cref="HttpWebRequest.GetResponse"/>.
+ /// </remarks>
+ protected void SendParametersInEntityAsMultipart(HttpWebRequest httpRequest, IEnumerable<MultipartPostPart> fields) {
+ httpRequest.PostMultipartNoGetResponse(this.WebRequestHandler, fields);
+ }
+
+ /// <summary>
/// Verifies the integrity and applicability of an incoming message.
/// </summary>
/// <param name="message">The message just received.</param>
diff --git a/src/DotNetOpenAuth/Messaging/IMessageWithBinaryData.cs b/src/DotNetOpenAuth/Messaging/IMessageWithBinaryData.cs
new file mode 100644
index 0000000..f411cf5
--- /dev/null
+++ b/src/DotNetOpenAuth/Messaging/IMessageWithBinaryData.cs
@@ -0,0 +1,152 @@
+//-----------------------------------------------------------------------
+// <copyright file="IMessageWithBinaryData.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.Messaging {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Text;
+
+ /// <summary>
+ /// The interface that classes must implement to be serialized/deserialized
+ /// as protocol or extension messages that uses POST multi-part data for binary content.
+ /// </summary>
+ [ContractClass(typeof(IMessageWithBinaryDataContract))]
+ public interface IMessageWithBinaryData : IDirectedProtocolMessage {
+ /// <summary>
+ /// Gets the parts of the message that carry binary data.
+ /// </summary>
+ /// <value>A list of parts. Never null.</value>
+ IList<MultipartPostPart> BinaryData { get; }
+
+ /// <summary>
+ /// Gets a value indicating whether this message should be sent as multi-part POST.
+ /// </summary>
+ bool SendAsMultipart { get; }
+ }
+
+ /// <summary>
+ /// The contract class for the <see cref="IMessageWithBinaryData"/> interface.
+ /// </summary>
+ [ContractClassFor(typeof(IMessageWithBinaryData))]
+ internal sealed class IMessageWithBinaryDataContract : IMessageWithBinaryData {
+ #region IMessageWithBinaryData Members
+
+ /// <summary>
+ /// Gets the parts of the message that carry binary data.
+ /// </summary>
+ /// <value>A list of parts. Never null.</value>
+ IList<MultipartPostPart> IMessageWithBinaryData.BinaryData {
+ get {
+ Contract.Ensures(Contract.Result<IList<MultipartPostPart>>() != null);
+ throw new NotImplementedException();
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this message should be sent as multi-part POST.
+ /// </summary>
+ bool IMessageWithBinaryData.SendAsMultipart {
+ get { throw new NotImplementedException(); }
+ }
+
+ #endregion
+
+ #region IMessage Properties
+
+ /// <summary>
+ /// Gets the version of the protocol or extension this message is prepared to implement.
+ /// </summary>
+ /// <value></value>
+ /// <remarks>
+ /// Implementations of this interface should ensure that this property never returns null.
+ /// </remarks>
+ Version IMessage.Version {
+ get {
+ Contract.Ensures(Contract.Result<Version>() != null);
+ return default(Version); // dummy return
+ }
+ }
+
+ /// <summary>
+ /// Gets the extra, non-standard Protocol parameters included in the message.
+ /// </summary>
+ /// <value></value>
+ /// <remarks>
+ /// Implementations of this interface should ensure that this property never returns null.
+ /// </remarks>
+ IDictionary<string, string> IMessage.ExtraData {
+ get {
+ Contract.Ensures(Contract.Result<IDictionary<string, string>>() != null);
+ return default(IDictionary<string, string>);
+ }
+ }
+
+ #endregion
+
+ #region IDirectedProtocolMessage Members
+
+ /// <summary>
+ /// Gets the preferred method of transport for the message.
+ /// </summary>
+ /// <remarks>
+ /// For indirect messages this will likely be GET+POST, which both can be simulated in the user agent:
+ /// the GET with a simple 301 Redirect, and the POST with an HTML form in the response with javascript
+ /// to automate submission.
+ /// </remarks>
+ HttpDeliveryMethods IDirectedProtocolMessage.HttpMethods {
+ get { throw new NotImplementedException(); }
+ }
+
+ /// <summary>
+ /// Gets the URL of the intended receiver of this message.
+ /// </summary>
+ Uri IDirectedProtocolMessage.Recipient {
+ get { throw new NotImplementedException(); }
+ }
+
+ #endregion
+
+ #region IProtocolMessage Members
+
+ /// <summary>
+ /// Gets the level of protection this message requires.
+ /// </summary>
+ MessageProtections IProtocolMessage.RequiredProtection {
+ get { throw new NotImplementedException(); }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this is a direct or indirect message.
+ /// </summary>
+ MessageTransport IProtocolMessage.Transport {
+ get { throw new NotImplementedException(); }
+ }
+
+ #endregion
+
+ #region IMessage methods
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ /// <remarks>
+ /// <para>Some messages have required fields, or combinations of fields that must relate to each other
+ /// in specialized ways. After deserializing a message, this method checks the state of the
+ /// message to see if it conforms to the protocol.</para>
+ /// <para>Note that this property should <i>not</i> check signatures or perform any state checks
+ /// outside this scope of this particular message.</para>
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception>
+ void IMessage.EnsureValidMessage() {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs b/src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs
index f2c9add..5654c00 100644
--- a/src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs
+++ b/src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs
@@ -70,6 +70,15 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
+ /// Looks up a localized string similar to Unable to send all message data because some of it requires multi-part POST, but IMessageWithBinaryData.SendAsMultipart was false..
+ /// </summary>
+ internal static string BinaryDataRequiresMultipart {
+ get {
+ return ResourceManager.GetString("BinaryDataRequiresMultipart", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to HttpContext.Current is null. There must be an ASP.NET request in process for this operation to succeed..
/// </summary>
internal static string CurrentHttpContextRequired {
diff --git a/src/DotNetOpenAuth/Messaging/MessagingStrings.resx b/src/DotNetOpenAuth/Messaging/MessagingStrings.resx
index 3d4e317..34385d4 100644
--- a/src/DotNetOpenAuth/Messaging/MessagingStrings.resx
+++ b/src/DotNetOpenAuth/Messaging/MessagingStrings.resx
@@ -297,4 +297,7 @@
<data name="StreamMustHaveKnownLength" xml:space="preserve">
<value>The stream must have a known length.</value>
</data>
+ <data name="BinaryDataRequiresMultipart" xml:space="preserve">
+ <value>Unable to send all message data because some of it requires multi-part POST, but IMessageWithBinaryData.SendAsMultipart was false.</value>
+ </data>
</root> \ No newline at end of file
diff --git a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs
index a6c1c65..fe8d6c2 100644
--- a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs
+++ b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs
@@ -152,6 +152,46 @@ namespace DotNetOpenAuth.Messaging {
Contract.Requires<ArgumentNullException>(requestHandler != null);
Contract.Requires<ArgumentNullException>(parts != null);
+ PostMultipartNoGetResponse(request, requestHandler, parts);
+ return requestHandler.GetResponse(request);
+ }
+
+ /// <summary>
+ /// Assembles a message comprised of the message on a given exception and all inner exceptions.
+ /// </summary>
+ /// <param name="exception">The exception.</param>
+ /// <returns>The assembled message.</returns>
+ public static string ToStringDescriptive(this Exception exception) {
+ // The input being null is probably bad, but since this method is called
+ // from a catch block, we don't really want to throw a new exception and
+ // hide the details of this one.
+ if (exception == null) {
+ Logger.Messaging.Error("MessagingUtilities.GetAllMessages called with null input.");
+ }
+
+ StringBuilder message = new StringBuilder();
+ while (exception != null) {
+ message.Append(exception.Message);
+ exception = exception.InnerException;
+ if (exception != null) {
+ message.Append(" ");
+ }
+ }
+
+ return message.ToString();
+ }
+
+ /// <summary>
+ /// Sends a multipart HTTP POST request (useful for posting files) but doesn't call GetResponse on it.
+ /// </summary>
+ /// <param name="request">The HTTP request.</param>
+ /// <param name="requestHandler">The request handler.</param>
+ /// <param name="parts">The parts to include in the POST entity.</param>
+ internal static void PostMultipartNoGetResponse(this HttpWebRequest request, IDirectWebRequestHandler requestHandler, IEnumerable<MultipartPostPart> parts) {
+ Contract.Requires<ArgumentNullException>(request != null);
+ Contract.Requires<ArgumentNullException>(requestHandler != null);
+ Contract.Requires<ArgumentNullException>(parts != null);
+
Reporting.RecordFeatureUse("MessagingUtilities.PostMultipart");
parts = parts.CacheGeneratedResults();
string boundary = Guid.NewGuid().ToString();
@@ -193,33 +233,6 @@ namespace DotNetOpenAuth.Messaging {
requestStream.Dispose();
}
}
-
- return requestHandler.GetResponse(request);
- }
-
- /// <summary>
- /// Assembles a message comprised of the message on a given exception and all inner exceptions.
- /// </summary>
- /// <param name="exception">The exception.</param>
- /// <returns>The assembled message.</returns>
- public static string ToStringDescriptive(this Exception exception) {
- // The input being null is probably bad, but since this method is called
- // from a catch block, we don't really want to throw a new exception and
- // hide the details of this one.
- if (exception == null) {
- Logger.Messaging.Error("MessagingUtilities.GetAllMessages called with null input.");
- }
-
- StringBuilder message = new StringBuilder();
- while (exception != null) {
- message.Append(exception.Message);
- exception = exception.InnerException;
- if (exception != null) {
- message.Append(" ");
- }
- }
-
- return message.ToString();
}
/// <summary>
diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs
index 499b996..b0e938f 100644
--- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs
+++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs
@@ -11,6 +11,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
using System.Diagnostics.Contracts;
using System.Globalization;
using System.IO;
+ using System.Linq;
using System.Net;
using System.Text;
using System.Web;
@@ -87,7 +88,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
/// </summary>
/// <param name="message">The message with data to encode.</param>
/// <returns>A dictionary of name-value pairs with their strings encoded.</returns>
- internal static IDictionary<string, string> GetUriEscapedParameters(MessageDictionary message) {
+ internal static IDictionary<string, string> GetUriEscapedParameters(IEnumerable<KeyValuePair<string, string>> message) {
var encodedDictionary = new Dictionary<string, string>();
UriEscapeParameters(message, encodedDictionary);
return encodedDictionary;
@@ -202,6 +203,8 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
if ((transmissionMethod & HttpDeliveryMethods.AuthorizationHeaderRequest) != 0) {
httpRequest = this.InitializeRequestAsAuthHeader(request);
} else if ((transmissionMethod & HttpDeliveryMethods.PostRequest) != 0) {
+ var requestMessageWithBinaryData = request as IMessageWithBinaryData;
+ ErrorUtilities.VerifyProtocol(requestMessageWithBinaryData == null || !requestMessageWithBinaryData.SendAsMultipart, OAuthStrings.MultipartPostMustBeUsedWithAuthHeader);
httpRequest = this.InitializeRequestAsPost(request);
} else if ((transmissionMethod & HttpDeliveryMethods.GetRequest) != 0) {
httpRequest = InitializeRequestAsGet(request);
@@ -274,7 +277,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
/// </summary>
/// <param name="source">The dictionary with names and values to encode.</param>
/// <param name="destination">The dictionary to add the encoded pairs to.</param>
- private static void UriEscapeParameters(IDictionary<string, string> source, IDictionary<string, string> destination) {
+ private static void UriEscapeParameters(IEnumerable<KeyValuePair<string, string>> source, IDictionary<string, string> destination) {
Contract.Requires<ArgumentNullException>(source != null);
Contract.Requires<ArgumentNullException>(destination != null);
@@ -351,12 +354,22 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
if (hasEntity) {
// WARNING: We only set up the request stream for the caller if there is
// extra data. If there isn't any extra data, the caller must do this themselves.
- if (requestMessage.ExtraData.Count > 0) {
- SendParametersInEntity(httpRequest, requestMessage.ExtraData);
+ var requestMessageWithBinaryData = requestMessage as IMessageWithBinaryData;
+ if (requestMessageWithBinaryData != null && requestMessageWithBinaryData.SendAsMultipart) {
+ // Include the binary data in the multipart entity, and any standard text extra message data.
+ // The standard declared message parts are included in the authorization header.
+ var multiPartFields = new List<MultipartPostPart>(requestMessageWithBinaryData.BinaryData);
+ multiPartFields.AddRange(requestMessage.ExtraData.Select(field => MultipartPostPart.CreateFormPart(field.Key, field.Value)));
+ this.SendParametersInEntityAsMultipart(httpRequest, multiPartFields);
} else {
- // We'll assume the content length is zero since the caller may not have
- // anything. They're responsible to change it when the add the payload if they have one.
- httpRequest.ContentLength = 0;
+ ErrorUtilities.VerifyProtocol(requestMessageWithBinaryData == null || requestMessageWithBinaryData.BinaryData.Count == 0, MessagingStrings.BinaryDataRequiresMultipart);
+ if (requestMessage.ExtraData.Count > 0) {
+ this.SendParametersInEntity(httpRequest, requestMessage.ExtraData);
+ } else {
+ // We'll assume the content length is zero since the caller may not have
+ // anything. They're responsible to change it when the add the payload if they have one.
+ httpRequest.ContentLength = 0;
+ }
}
}
diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs
index 004e7d5..b45da66 100644
--- a/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs
+++ b/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs
@@ -10,6 +10,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
using System.Collections.Specialized;
using System.Diagnostics.Contracts;
using System.Globalization;
+ using System.Linq;
using System.Text;
using System.Web;
using DotNetOpenAuth.Messaging;
@@ -157,7 +158,26 @@ namespace DotNetOpenAuth.OAuth.ChannelElements {
signatureBaseStringElements.Add(message.HttpMethod.ToUpperInvariant());
- var encodedDictionary = OAuthChannel.GetUriEscapedParameters(messageDictionary);
+ // For multipart POST messages, only include the message parts that are NOT
+ // in the POST entity (those parts that may appear in an OAuth authorization header).
+ var encodedDictionary = new Dictionary<string, string>();
+ IEnumerable<KeyValuePair<string, string>> partsToInclude = Enumerable.Empty<KeyValuePair<string, string>>();
+ var binaryMessage = message as IMessageWithBinaryData;
+ if (binaryMessage != null && binaryMessage.SendAsMultipart) {
+ HttpDeliveryMethods authHeaderInUseFlags = HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest;
+ ErrorUtilities.VerifyProtocol((binaryMessage.HttpMethods & authHeaderInUseFlags) == authHeaderInUseFlags, OAuthStrings.MultipartPostMustBeUsedWithAuthHeader);
+
+ // Include the declared keys in the signature as those will be signable.
+ // Cache in local variable to avoid recalculating DeclaredKeys in the delegate.
+ ICollection<string> declaredKeys = messageDictionary.DeclaredKeys;
+ partsToInclude = messageDictionary.Where(pair => declaredKeys.Contains(pair.Key));
+ } else {
+ partsToInclude = messageDictionary;
+ }
+
+ foreach (var pair in OAuthChannel.GetUriEscapedParameters(partsToInclude)) {
+ encodedDictionary[pair.Key] = pair.Value;
+ }
// An incoming message will already have included the query and form parameters
// in the message dictionary, but an outgoing message COULD have SOME parameters
diff --git a/src/DotNetOpenAuth/OAuth/ConsumerBase.cs b/src/DotNetOpenAuth/OAuth/ConsumerBase.cs
index 6c0ce42..48f54d7 100644
--- a/src/DotNetOpenAuth/OAuth/ConsumerBase.cs
+++ b/src/DotNetOpenAuth/OAuth/ConsumerBase.cs
@@ -9,6 +9,7 @@ namespace DotNetOpenAuth.OAuth {
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
+ using System.Linq;
using System.Net;
using DotNetOpenAuth.Configuration;
using DotNetOpenAuth.Messaging;
@@ -111,6 +112,27 @@ namespace DotNetOpenAuth.OAuth {
}
/// <summary>
+ /// Prepares an authorized request that carries an HTTP multi-part POST, allowing for binary data.
+ /// </summary>
+ /// <param name="endpoint">The URL and method on the Service Provider to send the request to.</param>
+ /// <param name="accessToken">The access token that permits access to the protected resource.</param>
+ /// <param name="binaryData">Extra parameters to include in the message. Must not be null, but may be empty.</param>
+ /// <returns>The initialized WebRequest object.</returns>
+ public HttpWebRequest PrepareAuthorizedRequest(MessageReceivingEndpoint endpoint, string accessToken, IEnumerable<MultipartPostPart> binaryData) {
+ Contract.Requires<ArgumentNullException>(endpoint != null);
+ Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(accessToken));
+ Contract.Requires<ArgumentNullException>(binaryData != null);
+
+ AccessProtectedResourceRequest message = this.CreateAuthorizingMessage(endpoint, accessToken);
+ foreach (MultipartPostPart part in binaryData) {
+ message.BinaryData.Add(part);
+ }
+
+ HttpWebRequest wr = this.OAuthChannel.InitializeRequest(message);
+ return wr;
+ }
+
+ /// <summary>
/// Prepares an HTTP request that has OAuth authorization already attached to it.
/// </summary>
/// <param name="message">The OAuth authorization message to attach to the HTTP request.</param>
diff --git a/src/DotNetOpenAuth/OAuth/Messages/AccessProtectedResourceRequest.cs b/src/DotNetOpenAuth/OAuth/Messages/AccessProtectedResourceRequest.cs
index b60fda4..f3231f0 100644
--- a/src/DotNetOpenAuth/OAuth/Messages/AccessProtectedResourceRequest.cs
+++ b/src/DotNetOpenAuth/OAuth/Messages/AccessProtectedResourceRequest.cs
@@ -6,6 +6,7 @@
namespace DotNetOpenAuth.OAuth.Messages {
using System;
+ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using DotNetOpenAuth.Messaging;
@@ -13,7 +14,12 @@ namespace DotNetOpenAuth.OAuth.Messages {
/// A message attached to a request for protected resources that provides the necessary
/// credentials to be granted access to those resources.
/// </summary>
- public class AccessProtectedResourceRequest : SignedMessageBase, ITokenContainingMessage {
+ public class AccessProtectedResourceRequest : SignedMessageBase, ITokenContainingMessage, IMessageWithBinaryData {
+ /// <summary>
+ /// A store for the binary data that is carried in the message.
+ /// </summary>
+ private List<MultipartPostPart> binaryData = new List<MultipartPostPart>();
+
/// <summary>
/// Initializes a new instance of the <see cref="AccessProtectedResourceRequest"/> class.
/// </summary>
@@ -43,5 +49,24 @@ namespace DotNetOpenAuth.OAuth.Messages {
/// </remarks>
[MessagePart("oauth_token", IsRequired = true)]
public string AccessToken { get; set; }
+
+ #region IMessageWithBinaryData Members
+
+ /// <summary>
+ /// Gets the parts of the message that carry binary data.
+ /// </summary>
+ /// <value>A list of parts. Never null.</value>
+ public IList<MultipartPostPart> BinaryData {
+ get { return this.binaryData; }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this message should be sent as multi-part POST.
+ /// </summary>
+ public bool SendAsMultipart {
+ get { return this.HttpMethod == "POST" && this.BinaryData.Count > 0; }
+ }
+
+ #endregion
}
}
diff --git a/src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs b/src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs
index 3593446..fd74bcb 100644
--- a/src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs
+++ b/src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs
@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
-// Runtime Version:2.0.50727.4918
+// Runtime Version:2.0.50727.4927
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -115,29 +115,38 @@ namespace DotNetOpenAuth.OAuth {
}
/// <summary>
- /// Looks up a localized string similar to Use of the OpenID+OAuth extension requires that the token manager in use implement the {0} interface..
+ /// Looks up a localized string similar to This OAuth service provider requires OAuth consumers to implement OAuth {0}, but this consumer appears to only support {1}..
/// </summary>
- internal static string OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface {
+ internal static string MinimumConsumerVersionRequirementNotMet {
get {
- return ResourceManager.GetString("OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface", resourceCulture);
+ return ResourceManager.GetString("MinimumConsumerVersionRequirementNotMet", resourceCulture);
}
}
/// <summary>
- /// Looks up a localized string similar to The OpenID Relying Party&apos;s realm is not recognized as belonging to the OAuth Consumer identified by the consumer key given..
+ /// Looks up a localized string similar to Cannot send OAuth message as multipart POST without an authorization HTTP header because sensitive data would not be signed..
/// </summary>
- internal static string OpenIdOAuthRealmConsumerKeyDoNotMatch {
+ internal static string MultipartPostMustBeUsedWithAuthHeader {
get {
- return ResourceManager.GetString("OpenIdOAuthRealmConsumerKeyDoNotMatch", resourceCulture);
+ return ResourceManager.GetString("MultipartPostMustBeUsedWithAuthHeader", resourceCulture);
}
}
/// <summary>
- /// Looks up a localized string similar to This OAuth service provider requires OAuth consumers to implement OAuth {0}, but this consumer appears to only support {1}..
+ /// Looks up a localized string similar to Use of the OpenID+OAuth extension requires that the token manager in use implement the {0} interface..
/// </summary>
- internal static string MinimumConsumerVersionRequirementNotMet {
+ internal static string OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface {
get {
- return ResourceManager.GetString("MinimumConsumerVersionRequirementNotMet", resourceCulture);
+ return ResourceManager.GetString("OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The OpenID Relying Party&apos;s realm is not recognized as belonging to the OAuth Consumer identified by the consumer key given..
+ /// </summary>
+ internal static string OpenIdOAuthRealmConsumerKeyDoNotMatch {
+ get {
+ return ResourceManager.GetString("OpenIdOAuthRealmConsumerKeyDoNotMatch", resourceCulture);
}
}
diff --git a/src/DotNetOpenAuth/OAuth/OAuthStrings.resx b/src/DotNetOpenAuth/OAuth/OAuthStrings.resx
index bbeeda9..34b314b 100644
--- a/src/DotNetOpenAuth/OAuth/OAuthStrings.resx
+++ b/src/DotNetOpenAuth/OAuth/OAuthStrings.resx
@@ -138,6 +138,9 @@
<data name="MinimumConsumerVersionRequirementNotMet" xml:space="preserve">
<value>This OAuth service provider requires OAuth consumers to implement OAuth {0}, but this consumer appears to only support {1}.</value>
</data>
+ <data name="MultipartPostMustBeUsedWithAuthHeader" xml:space="preserve">
+ <value>Cannot send OAuth message as multipart POST without an authorization HTTP header because sensitive data would not be signed.</value>
+ </data>
<data name="OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface" xml:space="preserve">
<value>Use of the OpenID+OAuth extension requires that the token manager in use implement the {0} interface.</value>
</data>
diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ExtensionsInteropHelper.cs b/src/DotNetOpenAuth/OpenId/Extensions/ExtensionsInteropHelper.cs
index c55e3bd..9e7ccd2 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.Debug("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 aaf9e1a..76a1c9b 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&lt;T&gt;()"/> method.</para>
/// </remarks>
public sealed class UIRequest : IOpenIdMessageExtension, IMessageWithEvents {
/// <summary>
diff --git a/src/DotNetOpenAuth/OpenId/HostMetaDiscoveryService.cs b/src/DotNetOpenAuth/OpenId/HostMetaDiscoveryService.cs
new file mode 100644
index 0000000..e96f362
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/HostMetaDiscoveryService.cs
@@ -0,0 +1,504 @@
+//-----------------------------------------------------------------------
+// <copyright file="HostMetaDiscoveryService.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.IO;
+ using System.Linq;
+ using System.Net;
+ using System.Security;
+ using System.Security.Cryptography;
+ using System.Security.Cryptography.X509Certificates;
+ using System.Security.Permissions;
+ using System.Text;
+ using System.Text.RegularExpressions;
+ using System.Xml;
+ using System.Xml.XPath;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+ using DotNetOpenAuth.Xrds;
+ using DotNetOpenAuth.Yadis;
+
+ /// <summary>
+ /// The discovery service to support host-meta based discovery, such as Google Apps for Domains.
+ /// </summary>
+ /// <remarks>
+ /// The spec for this discovery mechanism can be found at:
+ /// http://groups.google.com/group/google-federated-login-api/web/openid-discovery-for-hosted-domains
+ /// and the XMLDSig spec referenced in that spec can be found at:
+ /// http://wiki.oasis-open.org/xri/XrdOne/XmlDsigProfile
+ /// </remarks>
+ public class HostMetaDiscoveryService : IIdentifierDiscoveryService {
+ /// <summary>
+ /// The URI template for discovery host-meta on domains hosted by
+ /// Google Apps for Domains.
+ /// </summary>
+ private static readonly HostMetaProxy GoogleHostedHostMeta = new HostMetaProxy("https://www.google.com/accounts/o8/.well-known/host-meta?hd={0}", "hosted-id.google.com");
+
+ /// <summary>
+ /// Path to the well-known location of the host-meta document at a domain.
+ /// </summary>
+ private const string LocalHostMetaPath = "/.well-known/host-meta";
+
+ /// <summary>
+ /// The pattern within a host-meta file to look for to obtain the URI to the XRDS document.
+ /// </summary>
+ private static readonly Regex HostMetaLink = new Regex(@"^Link: <(?<location>.+?)>; rel=""describedby http://reltype.google.com/openid/xrd-op""; type=""application/xrds\+xml""$");
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="HostMetaDiscoveryService"/> class.
+ /// </summary>
+ public HostMetaDiscoveryService() {
+ this.TrustedHostMetaProxies = new List<HostMetaProxy>();
+ }
+
+ /// <summary>
+ /// Gets the set of URI templates to use to contact host-meta hosting proxies
+ /// for domain discovery.
+ /// </summary>
+ public IList<HostMetaProxy> TrustedHostMetaProxies { get; private set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether to trust Google to host domains' host-meta documents.
+ /// </summary>
+ /// <remarks>
+ /// This property is just a convenient mechanism for checking or changing the set of
+ /// trusted host-meta proxies in the <see cref="TrustedHostMetaProxies"/> property.
+ /// </remarks>
+ public bool UseGoogleHostedHostMeta {
+ get {
+ return this.TrustedHostMetaProxies.Contains(GoogleHostedHostMeta);
+ }
+
+ set {
+ if (value != this.UseGoogleHostedHostMeta) {
+ if (value) {
+ this.TrustedHostMetaProxies.Add(GoogleHostedHostMeta);
+ } else {
+ this.TrustedHostMetaProxies.Remove(GoogleHostedHostMeta);
+ }
+ }
+ }
+ }
+
+ #region IIdentifierDiscoveryService 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;
+
+ // Google Apps are always URIs -- not XRIs.
+ var uriIdentifier = identifier as UriIdentifier;
+ if (uriIdentifier == null) {
+ return Enumerable.Empty<IdentifierDiscoveryResult>();
+ }
+
+ var results = new List<IdentifierDiscoveryResult>();
+ string signingHost;
+ var response = GetXrdsResponse(uriIdentifier, requestHandler, out signingHost);
+
+ if (response != null) {
+ try {
+ var document = new XrdsDocument(XmlReader.Create(response.ResponseStream));
+ ValidateXmlDSig(document, uriIdentifier, response, signingHost);
+ var xrds = GetXrdElements(document, uriIdentifier.Uri.Host);
+
+ // Look for claimed identifier template URIs for an additional XRDS document.
+ results.AddRange(GetExternalServices(xrds, uriIdentifier, requestHandler));
+
+ // If we couldn't find any claimed identifiers, look for OP identifiers.
+ // Normally this would be the opposite (OP Identifiers take precedence over
+ // claimed identifiers, but for Google Apps, XRDS' always have OP Identifiers
+ // mixed in, which the OpenID spec mandate should eclipse Claimed Identifiers,
+ // which would break positive assertion checks).
+ if (results.Count == 0) {
+ results.AddRange(xrds.CreateServiceEndpoints(uriIdentifier, uriIdentifier));
+ }
+
+ abortDiscoveryChain = true;
+ } catch (XmlException ex) {
+ Logger.Yadis.ErrorFormat("Error while parsing XRDS document at {0} pointed to by host-meta: {1}", response.FinalUri, ex);
+ }
+ }
+
+ return results;
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Gets the XRD elements that have a given CanonicalID.
+ /// </summary>
+ /// <param name="document">The XRDS document.</param>
+ /// <param name="canonicalId">The CanonicalID to match on.</param>
+ /// <returns>A sequence of XRD elements.</returns>
+ private static IEnumerable<XrdElement> GetXrdElements(XrdsDocument document, string canonicalId) {
+ // filter to include only those XRD elements describing the host whose host-meta pointed us to this document.
+ return document.XrdElements.Where(xrd => string.Equals(xrd.CanonicalID, canonicalId, StringComparison.Ordinal));
+ }
+
+ /// <summary>
+ /// Gets the described-by services in XRD elements.
+ /// </summary>
+ /// <param name="xrds">The XRDs to search.</param>
+ /// <returns>A sequence of services.</returns>
+ private static IEnumerable<ServiceElement> GetDescribedByServices(IEnumerable<XrdElement> xrds) {
+ Contract.Requires<ArgumentNullException>(xrds != null);
+ Contract.Ensures(Contract.Result<IEnumerable<ServiceElement>>() != null);
+
+ var describedBy = from xrd in xrds
+ from service in xrd.SearchForServiceTypeUris(p => "http://www.iana.org/assignments/relation/describedby")
+ select service;
+ return describedBy;
+ }
+
+ /// <summary>
+ /// Gets the services for an identifier that are described by an external XRDS document.
+ /// </summary>
+ /// <param name="xrds">The XRD elements to search for described-by services.</param>
+ /// <param name="identifier">The identifier under discovery.</param>
+ /// <param name="requestHandler">The request handler.</param>
+ /// <returns>The discovered services.</returns>
+ private static IEnumerable<IdentifierDiscoveryResult> GetExternalServices(IEnumerable<XrdElement> xrds, UriIdentifier identifier, IDirectWebRequestHandler requestHandler) {
+ Contract.Requires<ArgumentNullException>(xrds != null);
+ Contract.Requires<ArgumentNullException>(identifier != null);
+ Contract.Requires<ArgumentNullException>(requestHandler != null);
+ Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null);
+
+ var results = new List<IdentifierDiscoveryResult>();
+ foreach (var serviceElement in GetDescribedByServices(xrds)) {
+ var templateNode = serviceElement.Node.SelectSingleNode("google:URITemplate", serviceElement.XmlNamespaceResolver);
+ var nextAuthorityNode = serviceElement.Node.SelectSingleNode("google:NextAuthority", serviceElement.XmlNamespaceResolver);
+ if (templateNode != null) {
+ Uri externalLocation = new Uri(templateNode.Value.Trim().Replace("{%uri}", Uri.EscapeDataString(identifier.Uri.AbsoluteUri)));
+ string nextAuthority = nextAuthorityNode != null ? nextAuthorityNode.Value.Trim() : identifier.Uri.Host;
+ try {
+ var externalXrdsResponse = GetXrdsResponse(identifier, requestHandler, externalLocation);
+ XrdsDocument externalXrds = new XrdsDocument(XmlReader.Create(externalXrdsResponse.ResponseStream));
+ ValidateXmlDSig(externalXrds, identifier, externalXrdsResponse, nextAuthority);
+ results.AddRange(GetXrdElements(externalXrds, identifier).CreateServiceEndpoints(identifier, identifier));
+ } catch (ProtocolException ex) {
+ Logger.Yadis.WarnFormat("HTTP GET error while retrieving described-by XRDS document {0}: {1}", externalLocation.AbsoluteUri, ex);
+ } catch (XmlException ex) {
+ Logger.Yadis.ErrorFormat("Error while parsing described-by XRDS document {0}: {1}", externalLocation.AbsoluteUri, ex);
+ }
+ }
+ }
+
+ return results;
+ }
+
+ /// <summary>
+ /// Validates the XML digital signature on an XRDS document.
+ /// </summary>
+ /// <param name="document">The XRDS document whose signature should be validated.</param>
+ /// <param name="identifier">The identifier under discovery.</param>
+ /// <param name="response">The response.</param>
+ /// <param name="signingHost">The host name on the certificate that should be used to verify the signature in the XRDS.</param>
+ /// <exception cref="ProtocolException">Thrown if the XRDS document has an invalid or a missing signature.</exception>
+ private static void ValidateXmlDSig(XrdsDocument document, UriIdentifier identifier, IncomingWebResponse response, string signingHost) {
+ Contract.Requires<ArgumentNullException>(document != null);
+ Contract.Requires<ArgumentNullException>(identifier != null);
+ Contract.Requires<ArgumentNullException>(response != null);
+
+ var signatureNode = document.Node.SelectSingleNode("/xrds:XRDS/ds:Signature", document.XmlNamespaceResolver);
+ ErrorUtilities.VerifyProtocol(signatureNode != null, "Missing Signature element.");
+ var signedInfoNode = signatureNode.SelectSingleNode("ds:SignedInfo", document.XmlNamespaceResolver);
+ ErrorUtilities.VerifyProtocol(signedInfoNode != null, "Missing SignedInfo element.");
+ ErrorUtilities.VerifyProtocol(
+ signedInfoNode.SelectSingleNode("ds:CanonicalizationMethod[@Algorithm='http://docs.oasis-open.org/xri/xrd/2009/01#canonicalize-raw-octets']", document.XmlNamespaceResolver) != null,
+ "Unrecognized or missing canonicalization method.");
+ ErrorUtilities.VerifyProtocol(
+ signedInfoNode.SelectSingleNode("ds:SignatureMethod[@Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1']", document.XmlNamespaceResolver) != null,
+ "Unrecognized or missing signature method.");
+ var certNodes = signatureNode.Select("ds:KeyInfo/ds:X509Data/ds:X509Certificate", document.XmlNamespaceResolver);
+ ErrorUtilities.VerifyProtocol(certNodes.Count > 0, "Missing X509Certificate element.");
+ var certs = certNodes.Cast<XPathNavigator>().Select(n => new X509Certificate2(Convert.FromBase64String(n.Value.Trim()))).ToList();
+
+ // Verify that we trust the signer of the certificates.
+ // Start by trying to validate just the certificate used to sign the XRDS document,
+ // since we can do that with partial trust.
+ if (!certs[0].Verify()) {
+ // We couldn't verify just the signing certificate, so try to verify the whole certificate chain.
+ try {
+ VerifyCertChain(certs);
+ } catch (SecurityException) {
+ Logger.Yadis.Warn("Signing certificate verification failed and we have insufficient code access security permissions to perform certificate chain validation.");
+ ErrorUtilities.ThrowProtocol(OpenIdStrings.X509CertificateNotTrusted);
+ }
+ }
+
+ // Verify that the certificate is issued to the host on whom we are performing discovery.
+ string hostName = certs[0].GetNameInfo(X509NameType.DnsName, false);
+ ErrorUtilities.VerifyProtocol(string.Equals(hostName, signingHost, StringComparison.OrdinalIgnoreCase), "X.509 signing certificate issued to {0}, but a certificate for {1} was expected.", hostName, signingHost);
+
+ // Verify the signature itself
+ byte[] signature = Convert.FromBase64String(response.Headers["Signature"]);
+ var provider = (RSACryptoServiceProvider)certs.First().PublicKey.Key;
+ byte[] data = new byte[response.ResponseStream.Length];
+ response.ResponseStream.Seek(0, SeekOrigin.Begin);
+ response.ResponseStream.Read(data, 0, data.Length);
+ ErrorUtilities.VerifyProtocol(provider.VerifyData(data, "SHA1", signature), "Invalid XmlDSig signature on XRDS document.");
+ }
+
+ /// <summary>
+ /// Verifies the cert chain.
+ /// </summary>
+ /// <param name="certs">The certs.</param>
+ /// <remarks>
+ /// This must be in a method of its own because there is a LinkDemand on the <see cref="X509Chain.Build"/>
+ /// method. By being in a method of its own, the caller of this method may catch a
+ /// <see cref="SecurityException"/> that is thrown if we're not running with full trust and execute
+ /// an alternative plan.
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the certificate chain is invalid or unverifiable.</exception>
+ [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "By design")]
+ private static void VerifyCertChain(List<X509Certificate2> certs) {
+ var chain = new X509Chain();
+ foreach (var cert in certs) {
+ chain.Build(cert);
+ }
+
+ if (chain.ChainStatus.Length > 0) {
+ ErrorUtilities.ThrowProtocol(
+ string.Format(
+ CultureInfo.CurrentCulture,
+ OpenIdStrings.X509CertificateNotTrusted + " {0}",
+ string.Join(", ", chain.ChainStatus.Select(status => status.StatusInformation).ToArray())));
+ }
+ }
+
+ /// <summary>
+ /// Gets the XRDS HTTP response for a given identifier.
+ /// </summary>
+ /// <param name="identifier">The identifier.</param>
+ /// <param name="requestHandler">The request handler.</param>
+ /// <param name="xrdsLocation">The location of the XRDS document to retrieve.</param>
+ /// <returns>
+ /// A HTTP response carrying an XRDS document.
+ /// </returns>
+ /// <exception cref="ProtocolException">Thrown if the XRDS document could not be obtained.</exception>
+ private static IncomingWebResponse GetXrdsResponse(UriIdentifier identifier, IDirectWebRequestHandler requestHandler, Uri xrdsLocation) {
+ Contract.Requires<ArgumentNullException>(identifier != null);
+ Contract.Requires<ArgumentNullException>(requestHandler != null);
+ Contract.Requires<ArgumentNullException>(xrdsLocation != null);
+ Contract.Ensures(Contract.Result<IncomingWebResponse>() != null);
+
+ var request = (HttpWebRequest)WebRequest.Create(xrdsLocation);
+ request.CachePolicy = Yadis.IdentifierDiscoveryCachePolicy;
+ request.Accept = ContentTypes.Xrds;
+ var options = identifier.IsDiscoverySecureEndToEnd ? DirectWebRequestOptions.RequireSsl : DirectWebRequestOptions.None;
+ var response = requestHandler.GetResponse(request, options);
+ if (!string.Equals(response.ContentType.MediaType, ContentTypes.Xrds, StringComparison.Ordinal)) {
+ Logger.Yadis.WarnFormat("Host-meta pointed to XRDS at {0}, but Content-Type at that URL was unexpected value '{1}'.", xrdsLocation, response.ContentType);
+ }
+
+ return response;
+ }
+
+ /// <summary>
+ /// Gets the XRDS HTTP response for a given identifier.
+ /// </summary>
+ /// <param name="identifier">The identifier.</param>
+ /// <param name="requestHandler">The request handler.</param>
+ /// <param name="signingHost">The host name on the certificate that should be used to verify the signature in the XRDS.</param>
+ /// <returns>A HTTP response carrying an XRDS document, or <c>null</c> if one could not be obtained.</returns>
+ /// <exception cref="ProtocolException">Thrown if the XRDS document could not be obtained.</exception>
+ private IncomingWebResponse GetXrdsResponse(UriIdentifier identifier, IDirectWebRequestHandler requestHandler, out string signingHost) {
+ Contract.Requires<ArgumentNullException>(identifier != null);
+ Contract.Requires<ArgumentNullException>(requestHandler != null);
+ Uri xrdsLocation = this.GetXrdsLocation(identifier, requestHandler, out signingHost);
+ if (xrdsLocation == null) {
+ return null;
+ }
+
+ var response = GetXrdsResponse(identifier, requestHandler, xrdsLocation);
+
+ return response;
+ }
+
+ /// <summary>
+ /// Gets the location of the XRDS document that describes a given identifier.
+ /// </summary>
+ /// <param name="identifier">The identifier under discovery.</param>
+ /// <param name="requestHandler">The request handler.</param>
+ /// <param name="signingHost">The host name on the certificate that should be used to verify the signature in the XRDS.</param>
+ /// <returns>An absolute URI, or <c>null</c> if one could not be determined.</returns>
+ private Uri GetXrdsLocation(UriIdentifier identifier, IDirectWebRequestHandler requestHandler, out string signingHost) {
+ Contract.Requires<ArgumentNullException>(identifier != null);
+ Contract.Requires<ArgumentNullException>(requestHandler != null);
+ var hostMetaResponse = this.GetHostMeta(identifier, requestHandler, out signingHost);
+ if (hostMetaResponse == null) {
+ return null;
+ }
+
+ using (var sr = hostMetaResponse.GetResponseReader()) {
+ string line = sr.ReadLine();
+ Match m = HostMetaLink.Match(line);
+ if (m.Success) {
+ Uri location = new Uri(m.Groups["location"].Value);
+ Logger.Yadis.InfoFormat("Found link to XRDS at {0} in host-meta document {1}.", location, hostMetaResponse.FinalUri);
+ return location;
+ }
+ }
+
+ Logger.Yadis.WarnFormat("Could not find link to XRDS in host-meta document: {0}", hostMetaResponse.FinalUri);
+ return null;
+ }
+
+ /// <summary>
+ /// Gets the host-meta for a given identifier.
+ /// </summary>
+ /// <param name="identifier">The identifier.</param>
+ /// <param name="requestHandler">The request handler.</param>
+ /// <param name="signingHost">The host name on the certificate that should be used to verify the signature in the XRDS.</param>
+ /// <returns>
+ /// The host-meta response, or <c>null</c> if no host-meta document could be obtained.
+ /// </returns>
+ private IncomingWebResponse GetHostMeta(UriIdentifier identifier, IDirectWebRequestHandler requestHandler, out string signingHost) {
+ Contract.Requires<ArgumentNullException>(identifier != null);
+ Contract.Requires<ArgumentNullException>(requestHandler != null);
+ foreach (var hostMetaProxy in this.GetHostMetaLocations(identifier)) {
+ var hostMetaLocation = hostMetaProxy.GetProxy(identifier);
+ var request = (HttpWebRequest)WebRequest.Create(hostMetaLocation);
+ request.CachePolicy = Yadis.IdentifierDiscoveryCachePolicy;
+ var options = DirectWebRequestOptions.AcceptAllHttpResponses;
+ if (identifier.IsDiscoverySecureEndToEnd) {
+ options |= DirectWebRequestOptions.RequireSsl;
+ }
+ var response = requestHandler.GetResponse(request, options);
+ if (response.Status == HttpStatusCode.OK) {
+ Logger.Yadis.InfoFormat("Found host-meta for {0} at: {1}", identifier.Uri.Host, hostMetaLocation);
+ signingHost = hostMetaProxy.GetSigningHost(identifier);
+ return response;
+ } else {
+ Logger.Yadis.InfoFormat("Could not obtain host-meta for {0} from {1}", identifier.Uri.Host, hostMetaLocation);
+ }
+ }
+
+ signingHost = null;
+ return null;
+ }
+
+ /// <summary>
+ /// Gets the URIs authorized to host host-meta documents on behalf of a given domain.
+ /// </summary>
+ /// <param name="identifier">The identifier.</param>
+ /// <returns>A sequence of URIs that MAY provide the host-meta for a given identifier.</returns>
+ private IEnumerable<HostMetaProxy> GetHostMetaLocations(UriIdentifier identifier) {
+ Contract.Requires<ArgumentNullException>(identifier != null);
+
+ // First try the proxies, as they are considered more "secure" than the local
+ // host-meta for a domain since the domain may be defaced.
+ IEnumerable<HostMetaProxy> result = this.TrustedHostMetaProxies;
+
+ // Finally, look for the local host-meta.
+ UriBuilder localHostMetaBuilder = new UriBuilder();
+ localHostMetaBuilder.Scheme = identifier.IsDiscoverySecureEndToEnd || identifier.Uri.IsTransportSecure() ? Uri.UriSchemeHttps : Uri.UriSchemeHttp;
+ localHostMetaBuilder.Host = identifier.Uri.Host;
+ localHostMetaBuilder.Path = LocalHostMetaPath;
+ result = result.Concat(new[] { new HostMetaProxy(localHostMetaBuilder.Uri.AbsoluteUri, identifier.Uri.Host) });
+
+ return result;
+ }
+
+ /// <summary>
+ /// A description of a web server that hosts host-meta documents.
+ /// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "By design")]
+ public class HostMetaProxy {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="HostMetaProxy"/> class.
+ /// </summary>
+ /// <param name="proxyFormat">The proxy formatting string.</param>
+ /// <param name="signingHostFormat">The signing host formatting string.</param>
+ public HostMetaProxy(string proxyFormat, string signingHostFormat) {
+ Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(proxyFormat));
+ Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(signingHostFormat));
+ this.ProxyFormat = proxyFormat;
+ this.SigningHostFormat = signingHostFormat;
+ }
+
+ /// <summary>
+ /// Gets the URL of the host-meta proxy.
+ /// </summary>
+ /// <value>The absolute proxy URL, which may include {0} to be replaced with the host of the identifier to be discovered.</value>
+ public string ProxyFormat { get; private set; }
+
+ /// <summary>
+ /// Gets the formatting string to determine the expected host name on the certificate
+ /// that is expected to be used to sign the XRDS document.
+ /// </summary>
+ /// <value>
+ /// Either a string literal, or a formatting string where these placeholders may exist:
+ /// {0} the host on the identifier discovery was originally performed on;
+ /// {1} the host on this proxy.
+ /// </value>
+ public string SigningHostFormat { get; private set; }
+
+ /// <summary>
+ /// Gets the absolute proxy URI.
+ /// </summary>
+ /// <param name="identifier">The identifier being discovered.</param>
+ /// <returns>The an absolute URI.</returns>
+ public virtual Uri GetProxy(UriIdentifier identifier) {
+ Contract.Requires<ArgumentNullException>(identifier != null);
+ return new Uri(string.Format(CultureInfo.InvariantCulture, this.ProxyFormat, Uri.EscapeDataString(identifier.Uri.Host)));
+ }
+
+ /// <summary>
+ /// Gets the signing host URI.
+ /// </summary>
+ /// <param name="identifier">The identifier being discovered.</param>
+ /// <returns>A host name.</returns>
+ public virtual string GetSigningHost(UriIdentifier identifier) {
+ Contract.Requires<ArgumentNullException>(identifier != null);
+ return string.Format(CultureInfo.InvariantCulture, this.SigningHostFormat, identifier.Uri.Host, this.GetProxy(identifier).Host);
+ }
+
+ /// <summary>
+ /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
+ /// </summary>
+ /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
+ /// <returns>
+ /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
+ /// </returns>
+ /// <exception cref="T:System.NullReferenceException">
+ /// The <paramref name="obj"/> parameter is null.
+ /// </exception>
+ public override bool Equals(object obj) {
+ var other = obj as HostMetaProxy;
+ if (other == null) {
+ return false;
+ }
+
+ return this.ProxyFormat == other.ProxyFormat && this.SigningHostFormat == other.SigningHostFormat;
+ }
+
+ /// <summary>
+ /// Serves as a hash function for a particular type.
+ /// </summary>
+ /// <returns>
+ /// A hash code for the current <see cref="T:System.Object"/>.
+ /// </returns>
+ public override int GetHashCode() {
+ return this.ProxyFormat.GetHashCode();
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/OpenId/IIdentifierDiscoveryService.cs b/src/DotNetOpenAuth/OpenId/IIdentifierDiscoveryService.cs
new file mode 100644
index 0000000..eb2bf98
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/IIdentifierDiscoveryService.cs
@@ -0,0 +1,61 @@
+//-----------------------------------------------------------------------
+// <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.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+
+ /// <summary>
+ /// A module that provides discovery services for OpenID identifiers.
+ /// </summary>
+ [ContractClass(typeof(IIdentifierDiscoveryServiceContract))]
+ public interface IIdentifierDiscoveryService {
+ /// <summary>
+ /// Performs discovery on the specified identifier.
+ /// </summary>
+ /// <param name="identifier">The identifier to perform discovery on.</param>
+ /// <param name="requestHandler">The means to place outgoing HTTP requests.</param>
+ /// <param name="abortDiscoveryChain">if set to <c>true</c>, no further discovery services will be called for this identifier.</param>
+ /// <returns>
+ /// A sequence of service endpoints yielded by discovery. Must not be null, but may be empty.
+ /// </returns>
+ [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "By design")]
+ [Pure]
+ IEnumerable<IdentifierDiscoveryResult> Discover(Identifier identifier, IDirectWebRequestHandler requestHandler, out bool abortDiscoveryChain);
+ }
+
+ /// <summary>
+ /// Code contract for the <see cref="IIdentifierDiscoveryService"/> interface.
+ /// </summary>
+ [ContractClassFor(typeof(IIdentifierDiscoveryService))]
+ internal 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..3190920 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) {
@@ -149,8 +117,15 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
}
} else if (uri != null) {
if (uri != this.Protocol.ClaimedIdentifierForOPIdentifier) {
- string displayUri = uri.Uri.Host + uri.Uri.AbsolutePath;
- displayUri = displayUri.TrimEnd('/');
+ string displayUri = uri.Uri.Host;
+
+ // We typically want to display the path, because that will often have the username in it.
+ // As Google Apps for Domains and the like become more popular, a standard /openid path
+ // will often appear, which is not helpful to identifying the user so we'll avoid including
+ // that path if it's present.
+ if (!string.Equals(uri.Uri.AbsolutePath, "/openid", StringComparison.OrdinalIgnoreCase)) {
+ displayUri += uri.Uri.AbsolutePath.TrimEnd('/');
+ }
// Multi-byte unicode characters get encoded by the Uri class for transit.
// Since this is for display purposes, we want to reverse this and display a readable
@@ -162,57 +137,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 +183,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 +197,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 +213,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 +229,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 +257,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 +272,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 +308,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>
- /// <param name="reader">The reader from which to deserialize the <see cref="ServiceEndpoint"/>.</param>
+ /// <typeparam name="T">The extension whose support is being queried.</typeparam>
/// <returns>
- /// A <see cref="ServiceEndpoint"/> object that has everything
- /// except the <see cref="ProviderSupportedServiceTypeUris"/>
- /// deserialized.
+ /// True if support for the extension is advertised. False otherwise.
/// </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;
+ /// <remarks>
+ /// Note that a true or false return value is no guarantee of a Provider's
+ /// support for or lack of support for an extension. The return value is
+ /// determined by how the authenticating user filled out his/her XRDS document only.
+ /// The only way to be sure of support for a given extension is to include
+ /// the extension in the request and see if a response comes back for that extension.
+ /// </remarks>
+ [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "No parameter at all.")]
+ public bool IsExtensionSupported<T>() where T : IOpenIdMessageExtension, new() {
+ T extension = new T();
+ return this.IsExtensionSupported(extension);
+ }
+
+ /// <summary>
+ /// Checks whether the OpenId Identifier claims support for a given extension.
+ /// </summary>
+ /// <param name="extensionType">The extension whose support is being queried.</param>
+ /// <returns>
+ /// True if support for the extension is advertised. False otherwise.
+ /// </returns>
+ /// <remarks>
+ /// Note that a true or false return value is no guarantee of a Provider's
+ /// support for or lack of support for an extension. The return value is
+ /// determined by how the authenticating user filled out his/her XRDS document only.
+ /// The only way to be sure of support for a given extension is to include
+ /// the extension in the request and see if a response comes back for that extension.
+ /// </remarks>
+ public bool IsExtensionSupported(Type extensionType) {
+ var extension = (IOpenIdMessageExtension)Activator.CreateInstance(extensionType);
+ return this.IsExtensionSupported(extension);
+ }
+
+ /// <summary>
+ /// Determines whether a given extension is supported by this endpoint.
+ /// </summary>
+ /// <param name="extension">An instance of the extension to check support for.</param>
+ /// <returns>
+ /// <c>true</c> if the extension is supported by this endpoint; otherwise, <c>false</c>.
+ /// </returns>
+ public bool IsExtensionSupported(IOpenIdMessageExtension extension) {
+ 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 +406,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 +427,30 @@ 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>
+ /// Sets the Capabilities property (this method is a test hook.)
+ /// </summary>
+ /// <param name="value">The value.</param>
+ /// <remarks>The publicize.exe tool should work for the unit tests, but for some reason it fails on the build server.</remarks>
+ internal void SetCapabilitiesForTestHook(ReadOnlyCollection<string> value) {
+ this.Capabilities = value;
}
/// <summary>
@@ -495,22 +459,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/OpenIdStrings.Designer.cs b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs
index 33a16f8..adfe4ee 100644
--- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs
+++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs
@@ -722,6 +722,15 @@ namespace DotNetOpenAuth.OpenId {
}
/// <summary>
+ /// Looks up a localized string similar to The X.509 certificate used to sign this document is not trusted..
+ /// </summary>
+ internal static string X509CertificateNotTrusted {
+ get {
+ return ResourceManager.GetString("X509CertificateNotTrusted", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to XRI support has been disabled at this site..
/// </summary>
internal static string XriResolutionDisabled {
diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx
index c5f506d..ae68fe6 100644
--- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx
+++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx
@@ -346,4 +346,7 @@ Discovered endpoint info:
<data name="PropertyNotSet" xml:space="preserve">
<value>The {0} property must be set first.</value>
</data>
+ <data name="X509CertificateNotTrusted" xml:space="preserve">
+ <value>The X.509 certificate used to sign this document is not trusted.</value>
+ </data>
</root> \ No newline at end of file
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..00468ed 100644
--- a/src/DotNetOpenAuth/OpenId/OpenIdXrdsHelper.cs
+++ b/src/DotNetOpenAuth/OpenId/OpenIdXrdsHelper.cs
@@ -27,6 +27,9 @@ namespace DotNetOpenAuth.OpenId {
/// or for Provider's to perform RP discovery/verification as part of authentication.
/// </remarks>
internal static IEnumerable<RelyingPartyEndpointDescription> FindRelyingPartyReceivingEndpoints(this XrdsDocument xrds) {
+ Contract.Requires<ArgumentNullException>(xrds != null);
+ Contract.Ensures(Contract.Result<IEnumerable<RelyingPartyEndpointDescription>>() != null);
+
return from service in xrds.FindReturnToServices()
from uri in service.UriElements
select new RelyingPartyEndpointDescription(uri.Uri, service.TypeElementUris);
@@ -39,6 +42,9 @@ namespace DotNetOpenAuth.OpenId {
/// <param name="xrds">The XrdsDocument to search.</param>
/// <returns>A sequence of the icon URLs in preferred order.</returns>
internal static IEnumerable<Uri> FindRelyingPartyIcons(this XrdsDocument xrds) {
+ Contract.Requires<ArgumentNullException>(xrds != null);
+ Contract.Ensures(Contract.Result<IEnumerable<Uri>>() != null);
+
return from xrd in xrds.XrdElements
from service in xrd.OpenIdRelyingPartyIcons
from uri in service.UriElements
@@ -55,15 +61,16 @@ 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>();
+ internal static IEnumerable<IdentifierDiscoveryResult> CreateServiceEndpoints(this IEnumerable<XrdElement> xrds, UriIdentifier claimedIdentifier, UriIdentifier userSuppliedIdentifier) {
+ Contract.Requires<ArgumentNullException>(xrds != null);
+ Contract.Requires<ArgumentNullException>(claimedIdentifier != null);
+ Contract.Requires<ArgumentNullException>(userSuppliedIdentifier != null);
+ Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null);
+
+ var endpoints = new List<IdentifierDiscoveryResult>();
endpoints.AddRange(xrds.GenerateOPIdentifierServiceEndpoints(userSuppliedIdentifier));
+ endpoints.AddRange(xrds.GenerateClaimedIdentifierServiceEndpoints(claimedIdentifier, userSuppliedIdentifier));
- // 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));
- }
Logger.Yadis.DebugFormat("Total services discovered in XRDS: {0}", endpoints.Count);
Logger.Yadis.Debug(endpoints.ToStringDeferred(true));
return endpoints;
@@ -76,18 +83,14 @@ 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 IEnumerable<XrdElement> 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>();
- endpoints.AddRange(xrds.GenerateOPIdentifierServiceEndpoints(userSuppliedIdentifier));
+ Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null);
- // 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(userSuppliedIdentifier));
- }
+ var endpoints = new List<IdentifierDiscoveryResult>();
+ endpoints.AddRange(xrds.GenerateOPIdentifierServiceEndpoints(userSuppliedIdentifier));
+ endpoints.AddRange(xrds.GenerateClaimedIdentifierServiceEndpoints(userSuppliedIdentifier));
Logger.Yadis.DebugFormat("Total services discovered in XRDS: {0}", endpoints.Count);
Logger.Yadis.Debug(endpoints.ToStringDeferred(true));
return endpoints;
@@ -99,15 +102,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 IEnumerable<XrdElement> 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 +123,16 @@ 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 IEnumerable<XrdElement> xrds, UriIdentifier claimedIdentifier, UriIdentifier userSuppliedIdentifier) {
+ Contract.Requires<ArgumentNullException>(xrds != null);
+ Contract.Requires<ArgumentNullException>(claimedIdentifier != null);
+ Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null);
+
return from service in xrds.FindClaimedIdentifierServices()
from uri in service.UriElements
where uri.Uri != null
let providerEndpoint = new ProviderEndpointDescription(uri.Uri, service.TypeElementUris)
- select 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 +142,12 @@ 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 IEnumerable<XrdElement> xrds, XriIdentifier userSuppliedIdentifier) {
+ // Cannot use code contracts because this method uses yield return.
+ ////Contract.Requires<ArgumentNullException>(xrds != null);
+ ////Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null);
+ ErrorUtilities.VerifyArgumentNotNull(xrds, "xrds");
+
foreach (var service in xrds.FindClaimedIdentifierServices()) {
foreach (var uri in service.UriElements) {
// spec section 7.3.2.3 on Claimed Id -> CanonicalID substitution
@@ -148,7 +160,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);
}
}
}
@@ -158,8 +170,11 @@ namespace DotNetOpenAuth.OpenId {
/// </summary>
/// <param name="xrds">The XrdsDocument instance to use in this process.</param>
/// <returns>A sequence of service elements.</returns>
- private static IEnumerable<ServiceElement> FindOPIdentifierServices(this XrdsDocument xrds) {
- return from xrd in xrds.XrdElements
+ private static IEnumerable<ServiceElement> FindOPIdentifierServices(this IEnumerable<XrdElement> xrds) {
+ Contract.Requires<ArgumentNullException>(xrds != null);
+ Contract.Ensures(Contract.Result<IEnumerable<ServiceElement>>() != null);
+
+ return from xrd in xrds
from service in xrd.OpenIdProviderIdentifierServices
select service;
}
@@ -170,8 +185,11 @@ namespace DotNetOpenAuth.OpenId {
/// </summary>
/// <param name="xrds">The XrdsDocument instance to use in this process.</param>
/// <returns>A sequence of the services offered.</returns>
- private static IEnumerable<ServiceElement> FindClaimedIdentifierServices(this XrdsDocument xrds) {
- return from xrd in xrds.XrdElements
+ private static IEnumerable<ServiceElement> FindClaimedIdentifierServices(this IEnumerable<XrdElement> xrds) {
+ Contract.Requires<ArgumentNullException>(xrds != null);
+ Contract.Ensures(Contract.Result<IEnumerable<ServiceElement>>() != null);
+
+ return from xrd in xrds
from service in xrd.OpenIdClaimedIdentifierServices
select service;
}
@@ -183,6 +201,9 @@ namespace DotNetOpenAuth.OpenId {
/// <param name="xrds">The XrdsDocument instance to use in this process.</param>
/// <returns>A sequence of service elements.</returns>
private static IEnumerable<ServiceElement> FindReturnToServices(this XrdsDocument xrds) {
+ Contract.Requires<ArgumentNullException>(xrds != null);
+ Contract.Ensures(Contract.Result<IEnumerable<ServiceElement>>() != null);
+
return from xrd in xrds.XrdElements
from service in xrd.OpenIdRelyingPartyReturnToServices
select service;
diff --git a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs
index 827ca6c..10c69a3 100644
--- a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs
+++ b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs
@@ -20,10 +20,12 @@ 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.
/// </summary>
+ [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "By design")]
[ContractVerification(true)]
public sealed class OpenIdProvider : IDisposable {
/// <summary>
@@ -43,6 +45,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()
@@ -160,6 +168,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; }
@@ -173,6 +188,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>
@@ -314,7 +348,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
@@ -323,15 +357,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>
@@ -340,7 +374,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
@@ -353,10 +387,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);
@@ -366,8 +400,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}",
@@ -385,11 +419,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,
@@ -428,6 +462,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/Realm.cs b/src/DotNetOpenAuth/OpenId/Realm.cs
index 600e6c0..818718a 100644
--- a/src/DotNetOpenAuth/OpenId/Realm.cs
+++ b/src/DotNetOpenAuth/OpenId/Realm.cs
@@ -13,6 +13,7 @@ namespace DotNetOpenAuth.OpenId {
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
+ using System.Web;
using System.Xml;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.Xrds;
@@ -98,6 +99,38 @@ namespace DotNetOpenAuth.OpenId {
: this(SafeUriBuilderToString(realmUriBuilder)) { }
/// <summary>
+ /// Gets the suggested realm to use for the calling web application.
+ /// </summary>
+ /// <value>A realm that matches this applications root URL.</value>
+ /// <remarks>
+ /// <para>For most circumstances the Realm generated by this property is sufficient.
+ /// However a wildcard Realm, such as "http://*.microsoft.com/" may at times be more
+ /// desirable than "http://www.microsoft.com/" in order to allow identifier
+ /// correlation across related web sites for directed identity Providers.</para>
+ /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para>
+ /// </remarks>
+ public static Realm AutoDetect {
+ get {
+ Contract.Requires<InvalidOperationException>(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired);
+ HttpRequestInfo requestInfo = new HttpRequestInfo(HttpContext.Current.Request);
+ UriBuilder realmUrl = new UriBuilder(requestInfo.UrlBeforeRewriting);
+ realmUrl.Path = HttpContext.Current.Request.ApplicationPath;
+ realmUrl.Query = null;
+ realmUrl.Fragment = null;
+
+ // For RP discovery, the realm url MUST NOT redirect. To prevent this for
+ // virtual directory hosted apps, we need to make sure that the realm path ends
+ // in a slash (since our calculation above guarantees it doesn't end in a specific
+ // page like default.aspx).
+ if (!realmUrl.Path.EndsWith("/", StringComparison.Ordinal)) {
+ realmUrl.Path += "/";
+ }
+
+ return realmUrl.Uri;
+ }
+ }
+
+ /// <summary>
/// Gets a value indicating whether a '*.' prefix to the hostname is
/// used in the realm to allow subdomains or hosts to be added to the URL.
/// </summary>
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 a317b95..e90c1d3 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,23 @@ 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
+ // Usually the Discover method we called will automatically filter this for us, but
+ // just to be sure, we'll do it here as well since the RP may be configured to allow
+ // these dual identifiers for assertion verification purposes.
+ 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 +384,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 +412,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 +424,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.DebugFormat("Creating authentication request for user supplied Identifier: {0}", userSuppliedIdentifier);
@@ -424,7 +440,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 +483,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 +502,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 +534,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 +564,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 +572,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 55d6403..c9106de 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);
@@ -116,8 +126,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>
@@ -199,7 +209,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;
}
@@ -229,6 +239,13 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
}
/// <summary>
+ /// Gets the list of services that can perform discovery on identifiers given to this relying party.
+ /// </summary>
+ public 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>
@@ -456,21 +473,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
Contract.Requires<InvalidOperationException>(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired);
Contract.Ensures(Contract.Result<IEnumerable<IAuthenticationRequest>>() != null);
- // Build the realm URL
- UriBuilder realmUrl = new UriBuilder(this.Channel.GetRequestFromContext().UrlBeforeRewriting);
- realmUrl.Path = HttpContext.Current.Request.ApplicationPath;
- realmUrl.Query = null;
- realmUrl.Fragment = null;
-
- // For RP discovery, the realm url MUST NOT redirect. To prevent this for
- // virtual directory hosted apps, we need to make sure that the realm path ends
- // in a slash (since our calculation above guarantees it doesn't end in a specific
- // page like default.aspx).
- if (!realmUrl.Path.EndsWith("/", StringComparison.Ordinal)) {
- realmUrl.Path += "/";
- }
-
- return this.CreateRequests(userSuppliedIdentifier, new Realm(realmUrl.Uri));
+ return this.CreateRequests(userSuppliedIdentifier, Realm.AutoDetect);
}
/// <summary>
@@ -574,6 +577,42 @@ 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;
+ }
+ }
+
+ // If any OP Identifier service elements were found, we must not proceed
+ // to use any Claimed Identifier services, per OpenID 2.0 sections 7.3.2.2 and 11.2.
+ // For a discussion on this topic, see
+ // http://groups.google.com/group/dotnetopenid/browse_thread/thread/4b5a8c6b2210f387/5e25910e4d2252c8
+ // Sometimes the IIdentifierDiscoveryService will automatically filter this for us, but
+ // just to be sure, we'll do it here as well.
+ if (!this.SecuritySettings.AllowDualPurposeIdentifiers) {
+ results = results.CacheGeneratedResults(); // avoid performing discovery repeatedly
+ var opIdentifiers = results.Where(result => result.ClaimedIdentifier == result.Protocol.ClaimedIdentifierForOPIdentifier);
+ var claimedIdentifiers = results.Where(result => result.ClaimedIdentifier != result.Protocol.ClaimedIdentifierForOPIdentifier);
+ results = opIdentifiers.Any() ? opIdentifiers : claimedIdentifiers;
+ }
+
+ return results;
+ }
+
+ /// <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 0254346..12d676b 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs
@@ -565,7 +565,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 02df7a2..a4a60d1 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs
@@ -810,7 +810,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.");
}
@@ -937,7 +937,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 6e7d7ef..5cfa191 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 695aa1e..b6a1b76 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,
@@ -114,7 +114,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.
@@ -155,7 +155,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..071a488 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs
@@ -111,11 +111,26 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
public bool RequireAssociation { get; set; }
/// <summary>
+ /// Gets or sets a value indicating whether identifiers that are both OP Identifiers and Claimed Identifiers
+ /// should ever be recognized as claimed identifiers.
+ /// </summary>
+ /// <value>
+ /// The default value is <c>false</c>, per the OpenID 2.0 spec.
+ /// </value>
+ /// <remarks>
+ /// OpenID 2.0 sections 7.3.2.2 and 11.2 specify that OP Identifiers never be recognized as Claimed Identifiers.
+ /// However, for some scenarios it may be desirable for an RP to override this behavior and allow this.
+ /// The security ramifications of setting this property to <c>true</c> have not been fully explored and
+ /// therefore this setting should only be changed with caution.
+ /// </remarks>
+ public bool AllowDualPurposeIdentifiers { get; set; }
+
+ /// <summary>
/// Filters out any disallowed endpoints.
/// </summary>
/// <param name="endpoints">The endpoints discovered on an Identifier.</param>
/// <returns>A sequence of endpoints that satisfy all security requirements.</returns>
- internal IEnumerable<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..f017d9f
--- /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.XrdElements.CreateServiceEndpoints(yadisResult.NormalizedUri, uriIdentifier);
+
+ // Filter out insecure endpoints if high security is required.
+ if (uriIdentifier.IsDiscoverySecureEndToEnd) {
+ xrdsEndpoints = xrdsEndpoints.Where(se => se.ProviderEndpoint.IsTransportSecure());
+ }
+ endpoints.AddRange(xrdsEndpoints);
+ } catch (XmlException ex) {
+ Logger.Yadis.Error("Error while parsing the XRDS document. Falling back to HTML discovery.", ex);
+ }
+ }
+
+ // Failing YADIS discovery of an XRDS document, we try HTML discovery.
+ if (endpoints.Count == 0) {
+ 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..b1a3430
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/XriDiscoveryProxyService.cs
@@ -0,0 +1,108 @@
+//-----------------------------------------------------------------------
+// <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.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.Linq;
+ using System.Text;
+ using System.Xml;
+ using DotNetOpenAuth.Configuration;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+ using DotNetOpenAuth.Xrds;
+ using DotNetOpenAuth.Yadis;
+
+ /// <summary>
+ /// The discovery service for XRI identifiers that uses an XRI proxy resolver for discovery.
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Xri", Justification = "Acronym")]
+ public class XriDiscoveryProxyService : IIdentifierDiscoveryService {
+ /// <summary>
+ /// The magic URL that will provide us an XRDS document for a given XRI identifier.
+ /// </summary>
+ /// <remarks>
+ /// We use application/xrd+xml instead of application/xrds+xml because it gets
+ /// xri.net to automatically give us exactly the right XRD element for community i-names
+ /// automatically, saving us having to choose which one to use out of the result.
+ /// The ssl=true parameter tells the proxy resolver to accept only SSL connections
+ /// when resolving community i-names.
+ /// </remarks>
+ private const string XriResolverProxyTemplate = "https://{1}/{0}?_xrd_r=application/xrd%2Bxml;sep=false";
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="XriDiscoveryProxyService"/> class.
+ /// </summary>
+ public XriDiscoveryProxyService() {
+ }
+
+ #region IDiscoveryService Members
+
+ /// <summary>
+ /// Performs discovery on the specified identifier.
+ /// </summary>
+ /// <param name="identifier">The identifier to perform discovery on.</param>
+ /// <param name="requestHandler">The means to place outgoing HTTP requests.</param>
+ /// <param name="abortDiscoveryChain">if set to <c>true</c>, no further discovery services will be called for this identifier.</param>
+ /// <returns>
+ /// A sequence of service endpoints yielded by discovery. Must not be null, but may be empty.
+ /// </returns>
+ public IEnumerable<IdentifierDiscoveryResult> Discover(Identifier identifier, IDirectWebRequestHandler requestHandler, out bool abortDiscoveryChain) {
+ abortDiscoveryChain = false;
+ var xriIdentifier = identifier as XriIdentifier;
+ if (xriIdentifier == null) {
+ return Enumerable.Empty<IdentifierDiscoveryResult>();
+ }
+
+ return DownloadXrds(xriIdentifier, requestHandler).XrdElements.CreateServiceEndpoints(xriIdentifier);
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Downloads the XRDS document for this XRI.
+ /// </summary>
+ /// <param name="identifier">The identifier.</param>
+ /// <param name="requestHandler">The request handler.</param>
+ /// <returns>The XRDS document.</returns>
+ private static XrdsDocument DownloadXrds(XriIdentifier identifier, IDirectWebRequestHandler requestHandler) {
+ 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 baba2e5..729f603 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>
@@ -180,30 +142,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.
@@ -255,22 +193,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.
@@ -279,7 +201,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
diff --git a/src/DotNetOpenAuth/Xrds/XrdElement.cs b/src/DotNetOpenAuth/Xrds/XrdElement.cs
index a8cc145..2cdc720 100644
--- a/src/DotNetOpenAuth/Xrds/XrdElement.cs
+++ b/src/DotNetOpenAuth/Xrds/XrdElement.cs
@@ -133,7 +133,7 @@ namespace DotNetOpenAuth.Xrds {
/// </summary>
/// <param name="p">A function that selects what element of the OpenID Protocol we're interested in finding.</param>
/// <returns>A sequence of service elements that match the search criteria, sorted in XRDS @priority attribute order.</returns>
- private IEnumerable<ServiceElement> SearchForServiceTypeUris(Func<Protocol, string> p) {
+ internal IEnumerable<ServiceElement> SearchForServiceTypeUris(Func<Protocol, string> p) {
var xpath = new StringBuilder();
xpath.Append("xrd:Service[");
foreach (var protocol in Protocol.AllVersions) {
diff --git a/src/DotNetOpenAuth/Xrds/XrdsDocument.cs b/src/DotNetOpenAuth/Xrds/XrdsDocument.cs
index dd0e19f..e2c2d72 100644
--- a/src/DotNetOpenAuth/Xrds/XrdsDocument.cs
+++ b/src/DotNetOpenAuth/Xrds/XrdsDocument.cs
@@ -18,6 +18,16 @@ namespace DotNetOpenAuth.Xrds {
/// </summary>
internal class XrdsDocument : XrdsNode {
/// <summary>
+ /// The namespace used by XML digital signatures.
+ /// </summary>
+ private const string XmlDSigNamespace = "http://www.w3.org/2000/09/xmldsig#";
+
+ /// <summary>
+ /// The namespace used by Google Apps for Domains for OpenID URI templates.
+ /// </summary>
+ private const string GoogleOpenIdNamespace = "http://namespace.google.com/openid/xmlns";
+
+ /// <summary>
/// Initializes a new instance of the <see cref="XrdsDocument"/> class.
/// </summary>
/// <param name="xrdsNavigator">The root node of the XRDS document.</param>
@@ -26,6 +36,8 @@ namespace DotNetOpenAuth.Xrds {
XmlNamespaceResolver.AddNamespace("xrd", XrdsNode.XrdNamespace);
XmlNamespaceResolver.AddNamespace("xrds", XrdsNode.XrdsNamespace);
XmlNamespaceResolver.AddNamespace("openid10", Protocol.V10.XmlNamespace);
+ XmlNamespaceResolver.AddNamespace("ds", XmlDSigNamespace);
+ XmlNamespaceResolver.AddNamespace("google", GoogleOpenIdNamespace);
}
/// <summary>
diff --git a/src/DotNetOpenAuth/Xrds/XrdsNode.cs b/src/DotNetOpenAuth/Xrds/XrdsNode.cs
index f8fa0af..39bd9b9 100644
--- a/src/DotNetOpenAuth/Xrds/XrdsNode.cs
+++ b/src/DotNetOpenAuth/Xrds/XrdsNode.cs
@@ -54,16 +54,16 @@ namespace DotNetOpenAuth.Xrds {
/// <summary>
/// Gets the node.
/// </summary>
- protected XPathNavigator Node { get; private set; }
+ internal XPathNavigator Node { get; private set; }
/// <summary>
/// Gets the parent node, or null if this is the root node.
/// </summary>
- protected XrdsNode ParentNode { get; private set; }
+ protected internal XrdsNode ParentNode { get; private set; }
/// <summary>
/// Gets the XML namespace resolver to use in XPath expressions.
/// </summary>
- protected XmlNamespaceManager XmlNamespaceResolver { get; private set; }
+ protected internal XmlNamespaceManager XmlNamespaceResolver { get; private set; }
}
}