diff options
Diffstat (limited to 'src/DotNetOpenAuth')
46 files changed, 1134 insertions, 122 deletions
diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj index 5dad6f0..309b731 100644 --- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj +++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj @@ -229,8 +229,10 @@ <Compile Include="Messaging\Reflection\IMessagePartEncoder.cs" /> <Compile Include="Messaging\Reflection\IMessagePartNullEncoder.cs" /> <Compile Include="Messaging\Reflection\MessageDescriptionCollection.cs" /> + <Compile Include="OAuth\ChannelElements\ICombinedOpenIdProviderTokenManager.cs" /> <Compile Include="OAuth\ChannelElements\IConsumerDescription.cs" /> <Compile Include="OAuth\ChannelElements\IConsumerTokenManager.cs" /> + <Compile Include="OAuth\ChannelElements\IOpenIdOAuthTokenManager.cs" /> <Compile Include="OAuth\ChannelElements\IServiceProviderTokenManager.cs" /> <Compile Include="OAuth\ChannelElements\OAuthConsumerMessageFactory.cs" /> <Compile Include="OAuth\ChannelElements\ITokenGenerator.cs" /> @@ -343,6 +345,10 @@ <Compile Include="OpenId\Extensions\ExtensionBase.cs" /> <Compile Include="OpenId\Extensions\ExtensionArgumentsManager.cs" /> <Compile Include="OpenId\Extensions\IClientScriptExtensionResponse.cs" /> + <Compile Include="OpenId\Extensions\OAuth\AuthorizationRequest.cs" /> + <Compile Include="OpenId\Extensions\OAuth\AuthorizationApprovedResponse.cs" /> + <Compile Include="OpenId\Extensions\OAuth\Constants.cs" /> + <Compile Include="OpenId\Extensions\OAuth\AuthorizationDeclinedResponse.cs" /> <Compile Include="OpenId\Extensions\OpenIdExtensionFactoryAggregator.cs" /> <Compile Include="OpenId\Extensions\StandardOpenIdExtensionFactory.cs" /> <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\AuthenticationPolicies.cs" /> diff --git a/src/DotNetOpenAuth/GlobalSuppressions.cs b/src/DotNetOpenAuth/GlobalSuppressions.cs index 48c749f..f5268d4 100644 --- a/src/DotNetOpenAuth/GlobalSuppressions.cs +++ b/src/DotNetOpenAuth/GlobalSuppressions.cs @@ -40,6 +40,7 @@ [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "DotNetOpenAuth.OpenId.Messages")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", Justification = "We sign it when producing drops.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "DotNetOpenAuth.OpenId.Extensions.OAuth")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "DotNetOpenAuth.Messaging.Reflection")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "oauthverifier", Scope = "resource", Target = "DotNetOpenAuth.OAuth.OAuthStrings.resources")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "whitelist", Scope = "resource", Target = "DotNetOpenAuth.OpenId.OpenIdStrings.resources")] diff --git a/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs b/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs index efc8896..fe2c2a2 100644 --- a/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs +++ b/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs @@ -16,6 +16,8 @@ namespace DotNetOpenAuth.InfoCard { using System.Drawing.Design; using System.Globalization; using System.Linq; + using System.Text.RegularExpressions; + using System.Web; using System.Web.UI; using System.Web.UI.HtmlControls; using System.Web.UI.WebControls; @@ -192,6 +194,13 @@ namespace DotNetOpenAuth.InfoCard { private bool audienceSet; /// <summary> + /// Initializes a new instance of the <see cref="InfoCardSelector"/> class. + /// </summary> + public InfoCardSelector() { + this.ToolTip = InfoCardStrings.SelectorClickPrompt; + } + + /// <summary> /// Occurs when an InfoCard has been submitted but not decoded yet. /// </summary> [Category(InfoCardCategory)] @@ -255,9 +264,30 @@ namespace DotNetOpenAuth.InfoCard { /// </summary> [Description("The URL to this site's privacy policy.")] [Category(InfoCardCategory), DefaultValue(PrivacyUrlDefault)] + [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Uri", Justification = "We construct a Uri to validate the format of the string.")] + [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "That overload is NOT the same.")] public string PrivacyUrl { - get { return (string)this.ViewState[PrivacyUrlViewStateKey] ?? PrivacyUrlDefault; } - set { this.ViewState[PrivacyUrlViewStateKey] = value; } + get { + return (string)this.ViewState[PrivacyUrlViewStateKey] ?? PrivacyUrlDefault; + } + + set { + if (this.Page != null && !this.DesignMode) { + // Validate new value by trying to construct a Uri based on it. + new Uri(new HttpRequestInfo(HttpContext.Current.Request).UrlBeforeRewriting, this.Page.ResolveUrl(value)); // throws an exception on failure. + } else { + // We can't fully test it, but it should start with either ~/ or a protocol. + if (Regex.IsMatch(value, @"^https?://")) { + new Uri(value); // make sure it's fully-qualified, but ignore wildcards + } else if (value.StartsWith("~/", StringComparison.Ordinal)) { + // this is valid too + } else { + throw new UriFormatException(); + } + } + + this.ViewState[PrivacyUrlViewStateKey] = value; + } } /// <summary> @@ -491,6 +521,20 @@ namespace DotNetOpenAuth.InfoCard { } /// <summary> + /// Raises the <see cref="E:System.Web.UI.Control.PreRender"/> event. + /// </summary> + /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param> + protected override void OnPreRender(EventArgs e) { + base.OnPreRender(e); + + if (!this.DesignMode) { + // The Cardspace selector will display an ugly error to the user if + // the privacy URL is present but the privacy version is not. + ErrorUtilities.VerifyOperation(string.IsNullOrEmpty(this.PrivacyUrl) || !string.IsNullOrEmpty(this.PrivacyVersion), InfoCardStrings.PrivacyVersionRequiredWithPrivacyUrl); + } + } + + /// <summary> /// Creates a control that renders to <Param Name="{0}" Value="{1}" /> /// </summary> /// <param name="name">The parameter name.</param> @@ -527,7 +571,7 @@ namespace DotNetOpenAuth.InfoCard { Image image = new Image(); image.ImageUrl = this.Page.ClientScript.GetWebResourceUrl(typeof(InfoCardSelector), InfoCardImage.GetImageManifestResourceStreamName(this.ImageSize)); image.AlternateText = InfoCardStrings.SelectorClickPrompt; - image.ToolTip = InfoCardStrings.SelectorClickPrompt; + image.ToolTip = this.ToolTip; image.Style[HtmlTextWriterStyle.Cursor] = "hand"; image.Attributes["onclick"] = this.GetInfoCardSelectorActivationScript(false); @@ -618,7 +662,8 @@ namespace DotNetOpenAuth.InfoCard { } if (!string.IsNullOrEmpty(this.PrivacyUrl)) { - cardSpaceControl.Controls.Add(CreateParam("privacyUrl", this.PrivacyUrl)); + string privacyUrl = this.DesignMode ? this.PrivacyUrl : new Uri(Page.Request.Url, Page.ResolveUrl(this.PrivacyUrl)).AbsoluteUri; + cardSpaceControl.Controls.Add(CreateParam("privacyUrl", privacyUrl)); } if (!string.IsNullOrEmpty(this.PrivacyVersion)) { diff --git a/src/DotNetOpenAuth/InfoCard/InfoCardStrings.Designer.cs b/src/DotNetOpenAuth/InfoCard/InfoCardStrings.Designer.cs index 4b1dc60..00eb1af 100644 --- a/src/DotNetOpenAuth/InfoCard/InfoCardStrings.Designer.cs +++ b/src/DotNetOpenAuth/InfoCard/InfoCardStrings.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:2.0.50727.3521 +// Runtime Version:2.0.50727.4918 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -97,6 +97,15 @@ namespace DotNetOpenAuth.InfoCard { } /// <summary> + /// Looks up a localized string similar to The PrivacyVersion property must be set whenever the PrivacyUrl property is set.. + /// </summary> + internal static string PrivacyVersionRequiredWithPrivacyUrl { + get { + return ResourceManager.GetString("PrivacyVersionRequiredWithPrivacyUrl", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to Click here to select your Information Card.. /// </summary> internal static string SelectorClickPrompt { diff --git a/src/DotNetOpenAuth/InfoCard/InfoCardStrings.resx b/src/DotNetOpenAuth/InfoCard/InfoCardStrings.resx index e82e8cd..956b321 100644 --- a/src/DotNetOpenAuth/InfoCard/InfoCardStrings.resx +++ b/src/DotNetOpenAuth/InfoCard/InfoCardStrings.resx @@ -129,6 +129,9 @@ <data name="PpidClaimRequired" xml:space="preserve"> <value>This operation requires the PPID claim to be included in the InfoCard token.</value> </data> + <data name="PrivacyVersionRequiredWithPrivacyUrl" xml:space="preserve"> + <value>The PrivacyVersion property must be set whenever the PrivacyUrl property is set.</value> + </data> <data name="SelectorClickPrompt" xml:space="preserve"> <value>Click here to select your Information Card.</value> </data> diff --git a/src/DotNetOpenAuth/Messaging/Channel.cs b/src/DotNetOpenAuth/Messaging/Channel.cs index 7435a90..2e0f1a8 100644 --- a/src/DotNetOpenAuth/Messaging/Channel.cs +++ b/src/DotNetOpenAuth/Messaging/Channel.cs @@ -394,6 +394,8 @@ namespace DotNetOpenAuth.Messaging { public TResponse Request<TResponse>(IDirectedProtocolMessage requestMessage) where TResponse : class, IProtocolMessage { Contract.Requires(requestMessage != null); + Contract.Ensures(Contract.Result<TResponse>() != null); + IProtocolMessage response = this.Request(requestMessage); ErrorUtilities.VerifyProtocol(response != null, MessagingStrings.ExpectedMessageNotReceived, typeof(TResponse)); @@ -565,13 +567,13 @@ namespace DotNetOpenAuth.Messaging { Contract.Requires(request != null); ErrorUtilities.VerifyArgumentNotNull(request, "request"); - Logger.Channel.DebugFormat("Incoming HTTP request: {0}", request.Url.AbsoluteUri); + Logger.Channel.DebugFormat("Incoming HTTP request: {0} {1}", request.HttpMethod, request.UrlBeforeRewriting.AbsoluteUri); // Search Form data first, and if nothing is there search the QueryString - Contract.Assume(request.Form != null && request.QueryString != null); + Contract.Assume(request.Form != null && request.QueryStringBeforeRewriting != null); var fields = request.Form.ToDictionary(); if (fields.Count == 0 && request.HttpMethod != "POST") { // OpenID 2.0 section 4.1.2 - fields = request.QueryString.ToDictionary(); + fields = request.QueryStringBeforeRewriting.ToDictionary(); } return (IDirectedProtocolMessage)this.Receive(fields, request.GetRecipient()); diff --git a/src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs b/src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs index d13da7b..9e9deb4 100644 --- a/src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs +++ b/src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs @@ -10,6 +10,7 @@ namespace DotNetOpenAuth.Messaging { using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; + using System.Globalization; using System.IO; using System.Net; using System.ServiceModel.Channels; @@ -54,6 +55,7 @@ namespace DotNetOpenAuth.Messaging { this.HttpMethod = request.HttpMethod; this.Url = request.Url; + this.UrlBeforeRewriting = GetPublicFacingUrl(request); this.RawUrl = request.RawUrl; this.Headers = GetHeaderCollection(request.Headers); this.InputStream = request.InputStream; @@ -87,6 +89,7 @@ namespace DotNetOpenAuth.Messaging { this.HttpMethod = httpMethod; this.Url = requestUrl; + this.UrlBeforeRewriting = requestUrl; this.RawUrl = rawUrl; this.Headers = headers; this.InputStream = inputStream; @@ -102,6 +105,7 @@ namespace DotNetOpenAuth.Messaging { this.HttpMethod = listenerRequest.HttpMethod; this.Url = listenerRequest.Url; + this.UrlBeforeRewriting = listenerRequest.Url; this.RawUrl = listenerRequest.RawUrl; this.Headers = new WebHeaderCollection(); foreach (string key in listenerRequest.Headers) { @@ -125,6 +129,7 @@ namespace DotNetOpenAuth.Messaging { this.HttpMethod = request.Method; this.Headers = request.Headers; this.Url = requestUri; + this.UrlBeforeRewriting = requestUri; this.RawUrl = MakeUpRawUrlFromUrl(requestUri); } @@ -146,6 +151,7 @@ namespace DotNetOpenAuth.Messaging { this.HttpMethod = request.Method; this.Url = request.RequestUri; + this.UrlBeforeRewriting = request.RequestUri; this.RawUrl = MakeUpRawUrlFromUrl(request.RequestUri); this.Headers = GetHeaderCollection(request.Headers); this.InputStream = null; @@ -190,27 +196,13 @@ namespace DotNetOpenAuth.Messaging { internal string RawUrl { get; set; } /// <summary> - /// Gets the full URL of a request before rewriting. + /// Gets or sets the full public URL used by the remote client to initiate this request, + /// before any URL rewriting and before any changes made by web farm load distributors. /// </summary> - internal Uri UrlBeforeRewriting { - get { - if (this.Url == null || this.RawUrl == null) { - return null; - } - - // We use Request.Url for the full path to the server, and modify it - // with Request.RawUrl to capture both the cookieless session "directory" if it exists - // and the original path in case URL rewriting is going on. We don't want to be - // fooled by URL rewriting because we're comparing the actual URL with what's in - // the return_to parameter in some cases. - // Response.ApplyAppPathModifier(builder.Path) would have worked for the cookieless - // session, but not the URL rewriting problem. - return new Uri(this.Url, this.RawUrl); - } - } + internal Uri UrlBeforeRewriting { get; set; } /// <summary> - /// Gets the query part of the URL (The ? and everything after it). + /// Gets the query part of the URL (The ? and everything after it), after URL rewriting. /// </summary> internal string Query { get { return this.Url != null ? this.Url.Query : null; } @@ -297,7 +289,56 @@ namespace DotNetOpenAuth.Messaging { /// <c>true</c> if this request's URL was rewritten; otherwise, <c>false</c>. /// </value> internal bool IsUrlRewritten { - get { return this.Url.PathAndQuery != this.RawUrl; } + get { return this.Url != this.UrlBeforeRewriting; } + } + + /// <summary> + /// Gets the public facing URL for the given incoming HTTP request. + /// </summary> + /// <param name="request">The request.</param> + /// <param name="serverVariables">The server variables to consider part of the request.</param> + /// <returns> + /// The URI that the outside world used to create this request. + /// </returns> + /// <remarks> + /// Although the <paramref name="serverVariables"/> value can be obtained from + /// <see cref="HttpRequest.ServerVariables"/>, it's useful to be able to pass them + /// in so we can simulate injected values from our unit tests since the actual property + /// is a read-only kind of <see cref="NameValueCollection"/>. + /// </remarks> + internal static Uri GetPublicFacingUrl(HttpRequest request, NameValueCollection serverVariables) { + Contract.Requires(request != null); + Contract.Requires(serverVariables != null); + ErrorUtilities.VerifyArgumentNotNull(request, "request"); + ErrorUtilities.VerifyArgumentNotNull(serverVariables, "serverVariables"); + + // Due to URL rewriting, cloud computing (i.e. Azure) + // and web farms, etc., we have to be VERY careful about what + // we consider the incoming URL. We want to see the URL as it would + // appear on the public-facing side of the hosting web site. + // HttpRequest.Url gives us the internal URL in a cloud environment, + // So we use a variable that (at least from what I can tell) gives us + // the public URL: + if (serverVariables["HTTP_HOST"] != null) { + ErrorUtilities.VerifySupported(request.Url.Scheme == Uri.UriSchemeHttps || request.Url.Scheme == Uri.UriSchemeHttp, "Only HTTP and HTTPS are supported protocols."); + string scheme = serverVariables["HTTP_X_FORWARDED_PROTO"] ?? request.Url.Scheme; + Uri hostAndPort = new Uri(scheme + Uri.SchemeDelimiter + serverVariables["HTTP_HOST"]); + UriBuilder publicRequestUri = new UriBuilder(request.Url); + publicRequestUri.Scheme = scheme; + publicRequestUri.Host = hostAndPort.Host; + publicRequestUri.Port = hostAndPort.Port; + return publicRequestUri.Uri; + } else { + // Failover to the method that works for non-web farm enviroments. + // We use Request.Url for the full path to the server, and modify it + // with Request.RawUrl to capture both the cookieless session "directory" if it exists + // and the original path in case URL rewriting is going on. We don't want to be + // fooled by URL rewriting because we're comparing the actual URL with what's in + // the return_to parameter in some cases. + // Response.ApplyAppPathModifier(builder.Path) would have worked for the cookieless + // session, but not the URL rewriting problem. + return new Uri(request.Url, request.RawUrl); + } } /// <summary> @@ -316,6 +357,18 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Gets the public facing URL for the given incoming HTTP request. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>The URI that the outside world used to create this request.</returns> + private static Uri GetPublicFacingUrl(HttpRequest request) { + Contract.Requires(request != null); + ErrorUtilities.VerifyArgumentNotNull(request, "request"); + + return GetPublicFacingUrl(request, request.ServerVariables); + } + + /// <summary> /// Makes up a reasonable guess at the raw URL from the possibly rewritten URL. /// </summary> /// <param name="url">A full URL.</param> diff --git a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs index 1cb2177..463b556 100644 --- a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs +++ b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs @@ -577,7 +577,7 @@ namespace DotNetOpenAuth.Messaging { /// <param name="request">The request to get recipient information from.</param> /// <returns>The recipient.</returns> internal static MessageReceivingEndpoint GetRecipient(this HttpRequestInfo request) { - return new MessageReceivingEndpoint(request.Url, request.HttpMethod == "GET" ? HttpDeliveryMethods.GetRequest : HttpDeliveryMethods.PostRequest); + return new MessageReceivingEndpoint(request.UrlBeforeRewriting, request.HttpMethod == "GET" ? HttpDeliveryMethods.GetRequest : HttpDeliveryMethods.PostRequest); } /// <summary> diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs index 9da4fee..8979693 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs @@ -114,7 +114,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <summary> /// Ensures the message parts pass basic validation. /// </summary> - /// <param name="parts">The key/value pairs of the serialzied message.</param> + /// <param name="parts">The key/value pairs of the serialized message.</param> internal void EnsureMessagePartsPassBasicValidation(IDictionary<string, string> parts) { try { this.EnsureRequiredMessagePartsArePresent(parts.Keys); diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs index 911093d..db5903e 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs @@ -258,7 +258,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { MessagePart part; if (this.description.Mapping.TryGetValue(key, out part)) { value = part.GetValue(this.message); - return true; + return value != null; } return this.message.ExtraData.TryGetValue(key, out value); } @@ -279,13 +279,28 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <summary> /// Removes all values in the message. /// </summary> - public void Clear() { + public void ClearValues() { foreach (string key in this.Keys) { this.Remove(key); } } /// <summary> + /// Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1"/>. + /// </summary> + /// <exception cref="T:System.NotSupportedException"> + /// The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only. + /// </exception> + /// <remarks> + /// This method cannot be implemented because keys are not guaranteed to be removed + /// since some are inherent to the type of message that this dictionary provides + /// access to. + /// </remarks> + public void Clear() { + throw new NotSupportedException(); + } + + /// <summary> /// Checks whether a named value has been set on the message. /// </summary> /// <param name="item">The name/value pair.</param> diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs index 0732bb2..a8f04ec 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs @@ -188,7 +188,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { this.field.SetValue(message, this.ToValue(value)); } } - } catch (FormatException ex) { + } catch (Exception ex) { throw ErrorUtilities.Wrap(ex, MessagingStrings.MessagePartReadFailure, message.GetType(), this.Name, value); } } diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/ICombinedOpenIdProviderTokenManager.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/ICombinedOpenIdProviderTokenManager.cs new file mode 100644 index 0000000..ff004cb --- /dev/null +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/ICombinedOpenIdProviderTokenManager.cs @@ -0,0 +1,33 @@ +//----------------------------------------------------------------------- +// <copyright file="ICombinedOpenIdProviderTokenManager.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.ChannelElements { + using DotNetOpenAuth.OpenId; + + /// <summary> + /// An interface that providers that play a dual role as OpenID Provider + /// and OAuth Service Provider should implement on their token manager classes. + /// </summary> + /// <remarks> + /// This interface should be implemented by the same class that implements + /// <see cref="ITokenManager"/> in order to enable the OpenID+OAuth extension. + /// </remarks> + public interface ICombinedOpenIdProviderTokenManager { + /// <summary> + /// Gets the OAuth consumer key for a given OpenID relying party realm. + /// </summary> + /// <param name="realm">The relying party's OpenID realm.</param> + /// <returns>The OAuth consumer key for a given OpenID realm.</returns> + /// <para>This is a security-critical function. Since OpenID requests + /// and OAuth extensions for those requests can be formulated by ANYONE + /// (no signing is required by the relying party), and since the response to + /// the authentication will include access the user is granted to the + /// relying party who CLAIMS to be from some realm, it is of paramount + /// importance that the realm is recognized as belonging to the consumer + /// key by the host service provider in order to protect against phishers.</para> + string GetConsumerKey(Realm realm); + } +} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/IOpenIdOAuthTokenManager.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/IOpenIdOAuthTokenManager.cs new file mode 100644 index 0000000..b3ee320 --- /dev/null +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/IOpenIdOAuthTokenManager.cs @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------- +// <copyright file="IOpenIdOAuthTokenManager.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.ChannelElements { + using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.OpenId.Extensions.OAuth; + + /// <summary> + /// Additional methods an <see cref="ITokenManager"/> implementing class + /// may implement to support the OpenID+OAuth extension. + /// </summary> + public interface IOpenIdOAuthTokenManager { + /// <summary> + /// Stores a new request token obtained over an OpenID request. + /// </summary> + /// <param name="consumerKey">The consumer key.</param> + /// <param name="authorization">The authorization message carrying the request token and authorized access scope.</param> + /// <remarks> + /// <para>The token secret is the empty string.</para> + /// <para>Tokens stored by this method should be short-lived to mitigate + /// possible security threats. Their lifetime should be sufficient for the + /// relying party to receive the positive authentication assertion and immediately + /// send a follow-up request for the access token.</para> + /// </remarks> + void StoreOpenIdAuthorizedRequestToken(string consumerKey, AuthorizationApprovedResponse authorization); + } +} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs index ce4c610..d325825 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs @@ -163,7 +163,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { // Add receiving HTTP transport information required for signature generation. var signedMessage = message as ITamperResistantOAuthMessage; if (signedMessage != null) { - signedMessage.Recipient = request.Url; + signedMessage.Recipient = request.UrlBeforeRewriting; signedMessage.HttpMethod = request.HttpMethod; } diff --git a/src/DotNetOpenAuth/OAuth/ConsumerBase.cs b/src/DotNetOpenAuth/OAuth/ConsumerBase.cs index 8c4484b..55b40ac 100644 --- a/src/DotNetOpenAuth/OAuth/ConsumerBase.cs +++ b/src/DotNetOpenAuth/OAuth/ConsumerBase.cs @@ -233,6 +233,10 @@ namespace DotNetOpenAuth.OAuth { /// The access token assigned by the Service Provider. /// </returns> protected AuthorizedTokenResponse ProcessUserAuthorization(string requestToken, string verifier) { + Contract.Requires(!String.IsNullOrEmpty(requestToken)); + Contract.Ensures(Contract.Result<AuthorizedTokenResponse>() != null); + ErrorUtilities.VerifyNonZeroLength(requestToken, "requestToken"); + var requestAccess = new AuthorizedTokenRequest(this.ServiceProvider.AccessTokenEndpoint, this.ServiceProvider.Version) { RequestToken = requestToken, VerificationCode = verifier, diff --git a/src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs b/src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs index 689998a..3593446 100644 --- a/src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs +++ b/src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs @@ -115,6 +115,24 @@ 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.. + /// </summary> + internal static string OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface { + get { + return ResourceManager.GetString("OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The OpenID Relying Party'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); + } + } + + /// <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}.. /// </summary> internal static string MinimumConsumerVersionRequirementNotMet { diff --git a/src/DotNetOpenAuth/OAuth/OAuthStrings.resx b/src/DotNetOpenAuth/OAuth/OAuthStrings.resx index a40b35d..bbeeda9 100644 --- a/src/DotNetOpenAuth/OAuth/OAuthStrings.resx +++ b/src/DotNetOpenAuth/OAuth/OAuthStrings.resx @@ -138,6 +138,12 @@ <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="OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface" xml:space="preserve"> + <value>Use of the OpenID+OAuth extension requires that the token manager in use implement the {0} interface.</value> + </data> + <data name="OpenIdOAuthRealmConsumerKeyDoNotMatch" xml:space="preserve"> + <value>The OpenID Relying Party's realm is not recognized as belonging to the OAuth Consumer identified by the consumer key given.</value> + </data> <data name="RequestUrlMustNotHaveOAuthParameters" xml:space="preserve"> <value>The request URL query MUST NOT contain any OAuth Protocol Parameters.</value> </data> diff --git a/src/DotNetOpenAuth/OAuth/ServiceProvider.cs b/src/DotNetOpenAuth/OAuth/ServiceProvider.cs index 70ecf75..40f7a5e 100644 --- a/src/DotNetOpenAuth/OAuth/ServiceProvider.cs +++ b/src/DotNetOpenAuth/OAuth/ServiceProvider.cs @@ -18,6 +18,10 @@ namespace DotNetOpenAuth.OAuth { using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.OAuth.ChannelElements; using DotNetOpenAuth.OAuth.Messages; + using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.OpenId.Extensions.OAuth; + using DotNetOpenAuth.OpenId.Messages; + using DotNetOpenAuth.OpenId.Provider; /// <summary> /// A web application that allows access via OAuth. @@ -285,6 +289,73 @@ namespace DotNetOpenAuth.OAuth { } /// <summary> + /// Gets the OAuth authorization request included with an OpenID authentication + /// request. + /// </summary> + /// <param name="openIdAuthenticationRequest">The OpenID authentication request.</param> + /// <returns> + /// The scope of access the relying party is requesting. + /// </returns> + /// <remarks> + /// <para>Call this method rather than simply extracting the OAuth extension + /// out from the authentication request directly to ensure that the additional + /// security measures that are required are taken.</para> + /// </remarks> + public AuthorizationRequest ReadAuthorizationRequest(IAuthenticationRequest openIdAuthenticationRequest) { + Contract.Requires(openIdAuthenticationRequest != null); + Contract.Requires(this.TokenManager is ICombinedOpenIdProviderTokenManager); + ErrorUtilities.VerifyArgumentNotNull(openIdAuthenticationRequest, "openIdAuthenticationRequest"); + var openidTokenManager = this.TokenManager as ICombinedOpenIdProviderTokenManager; + ErrorUtilities.VerifyOperation(openidTokenManager != null, OAuthStrings.OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface, typeof(IOpenIdOAuthTokenManager).FullName); + + var authzRequest = openIdAuthenticationRequest.GetExtension<AuthorizationRequest>(); + if (authzRequest == null) { + return null; + } + + // OpenID+OAuth spec section 9: + // The Combined Provider SHOULD verify that the consumer key passed in the + // request is authorized to be used for the realm passed in the request. + string expectedConsumerKey = openidTokenManager.GetConsumerKey(openIdAuthenticationRequest.Realm); + ErrorUtilities.VerifyProtocol( + string.Equals(expectedConsumerKey, authzRequest.Consumer, StringComparison.Ordinal), + OAuthStrings.OpenIdOAuthRealmConsumerKeyDoNotMatch); + + return authzRequest; + } + + /// <summary> + /// Attaches the authorization response to an OpenID authentication response. + /// </summary> + /// <param name="openIdAuthenticationRequest">The OpenID authentication request.</param> + /// <param name="consumerKey">The consumer key. May and should be <c>null</c> if and only if <paramref name="scope"/> is null.</param> + /// <param name="scope">The approved access scope. Use <c>null</c> to indicate no access was granted. The empty string will be interpreted as some default level of access is granted.</param> + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "We want to take IAuthenticationRequest because that's the only supported use case.")] + public void AttachAuthorizationResponse(IAuthenticationRequest openIdAuthenticationRequest, string consumerKey, string scope) { + Contract.Requires(openIdAuthenticationRequest != null); + Contract.Requires((consumerKey == null) == (scope == null)); + Contract.Requires(this.TokenManager is IOpenIdOAuthTokenManager); + ErrorUtilities.VerifyArgumentNotNull(openIdAuthenticationRequest, "openIdAuthenticationRequest"); + var openidTokenManager = this.TokenManager as IOpenIdOAuthTokenManager; + ErrorUtilities.VerifyOperation(openidTokenManager != null, OAuthStrings.OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface, typeof(IOpenIdOAuthTokenManager).FullName); + + IOpenIdMessageExtension response; + if (scope != null) { + // Generate an authorized request token to return to the relying party. + var approvedResponse = new AuthorizationApprovedResponse { + RequestToken = this.TokenGenerator.GenerateRequestToken(consumerKey), + Scope = scope, + }; + openidTokenManager.StoreOpenIdAuthorizedRequestToken(consumerKey, approvedResponse); + response = approvedResponse; + } else { + response = new AuthorizationDeclinedResponse(); + } + + openIdAuthenticationRequest.AddResponseExtension(response); + } + + /// <summary> /// Prepares the message to send back to the consumer following proper authorization of /// a token by an interactive user at the Service Provider's web site. /// </summary> diff --git a/src/DotNetOpenAuth/OAuth/WebConsumer.cs b/src/DotNetOpenAuth/OAuth/WebConsumer.cs index 11f3da8..d86444d 100644 --- a/src/DotNetOpenAuth/OAuth/WebConsumer.cs +++ b/src/DotNetOpenAuth/OAuth/WebConsumer.cs @@ -7,10 +7,13 @@ namespace DotNetOpenAuth.OAuth { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.Web; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth.ChannelElements; using DotNetOpenAuth.OAuth.Messages; + using DotNetOpenAuth.OpenId.Extensions.OAuth; + using DotNetOpenAuth.OpenId.RelyingParty; /// <summary> /// A website or application that uses OAuth to access the Service Provider on behalf of the User. @@ -71,11 +74,77 @@ namespace DotNetOpenAuth.OAuth { } /// <summary> + /// Attaches an OAuth authorization request to an outgoing OpenID authentication request. + /// </summary> + /// <param name="openIdAuthenticationRequest">The OpenID authentication request.</param> + /// <param name="scope">The scope of access that is requested of the service provider.</param> + public void AttachAuthorizationRequest(IAuthenticationRequest openIdAuthenticationRequest, string scope) { + Contract.Requires(openIdAuthenticationRequest != null); + ErrorUtilities.VerifyArgumentNotNull(openIdAuthenticationRequest, "openIdAuthenticationRequest"); + + var authorizationRequest = new AuthorizationRequest { + Consumer = this.ConsumerKey, + Scope = scope, + }; + + openIdAuthenticationRequest.AddExtension(authorizationRequest); + } + + /// <summary> + /// Processes an incoming authorization-granted message from an SP and obtains an access token. + /// </summary> + /// <param name="openIdAuthenticationResponse">The OpenID authentication response that may be carrying an authorized request token.</param> + /// <returns> + /// The access token, or null if OAuth authorization was denied by the user or service provider. + /// </returns> + /// <remarks> + /// The access token, if granted, is automatically stored in the <see cref="ConsumerBase.TokenManager"/>. + /// The token manager instance must implement <see cref="IOpenIdOAuthTokenManager"/>. + /// </remarks> + public AuthorizedTokenResponse ProcessUserAuthorization(IAuthenticationResponse openIdAuthenticationResponse) { + Contract.Requires(openIdAuthenticationResponse != null); + Contract.Requires(this.TokenManager is IOpenIdOAuthTokenManager); + ErrorUtilities.VerifyArgumentNotNull(openIdAuthenticationResponse, "openIdAuthenticationResponse"); + var openidTokenManager = this.TokenManager as IOpenIdOAuthTokenManager; + ErrorUtilities.VerifyOperation(openidTokenManager != null, OAuthStrings.OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface, typeof(IOpenIdOAuthTokenManager).FullName); + + // The OAuth extension is only expected in positive assertion responses. + if (openIdAuthenticationResponse.Status != AuthenticationStatus.Authenticated) { + return null; + } + + // Retrieve the OAuth extension + var positiveAuthorization = openIdAuthenticationResponse.GetExtension<AuthorizationApprovedResponse>(); + if (positiveAuthorization == null) { + return null; + } + + // Prepare a message to exchange the request token for an access token. + // We are careful to use a v1.0 message version so that the oauth_verifier is not required. + var requestAccess = new AuthorizedTokenRequest(this.ServiceProvider.AccessTokenEndpoint, Protocol.V10.Version) { + RequestToken = positiveAuthorization.RequestToken, + ConsumerKey = this.ConsumerKey, + }; + + // Retrieve the access token and store it in the token manager. + openidTokenManager.StoreOpenIdAuthorizedRequestToken(this.ConsumerKey, positiveAuthorization); + var grantAccess = this.Channel.Request<AuthorizedTokenResponse>(requestAccess); + this.TokenManager.ExpireRequestTokenAndStoreNewAccessToken(this.ConsumerKey, positiveAuthorization.RequestToken, grantAccess.AccessToken, grantAccess.TokenSecret); + + // Provide the caller with the access token so it may be associated with the user + // that is logging in. + return grantAccess; + } + + /// <summary> /// Processes an incoming authorization-granted message from an SP and obtains an access token. /// </summary> /// <param name="request">The incoming HTTP request.</param> /// <returns>The access token, or null if no incoming authorization message was recognized.</returns> - internal AuthorizedTokenResponse ProcessUserAuthorization(HttpRequestInfo request) { + public AuthorizedTokenResponse ProcessUserAuthorization(HttpRequestInfo request) { + Contract.Requires(request != null); + ErrorUtilities.VerifyArgumentNotNull(request, "request"); + UserAuthorizationResponse authorizationMessage; if (this.Channel.TryReadFromRequest<UserAuthorizationResponse>(request, out authorizationMessage)) { string requestToken = authorizationMessage.RequestToken; diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/ExtensionsBindingElement.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/ExtensionsBindingElement.cs index 035f389..40ed463 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/ExtensionsBindingElement.cs +++ b/src/DotNetOpenAuth/OpenId/ChannelElements/ExtensionsBindingElement.cs @@ -23,39 +23,13 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// </summary> internal class ExtensionsBindingElement : IChannelBindingElement { /// <summary> - /// The security settings on the Relying Party that is hosting this binding element. - /// </summary> - private RelyingPartySecuritySettings rpSecuritySettings; - - /// <summary> - /// The security settings on the Provider that is hosting this binding element. - /// </summary> - private ProviderSecuritySettings opSecuritySettings; - - /// <summary> - /// Initializes a new instance of the <see cref="ExtensionsBindingElement"/> class. - /// </summary> - /// <param name="extensionFactory">The extension factory.</param> - /// <param name="securitySettings">The security settings to apply.</param> - internal ExtensionsBindingElement(IOpenIdExtensionFactory extensionFactory, RelyingPartySecuritySettings securitySettings) { - ErrorUtilities.VerifyArgumentNotNull(extensionFactory, "extensionFactory"); - ErrorUtilities.VerifyArgumentNotNull(securitySettings, "securitySettings"); - - this.ExtensionFactory = extensionFactory; - this.rpSecuritySettings = securitySettings; - } - - /// <summary> /// Initializes a new instance of the <see cref="ExtensionsBindingElement"/> class. /// </summary> /// <param name="extensionFactory">The extension factory.</param> - /// <param name="securitySettings">The security settings to apply.</param> - internal ExtensionsBindingElement(IOpenIdExtensionFactory extensionFactory, ProviderSecuritySettings securitySettings) { + internal ExtensionsBindingElement(IOpenIdExtensionFactory extensionFactory) { ErrorUtilities.VerifyArgumentNotNull(extensionFactory, "extensionFactory"); - ErrorUtilities.VerifyArgumentNotNull(securitySettings, "securitySettings"); this.ExtensionFactory = extensionFactory; - this.opSecuritySettings = securitySettings; } #region IChannelBindingElement Members @@ -118,7 +92,12 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { // OpenID 2.0 Section 12 forbids two extensions with the same TypeURI in the same message. ErrorUtilities.VerifyProtocol(!extensionManager.ContainsExtension(extension.TypeUri), OpenIdStrings.ExtensionAlreadyAddedWithSameTypeURI, extension.TypeUri); - var extensionDictionary = this.Channel.MessageDescriptions.GetAccessor(extension).Serialize(); + // Ensure that we're sending out a valid extension. + var extensionDescription = this.Channel.MessageDescriptions.Get(extension); + var extensionDictionary = extensionDescription.GetDictionary(extension).Serialize(); + extensionDescription.EnsureMessagePartsPassBasicValidation(extensionDictionary); + + // Add the extension to the outgoing message payload. extensionManager.AddExtensionArguments(extension.TypeUri, extensionDictionary); } else { Logger.OpenId.WarnFormat("Unexpected extension type {0} did not implement {1}.", protocolExtension.GetType(), typeof(IOpenIdMessageExtension).Name); @@ -159,17 +138,62 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { public MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { var extendableMessage = message as IProtocolMessageWithExtensions; if (extendableMessage != null) { - // We have a helper class that will do all the heavy-lifting of organizing - // all the extensions, their aliases, and their parameters. - bool isAtProvider = extendableMessage is SignedResponseRequest; - var extensionManager = ExtensionArgumentsManager.CreateIncomingExtensions(this.GetExtensionsDictionary(message)); - foreach (string typeUri in extensionManager.GetExtensionTypeUris()) { - var extensionData = extensionManager.GetExtensionArguments(typeUri); + // First add the extensions that are signed by the Provider. + foreach (IOpenIdMessageExtension signedExtension in this.GetExtensions(extendableMessage, true, null)) { + signedExtension.IsSignedByRemoteParty = true; + extendableMessage.Extensions.Add(signedExtension); + } - // Initialize this particular extension. - IOpenIdMessageExtension extension = this.ExtensionFactory.Create(typeUri, extensionData, extendableMessage, isAtProvider); - if (extension != null) { - MessageDictionary extensionDictionary = this.Channel.MessageDescriptions.GetAccessor(extension); + // Now search again, considering ALL extensions whether they are signed or not, + // skipping the signed ones and adding the new ones as unsigned extensions. + Func<string, bool> isNotSigned = typeUri => !extendableMessage.Extensions.Cast<IOpenIdMessageExtension>().Any(ext => ext.TypeUri == typeUri); + foreach (IOpenIdMessageExtension unsignedExtension in this.GetExtensions(extendableMessage, false, isNotSigned)) { + unsignedExtension.IsSignedByRemoteParty = false; + extendableMessage.Extensions.Add(unsignedExtension); + } + + return MessageProtections.None; + } + + return null; + } + + #endregion + + /// <summary> + /// Gets the extensions on a message. + /// </summary> + /// <param name="message">The carrier of the extensions.</param> + /// <param name="ignoreUnsigned">If set to <c>true</c> only signed extensions will be available.</param> + /// <param name="extensionFilter">A optional filter that takes an extension type URI and + /// returns a value indicating whether that extension should be deserialized and + /// returned in the sequence. May be null.</param> + /// <returns>A sequence of extensions in the message.</returns> + private IEnumerable<IOpenIdMessageExtension> GetExtensions(IProtocolMessageWithExtensions message, bool ignoreUnsigned, Func<string, bool> extensionFilter) { + bool isAtProvider = message is SignedResponseRequest; + + // We have a helper class that will do all the heavy-lifting of organizing + // all the extensions, their aliases, and their parameters. + var extensionManager = ExtensionArgumentsManager.CreateIncomingExtensions(this.GetExtensionsDictionary(message, ignoreUnsigned)); + foreach (string typeUri in extensionManager.GetExtensionTypeUris()) { + // Our caller may have already obtained a signed version of this extension, + // so skip it if they don't want this one. + if (extensionFilter != null && !extensionFilter(typeUri)) { + continue; + } + + var extensionData = extensionManager.GetExtensionArguments(typeUri); + + // Initialize this particular extension. + IOpenIdMessageExtension extension = this.ExtensionFactory.Create(typeUri, extensionData, message, isAtProvider); + if (extension != null) { + try { + // Make sure the extension fulfills spec requirements before deserializing it. + MessageDescription messageDescription = this.Channel.MessageDescriptions.Get(extension); + messageDescription.EnsureMessagePartsPassBasicValidation(extensionData); + + // Deserialize the extension. + MessageDictionary extensionDictionary = messageDescription.GetDictionary(extension); foreach (var pair in extensionData) { extensionDictionary[pair.Key] = pair.Value; } @@ -179,39 +203,34 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { if (customSerializingExtension != null) { customSerializingExtension.OnReceiving(); } + } catch (ProtocolException ex) { + Logger.OpenId.ErrorFormat(OpenIdStrings.BadExtension, extension.GetType(), ex); + extension = null; + } - extendableMessage.Extensions.Add(extension); - } else { - Logger.OpenId.WarnFormat("Extension with type URI '{0}' ignored because it is not a recognized extension.", typeUri); + if (extension != null) { + yield return extension; } + } else { + Logger.OpenId.WarnFormat("Extension with type URI '{0}' ignored because it is not a recognized extension.", typeUri); } - - return MessageProtections.None; } - - return null; } - #endregion - /// <summary> /// Gets the dictionary of message parts that should be deserialized into extensions. /// </summary> /// <param name="message">The message.</param> - /// <returns>A dictionary of message parts, including only signed parts when appropriate.</returns> - private IDictionary<string, string> GetExtensionsDictionary(IProtocolMessage message) { + /// <param name="ignoreUnsigned">If set to <c>true</c> only signed extensions will be available.</param> + /// <returns> + /// A dictionary of message parts, including only signed parts when appropriate. + /// </returns> + private IDictionary<string, string> GetExtensionsDictionary(IProtocolMessage message, bool ignoreUnsigned) { Contract.Requires(this.Channel != null); ErrorUtilities.VerifyOperation(this.Channel != null, "Channel property has not been set."); - // An IndirectSignedResponse message (the only one we care to filter parts for) - // can be received both by RPs and OPs (during check_auth). - // Whichever party is reading the extensions, apply their security policy regarding - // signing. (Although OPs have no reason to deserialize extensions during check_auth) - // so that scenario might be optimized away eventually. - bool extensionsShouldBeSigned = this.rpSecuritySettings != null ? !this.rpSecuritySettings.AllowUnsignedIncomingExtensions : this.opSecuritySettings.SignOutgoingExtensions; - IndirectSignedResponse signedResponse = message as IndirectSignedResponse; - if (signedResponse != null && extensionsShouldBeSigned) { + if (signedResponse != null && ignoreUnsigned) { return signedResponse.GetSignedMessageParts(this.Channel); } else { return this.Channel.MessageDescriptions.GetAccessor(message); diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs index 1ddd1eb..bc86259 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs +++ b/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs @@ -341,8 +341,8 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { var extensionFactory = OpenIdExtensionFactoryAggregator.LoadFromConfiguration(); List<IChannelBindingElement> elements = new List<IChannelBindingElement>(7); + elements.Add(new ExtensionsBindingElement(extensionFactory)); if (isRelyingPartyRole) { - elements.Add(new ExtensionsBindingElement(extensionFactory, rpSecuritySettings)); elements.Add(new BackwardCompatibilityBindingElement()); if (associationStore != null) { @@ -358,8 +358,6 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { elements.Add(new ReturnToSignatureBindingElement(rpAssociationStore, rpSecuritySettings)); } } else { - elements.Add(new ExtensionsBindingElement(extensionFactory, opSecuritySettings)); - // Providers must always have a nonce store. ErrorUtilities.VerifyArgumentNotNull(nonceStore, "nonceStore"); } diff --git a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/FetchResponse.cs b/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/FetchResponse.cs index 73039d4..9413e2f 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/FetchResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/FetchResponse.cs @@ -71,6 +71,16 @@ namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange { public Uri UpdateUrl { get; set; } /// <summary> + /// Gets a value indicating whether this extension is signed by the Provider. + /// </summary> + /// <value> + /// <c>true</c> if this instance is signed by the Provider; otherwise, <c>false</c>. + /// </value> + public bool IsSignedByProvider { + get { return this.IsSignedByRemoteParty; } + } + + /// <summary> /// 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> diff --git a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/StoreResponse.cs b/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/StoreResponse.cs index ae6ea5b..ba7f091 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/StoreResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/StoreResponse.cs @@ -74,6 +74,16 @@ namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange { public string FailureReason { get; set; } /// <summary> + /// Gets a value indicating whether this extension is signed by the Provider. + /// </summary> + /// <value> + /// <c>true</c> if this instance is signed by the Provider; otherwise, <c>false</c>. + /// </value> + public bool IsSignedByProvider { + get { return this.IsSignedByRemoteParty; } + } + + /// <summary> /// Gets or sets the mode argument. /// </summary> /// <value>One of 'store_response_success' or 'store_response_failure'.</value> diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ExtensionBase.cs b/src/DotNetOpenAuth/OpenId/Extensions/ExtensionBase.cs index 3ca979d..108ac52 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/ExtensionBase.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/ExtensionBase.cs @@ -72,6 +72,18 @@ namespace DotNetOpenAuth.OpenId.Extensions { get { return this.AdditionalSupportedTypeUris; } } + /// <summary> + /// Gets or sets a value indicating whether this extension was + /// signed by the OpenID Provider. + /// </summary> + /// <value> + /// <c>true</c> if this instance is signed by the provider; otherwise, <c>false</c>. + /// </value> + bool IOpenIdMessageExtension.IsSignedByRemoteParty { + get { return this.IsSignedByRemoteParty; } + set { this.IsSignedByRemoteParty = value; } + } + #endregion #region IMessage Properties @@ -101,6 +113,15 @@ namespace DotNetOpenAuth.OpenId.Extensions { } /// <summary> + /// Gets or sets a value indicating whether this extension was + /// signed by the OpenID Provider. + /// </summary> + /// <value> + /// <c>true</c> if this instance is signed by the provider; otherwise, <c>false</c>. + /// </value> + protected bool IsSignedByRemoteParty { get; set; } + + /// <summary> /// Gets the additional TypeURIs that are supported by this extension, in preferred order. /// May be empty if none other than <see cref="IOpenIdMessageExtension.TypeUri"/> is supported, but /// should not be null. diff --git a/src/DotNetOpenAuth/OpenId/Extensions/OAuth/AuthorizationApprovedResponse.cs b/src/DotNetOpenAuth/OpenId/Extensions/OAuth/AuthorizationApprovedResponse.cs new file mode 100644 index 0000000..5e7bc49 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Extensions/OAuth/AuthorizationApprovedResponse.cs @@ -0,0 +1,48 @@ +//----------------------------------------------------------------------- +// <copyright file="AuthorizationApprovedResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Extensions.OAuth { + using System; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// The OAuth response that a Provider may include with a positive + /// OpenID identity assertion with an approved request token. + /// </summary> + [Serializable] + public class AuthorizationApprovedResponse : ExtensionBase { + /// <summary> + /// The factory method that may be used in deserialization of this message. + /// </summary> + internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => { + if (typeUri == Constants.TypeUri && !isProviderRole && data.ContainsKey(Constants.RequestTokenParameter)) { + return new AuthorizationApprovedResponse(); + } + + return null; + }; + + /// <summary> + /// Initializes a new instance of the <see cref="AuthorizationApprovedResponse"/> class. + /// </summary> + public AuthorizationApprovedResponse() + : base(new Version(1, 0), Constants.TypeUri, null) { + } + + /// <summary> + /// Gets or sets the user-approved request token. + /// </summary> + /// <value>The request token.</value> + [MessagePart(Constants.RequestTokenParameter, IsRequired = true, AllowEmpty = false)] + public string RequestToken { get; set; } + + /// <summary> + /// Gets or sets a string that encodes, in a way possibly specific to the Combined Provider, one or more scopes that the returned request token is valid for. This will typically indicate a subset of the scopes requested in Section 8. + /// </summary> + [MessagePart("scope", IsRequired = false, AllowEmpty = true)] + public string Scope { get; set; } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/OAuth/AuthorizationDeclinedResponse.cs b/src/DotNetOpenAuth/OpenId/Extensions/OAuth/AuthorizationDeclinedResponse.cs new file mode 100644 index 0000000..7c3a5ad --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Extensions/OAuth/AuthorizationDeclinedResponse.cs @@ -0,0 +1,34 @@ +//----------------------------------------------------------------------- +// <copyright file="AuthorizationDeclinedResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Extensions.OAuth { + using System; + + /// <summary> + /// The OAuth response that a Provider should include with a positive + /// OpenID identity assertion when OAuth authorization was declined. + /// </summary> + [Serializable] + public class AuthorizationDeclinedResponse : ExtensionBase { + /// <summary> + /// The factory method that may be used in deserialization of this message. + /// </summary> + internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => { + if (typeUri == Constants.TypeUri && !isProviderRole && !data.ContainsKey(Constants.RequestTokenParameter)) { + return new AuthorizationDeclinedResponse(); + } + + return null; + }; + + /// <summary> + /// Initializes a new instance of the <see cref="AuthorizationDeclinedResponse"/> class. + /// </summary> + public AuthorizationDeclinedResponse() + : base(new Version(1, 0), Constants.TypeUri, null) { + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/OAuth/AuthorizationRequest.cs b/src/DotNetOpenAuth/OpenId/Extensions/OAuth/AuthorizationRequest.cs new file mode 100644 index 0000000..99f0880 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Extensions/OAuth/AuthorizationRequest.cs @@ -0,0 +1,57 @@ +//----------------------------------------------------------------------- +// <copyright file="AuthorizationRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Extensions.OAuth { + using System; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// An extension to include with an authentication request in order to also + /// obtain authorization to access user data at the combined OpenID Provider + /// and Service Provider. + /// </summary> + /// <remarks> + /// <para>When requesting OpenID Authentication via the protocol mode "checkid_setup" + /// or "checkid_immediate", this extension can be used to request that the end + /// user authorize an OAuth access token at the same time as an OpenID + /// authentication. This is done by sending the following parameters as part + /// of the OpenID request. (Note that the use of "oauth" as part of the parameter + /// names here and in subsequent sections is just an example. See Section 5 for details.)</para> + /// <para>See section 8.</para> + /// </remarks> + [Serializable] + public class AuthorizationRequest : ExtensionBase { + /// <summary> + /// The factory method that may be used in deserialization of this message. + /// </summary> + internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => { + if (typeUri == Constants.TypeUri && isProviderRole) { + return new AuthorizationRequest(); + } + + return null; + }; + + /// <summary> + /// Initializes a new instance of the <see cref="AuthorizationRequest"/> class. + /// </summary> + public AuthorizationRequest() + : base(new Version(1, 0), Constants.TypeUri, null) { + } + + /// <summary> + /// Gets or sets the consumer key agreed upon between the Consumer and Service Provider. + /// </summary> + [MessagePart("consumer", IsRequired = true, AllowEmpty = false)] + public string Consumer { get; set; } + + /// <summary> + /// Gets or sets a string that encodes, in a way possibly specific to the Combined Provider, one or more scopes for the OAuth token expected in the authentication response. + /// </summary> + [MessagePart("scope", IsRequired = false)] + public string Scope { get; set; } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/OAuth/Constants.cs b/src/DotNetOpenAuth/OpenId/Extensions/OAuth/Constants.cs new file mode 100644 index 0000000..32efee9 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Extensions/OAuth/Constants.cs @@ -0,0 +1,22 @@ +//----------------------------------------------------------------------- +// <copyright file="Constants.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Extensions.OAuth { + /// <summary> + /// Constants used in the OpenID OAuth extension. + /// </summary> + internal static class Constants { + /// <summary> + /// The TypeURI for the OpenID OAuth extension. + /// </summary> + internal const string TypeUri = "http://specs.openid.net/extensions/oauth/1.0"; + + /// <summary> + /// The name of the parameter that carries the request token in the response. + /// </summary> + internal const string RequestTokenParameter = "request_token"; + } +} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs index 8cf493c..b476cf7 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs @@ -140,6 +140,16 @@ namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy { /// </remarks> public IDictionary<string, string> AssuranceLevels { get; private set; } + /// <summary> + /// Gets a value indicating whether this extension is signed by the Provider. + /// </summary> + /// <value> + /// <c>true</c> if this instance is signed by the Provider; otherwise, <c>false</c>. + /// </value> + public bool IsSignedByProvider { + get { return this.IsSignedByRemoteParty; } + } + #region IMessageWithEvents Members /// <summary> diff --git a/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs b/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs index f17df46..a58c754 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs @@ -217,6 +217,16 @@ namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration { } /// <summary> + /// Gets a value indicating whether this extension is signed by the Provider. + /// </summary> + /// <value> + /// <c>true</c> if this instance is signed by the Provider; otherwise, <c>false</c>. + /// </value> + public bool IsSignedByProvider { + get { return this.IsSignedByRemoteParty; } + } + + /// <summary> /// Tests equality of two <see cref="ClaimsResponse"/> objects. /// </summary> /// <param name="one">One instance to compare.</param> diff --git a/src/DotNetOpenAuth/OpenId/Extensions/StandardOpenIdExtensionFactory.cs b/src/DotNetOpenAuth/OpenId/Extensions/StandardOpenIdExtensionFactory.cs index 9dda6ad..a669672 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/StandardOpenIdExtensionFactory.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/StandardOpenIdExtensionFactory.cs @@ -9,6 +9,7 @@ namespace DotNetOpenAuth.OpenId.Extensions { using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.ChannelElements; using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; + using DotNetOpenAuth.OpenId.Extensions.OAuth; using DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy; using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; using DotNetOpenAuth.OpenId.Messages; @@ -35,6 +36,9 @@ namespace DotNetOpenAuth.OpenId.Extensions { this.RegisterExtension(StoreResponse.Factory); this.RegisterExtension(PolicyRequest.Factory); this.RegisterExtension(PolicyResponse.Factory); + this.RegisterExtension(AuthorizationRequest.Factory); + this.RegisterExtension(AuthorizationApprovedResponse.Factory); + this.RegisterExtension(AuthorizationDeclinedResponse.Factory); } /// <summary> diff --git a/src/DotNetOpenAuth/OpenId/Interop/OpenIdRelyingPartyShim.cs b/src/DotNetOpenAuth/OpenId/Interop/OpenIdRelyingPartyShim.cs index 278a70f..e2bb365 100644 --- a/src/DotNetOpenAuth/OpenId/Interop/OpenIdRelyingPartyShim.cs +++ b/src/DotNetOpenAuth/OpenId/Interop/OpenIdRelyingPartyShim.cs @@ -156,7 +156,7 @@ namespace DotNetOpenAuth.OpenId.Interop { /// <returns>The Provider's response to a previous authentication request, or null if no response is present.</returns> public AuthenticationResponseShim ProcessAuthentication(string url, string form) { OpenIdRelyingParty rp = new OpenIdRelyingParty(null); - HttpRequestInfo requestInfo = new HttpRequestInfo { Url = new Uri(url) }; + HttpRequestInfo requestInfo = new HttpRequestInfo { UrlBeforeRewriting = new Uri(url) }; if (!string.IsNullOrEmpty(form)) { requestInfo.HttpMethod = "POST"; requestInfo.InputStream = new MemoryStream(Encoding.Unicode.GetBytes(form)); diff --git a/src/DotNetOpenAuth/OpenId/Messages/IOpenIdMessageExtension.cs b/src/DotNetOpenAuth/OpenId/Messages/IOpenIdMessageExtension.cs index fb984fe..95080e6 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/IOpenIdMessageExtension.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/IOpenIdMessageExtension.cs @@ -41,5 +41,14 @@ namespace DotNetOpenAuth.OpenId.Messages { /// The <see cref="Extensions.SimpleRegistration.ClaimsRequest.CreateResponse"/> for an example. /// </remarks> IEnumerable<string> AdditionalSupportedTypeUris { get; } + + /// <summary> + /// Gets or sets a value indicating whether this extension was + /// signed by the sender. + /// </summary> + /// <value> + /// <c>true</c> if this instance is signed by the sender; otherwise, <c>false</c>. + /// </value> + bool IsSignedByRemoteParty { get; set; } } } diff --git a/src/DotNetOpenAuth/OpenId/Messages/IndirectSignedResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/IndirectSignedResponse.cs index 107046d..9462d21 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/IndirectSignedResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/IndirectSignedResponse.cs @@ -238,6 +238,20 @@ namespace DotNetOpenAuth.OpenId.Messages { internal bool ReturnToParametersSignatureValidated { get; set; } /// <summary> + /// Gets the signed extensions on this message. + /// </summary> + internal IEnumerable<IOpenIdMessageExtension> SignedExtensions { + get { return this.extensions.OfType<IOpenIdMessageExtension>().Where(ext => ext.IsSignedByRemoteParty); } + } + + /// <summary> + /// Gets the unsigned extensions on this message. + /// </summary> + internal IEnumerable<IOpenIdMessageExtension> UnsignedExtensions { + get { return this.extensions.OfType<IOpenIdMessageExtension>().Where(ext => !ext.IsSignedByRemoteParty); } + } + + /// <summary> /// Gets or sets the nonce that will protect the message from replay attacks. /// </summary> /// <value> diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs index a5802d8..d3181f0 100644 --- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs +++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs @@ -133,6 +133,15 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> + /// Looks up a localized string similar to The {0} extension failed to deserialize and will be skipped. {1}. + /// </summary> + internal static string BadExtension { + get { + return ResourceManager.GetString("BadExtension", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to Callback arguments are only supported when a {0} is provided to the {1}.. /// </summary> internal static string CallbackArgumentsRequireSecretStore { @@ -322,7 +331,7 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> - /// Looks up a localized string similar to The OpenId Provider issued an assertion for an Identifier whose discovery information did not match. + /// Looks up a localized string similar to The OpenID Provider issued an assertion for an Identifier whose discovery information did not match. ///Assertion endpoint info: ///{0} ///Discovered endpoint info: @@ -389,7 +398,7 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> - /// Looks up a localized string similar to No XRDS document containing OpenId relying party endpoint information could be found at {0}.. + /// Looks up a localized string similar to No XRDS document containing OpenID relying party endpoint information could be found at {0}.. /// </summary> internal static string NoRelyingPartyEndpointDiscovered { get { @@ -416,7 +425,7 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> - /// Looks up a localized string similar to No OpenId endpoint found.. + /// Looks up a localized string similar to No OpenID endpoint found.. /// </summary> internal static string OpenIdEndpointNotFound { get { @@ -425,7 +434,7 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> - /// Looks up a localized string similar to No OpenId url is provided.. + /// Looks up a localized string similar to No OpenID url is provided.. /// </summary> internal static string OpenIdTextBoxEmpty { get { diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx index 40a0d0b..18c8f2a 100644 --- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx +++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx @@ -181,7 +181,7 @@ <value>Not a recognized XRI format: '{0}'.</value> </data> <data name="IssuedAssertionFailsIdentifierDiscovery" xml:space="preserve"> - <value>The OpenId Provider issued an assertion for an Identifier whose discovery information did not match. + <value>The OpenID Provider issued an assertion for an Identifier whose discovery information did not match. Assertion endpoint info: {0} Discovered endpoint info: @@ -206,7 +206,7 @@ Discovered endpoint info: <value>Diffie-Hellman session type '{0}' not found for OpenID {1}.</value> </data> <data name="OpenIdEndpointNotFound" xml:space="preserve"> - <value>No OpenId endpoint found.</value> + <value>No OpenID endpoint found.</value> </data> <data name="OperationOnlyValidForSetupRequiredState" xml:space="preserve"> <value>This operation is only allowed when IAuthenticationResponse.State == AuthenticationStatus.SetupRequired.</value> @@ -263,7 +263,7 @@ Discovered endpoint info: <value>An authentication request has already been created using CreateRequest().</value> </data> <data name="OpenIdTextBoxEmpty" xml:space="preserve"> - <value>No OpenId url is provided.</value> + <value>No OpenID url is provided.</value> </data> <data name="ClientScriptExtensionPropertyNameCollision" xml:space="preserve"> <value>An extension with this property name ('{0}') has already been registered.</value> @@ -278,7 +278,7 @@ Discovered endpoint info: <value>This operation is not supported by serialized authentication responses. Try this operation from the LoggedIn event handler.</value> </data> <data name="NoRelyingPartyEndpointDiscovered" xml:space="preserve"> - <value>No XRDS document containing OpenId relying party endpoint information could be found at {0}.</value> + <value>No XRDS document containing OpenID relying party endpoint information could be found at {0}.</value> </data> <data name="AbsoluteUriRequired" xml:space="preserve"> <value>An absolute URI is required for this value.</value> @@ -301,7 +301,10 @@ Discovered endpoint info: <data name="RequireSslNotSatisfiedByAssertedClaimedId" xml:space="preserve"> <value>Sorry. This site only accepts OpenIDs that are HTTPS-secured, but {0} is not a secure Identifier.</value> </data> + <data name="BadExtension" xml:space="preserve"> + <value>The {0} extension failed to deserialize and will be skipped. {1}</value> + </data> <data name="PositiveAssertionFromNonWhitelistedProvider" xml:space="preserve"> <value>An positive OpenID assertion was received from OP endpoint {0} that is not on this relying party's whitelist.</value> </data> -</root>
\ No newline at end of file +</root> diff --git a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs index 6f0e7bf..1fed8a6 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs @@ -192,7 +192,7 @@ namespace DotNetOpenAuth.OpenId.Provider { // If the incoming request does not resemble an OpenID message at all, // it's probably a user who just navigated to this URL, and we should // just return null so the host can display a message to the user. - if (httpRequestInfo.HttpMethod == "GET" && !httpRequestInfo.Url.QueryStringContainPrefixedParameters(Protocol.Default.openid.Prefix)) { + if (httpRequestInfo.HttpMethod == "GET" && !httpRequestInfo.UrlBeforeRewriting.QueryStringContainPrefixedParameters(Protocol.Default.openid.Prefix)) { return null; } diff --git a/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs b/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs index 635a3c0..43e666d 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs @@ -114,7 +114,12 @@ namespace DotNetOpenAuth.OpenId.Provider { protected override void OnLoad(EventArgs e) { base.OnLoad(e); - if (this.Enabled) { + // There is the unusual scenario that this control is hosted by + // an ASP.NET web page that has other UI on it to that the user + // might see, including controls that cause a postback to occur. + // We definitely want to ignore postbacks, since any openid messages + // they contain will be old. + if (this.Enabled && !this.Page.IsPostBack) { // Use the explicitly given state store on this control if there is one. // Then try the configuration file specified one. Finally, use the default // in-memory one that's built into OpenIdProvider. diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationResponseSnapshot.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationResponseSnapshot.cs index f70bbaa..5ab7ec4 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationResponseSnapshot.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationResponseSnapshot.cs @@ -109,6 +109,19 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <returns> /// The extension, if it is found. Null otherwise. /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension<T>"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> public T GetExtension<T>() where T : IOpenIdMessageExtension { throw new NotSupportedException(OpenIdStrings.NotSupportedByAuthenticationSnapshot); } @@ -120,11 +133,73 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <returns> /// The extension, if it is found. Null otherwise. /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> public IOpenIdMessageExtension GetExtension(Type extensionType) { throw new NotSupportedException(OpenIdStrings.NotSupportedByAuthenticationSnapshot); } /// <summary> + /// Tries to get an OpenID extension that may be present in the response, without + /// requiring it to be signed by the Provider. + /// </summary> + /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension<T>"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public T GetUntrustedExtension<T>() where T : IOpenIdMessageExtension { + throw new NotSupportedException(OpenIdStrings.NotSupportedByAuthenticationSnapshot); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response. + /// </summary> + /// <param name="extensionType">Type of the extension to look for in the response.</param> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public IOpenIdMessageExtension GetUntrustedExtension(Type extensionType) { + throw new NotSupportedException(OpenIdStrings.NotSupportedByAuthenticationSnapshot); + } + + /// <summary> /// Gets all the callback arguments that were previously added using /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part /// of the return_to URL. diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/FailedAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/FailedAuthenticationResponse.cs index 391aa6e..0dc21bb 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/FailedAuthenticationResponse.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/FailedAuthenticationResponse.cs @@ -143,6 +143,19 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <returns> /// The extension, if it is found. Null otherwise. /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension<T>"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> public T GetExtension<T>() where T : IOpenIdMessageExtension { return default(T); } @@ -154,10 +167,72 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <returns> /// The extension, if it is found. Null otherwise. /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> public IOpenIdMessageExtension GetExtension(Type extensionType) { return null; } + /// <summary> + /// Tries to get an OpenID extension that may be present in the response, without + /// requiring it to be signed by the Provider. + /// </summary> + /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension<T>"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public T GetUntrustedExtension<T>() where T : IOpenIdMessageExtension { + return default(T); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response. + /// </summary> + /// <param name="extensionType">Type of the extension to look for in the response.</param> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public IOpenIdMessageExtension GetUntrustedExtension(Type extensionType) { + return null; + } + #endregion } } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationResponse.cs index 7df17b8..afca13d 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationResponse.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationResponse.cs @@ -122,6 +122,19 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <returns> /// The extension, if it is found. Null otherwise. /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension<T>"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "No parameter at all is required. T is used for return type.")] T GetExtension<T>() where T : IOpenIdMessageExtension; @@ -132,6 +145,66 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <returns> /// The extension, if it is found. Null otherwise. /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> IOpenIdMessageExtension GetExtension(Type extensionType); + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response, without + /// requiring it to be signed by the Provider. + /// </summary> + /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension<T>"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "No parameter at all is required. T is used for return type.")] + T GetUntrustedExtension<T>() where T : IOpenIdMessageExtension; + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response, without + /// requiring it to be signed by the Provider. + /// </summary> + /// <param name="extensionType">Type of the extension to look for in the response.</param> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + IOpenIdMessageExtension GetUntrustedExtension(Type extensionType); } } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/NegativeAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/NegativeAuthenticationResponse.cs index 0a335c8..cd68a81 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/NegativeAuthenticationResponse.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/NegativeAuthenticationResponse.cs @@ -168,6 +168,19 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <returns> /// The extension, if it is found. Null otherwise. /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension<T>"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> public T GetExtension<T>() where T : IOpenIdMessageExtension { return default(T); } @@ -179,10 +192,72 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <returns> /// The extension, if it is found. Null otherwise. /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> public IOpenIdMessageExtension GetExtension(Type extensionType) { return null; } + /// <summary> + /// Tries to get an OpenID extension that may be present in the response, without + /// requiring it to be signed by the Provider. + /// </summary> + /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension<T>"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public T GetUntrustedExtension<T>() where T : IOpenIdMessageExtension { + return default(T); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response. + /// </summary> + /// <param name="extensionType">Type of the extension to look for in the response.</param> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public IOpenIdMessageExtension GetUntrustedExtension(Type extensionType) { + return null; + } + #endregion } } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.cs index cf66de3..0ffe007 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.cs @@ -356,7 +356,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { Uri authUri = new Uri(formAuthData); HttpRequestInfo clientResponseInfo = new HttpRequestInfo { - Url = authUri, + UrlBeforeRewriting = authUri, }; this.authenticationResponse = this.RelyingParty.GetResponse(clientResponseInfo); diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs index a065bcd..a7cf801 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs @@ -209,8 +209,21 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <returns> /// The extension, if it is found. Null otherwise. /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension<T>"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> public T GetExtension<T>() where T : IOpenIdMessageExtension { - return this.response.Extensions.OfType<T>().FirstOrDefault(); + return this.response.SignedExtensions.OfType<T>().FirstOrDefault(); } /// <summary> @@ -220,8 +233,71 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <returns> /// The extension, if it is found. Null otherwise. /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> public IOpenIdMessageExtension GetExtension(Type extensionType) { ErrorUtilities.VerifyArgumentNotNull(extensionType, "extensionType"); + return this.response.SignedExtensions.OfType<IOpenIdMessageExtension>().Where(ext => extensionType.IsInstanceOfType(ext)).FirstOrDefault(); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response, without + /// requiring it to be signed by the Provider. + /// </summary> + /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension<T>"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public T GetUntrustedExtension<T>() where T : IOpenIdMessageExtension { + return this.response.Extensions.OfType<T>().FirstOrDefault(); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response. + /// </summary> + /// <param name="extensionType">Type of the extension to look for in the response.</param> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="GetExtension"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + public IOpenIdMessageExtension GetUntrustedExtension(Type extensionType) { + ErrorUtilities.VerifyArgumentNotNull(extensionType, "extensionType"); return this.response.Extensions.OfType<IOpenIdMessageExtension>().Where(ext => extensionType.IsInstanceOfType(ext)).FirstOrDefault(); } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs index 64c6099..f7ac3c2 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs @@ -86,20 +86,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { public TimeSpan PrivateSecretMaximumAge { get; set; } /// <summary> - /// Gets or sets a value indicating whether unsigned extension responses will be deserialized. - /// </summary> - /// <value> - /// <c>false</c> to ignore unsigned extension responses; <c>true</c> to accept them. - /// Default is <c>false</c>. - /// </value> - /// <remarks> - /// This is an internal-only property because not requiring signed extensions is - /// potentially dangerous. It is included here as an internal option primarily - /// to enable testing. - /// </remarks> - internal bool AllowUnsignedIncomingExtensions { get; set; } - - /// <summary> /// Fires the <see cref="RequireSslChanged"/> event. /// </summary> private void OnRequireSslChanged() { diff --git a/src/DotNetOpenAuth/Yadis/Yadis.cs b/src/DotNetOpenAuth/Yadis/Yadis.cs index 151aa63..14aea62 100644 --- a/src/DotNetOpenAuth/Yadis/Yadis.cs +++ b/src/DotNetOpenAuth/Yadis/Yadis.cs @@ -28,7 +28,11 @@ namespace DotNetOpenAuth.Yadis { /// <summary> /// Gets or sets the cache that can be used for HTTP requests made during identifier discovery. /// </summary> +#if DEBUG + internal static readonly RequestCachePolicy IdentifierDiscoveryCachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.BypassCache); +#else internal static readonly RequestCachePolicy IdentifierDiscoveryCachePolicy = new HttpRequestCachePolicy(DotNetOpenAuthSection.Configuration.OpenId.CacheDiscovery ? HttpRequestCacheLevel.CacheIfAvailable : HttpRequestCacheLevel.BypassCache); +#endif /// <summary> /// The maximum number of bytes to read from an HTTP response |