diff options
Diffstat (limited to 'src')
21 files changed, 765 insertions, 332 deletions
diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj index a6069b4..1f3df12 100644 --- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj +++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj @@ -368,10 +368,15 @@ <Compile Include="OpenId\Messages\SignedResponseRequest.cs" /> <Compile Include="OpenId\NoDiscoveryIdentifier.cs" /> <Compile Include="OpenId\OpenIdUtilities.cs" /> + <Compile Include="OpenId\Provider\AnonymousRequest.cs" /> + <Compile Include="OpenId\Provider\AnonymousRequestEventArgs.cs" /> <Compile Include="OpenId\Provider\AuthenticationChallengeEventArgs.cs" /> <Compile Include="OpenId\Provider\AuthenticationRequest.cs" /> <Compile Include="OpenId\Provider\AutoResponsiveRequest.cs" /> + <Compile Include="OpenId\Provider\HostProcessedRequest.cs" /> + <Compile Include="OpenId\Provider\IAnonymousRequest.cs" /> <Compile Include="OpenId\Provider\IAuthenticationRequest.cs" /> + <Compile Include="OpenId\Provider\IHostProcessedRequest.cs" /> <Compile Include="OpenId\Provider\IdentityEndpoint.cs" /> <Compile Include="OpenId\Provider\IdentityEndpointNormalizationEventArgs.cs" /> <Compile Include="OpenId\Provider\IErrorReporting.cs" /> @@ -418,6 +423,7 @@ <Compile Include="OpenId\RelyingParty\OpenIdLogin.cs" /> <Compile Include="OpenId\RelyingParty\OpenIdMobileTextBox.cs" /> <Compile Include="OpenId\RelyingParty\OpenIdTextBox.cs" /> + <Compile Include="OpenId\RelyingParty\PositiveAnonymousResponse.cs" /> <Compile Include="OpenId\RelyingParty\PositiveAuthenticationResponse.cs" /> <Compile Include="OpenId\RelyingParty\AuthenticationStatus.cs" /> <Compile Include="OpenId\RelyingParty\FailedAuthenticationResponse.cs" /> diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdMessageFactory.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdMessageFactory.cs index c8ffae8..31a2da5 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdMessageFactory.cs +++ b/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdMessageFactory.cs @@ -57,6 +57,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { if (fields.ContainsKey(protocol.openid.identity)) { message = new CheckIdRequest(protocol.Version, recipient.Location, authMode); } else { + ErrorUtilities.VerifyProtocol(!fields.ContainsKey(protocol.openid.claimed_id), OpenIdStrings.IdentityAndClaimedIdentifierMustBeBothPresentOrAbsent); message = new SignedResponseRequest(protocol.Version, recipient.Location, authMode); } } else if (string.Equals(mode, protocol.Args.Mode.cancel) || @@ -66,6 +67,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { if (fields.ContainsKey(protocol.openid.identity)) { message = new PositiveAssertionResponse(protocol.Version, recipient.Location); } else { + ErrorUtilities.VerifyProtocol(!fields.ContainsKey(protocol.openid.claimed_id), OpenIdStrings.IdentityAndClaimedIdentifierMustBeBothPresentOrAbsent); message = new IndirectSignedResponse(protocol.Version, recipient.Location); } } else if (string.Equals(mode, protocol.Args.Mode.check_authentication)) { diff --git a/src/DotNetOpenAuth/OpenId/Messages/NegativeAssertionResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/NegativeAssertionResponse.cs index f03422c..99a7c5a 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/NegativeAssertionResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/NegativeAssertionResponse.cs @@ -31,12 +31,13 @@ namespace DotNetOpenAuth.OpenId.Messages { /// </summary> /// <param name="request">The request that the relying party sent.</param> /// <param name="channel">The channel to use to simulate construction of the user_setup_url, if applicable. May be null, but the user_setup_url will not be constructed.</param> - internal NegativeAssertionResponse(CheckIdRequest request, Channel channel) + internal NegativeAssertionResponse(SignedResponseRequest request, Channel channel) : base(request, GetMode(request)) { // If appropriate, and when we're provided with a channel to do it, // go ahead and construct the user_setup_url if (this.Version.Major < 2 && request.Immediate && channel != null) { - this.UserSetupUrl = ConstructUserSetupUrl(request, channel); + // All requests are CheckIdRequests in OpenID 1.x, so this cast should be safe. + this.UserSetupUrl = ConstructUserSetupUrl((CheckIdRequest)request, channel); } } @@ -130,7 +131,7 @@ namespace DotNetOpenAuth.OpenId.Messages { /// </summary> /// <param name="request">The request that we're responding to.</param> /// <returns>The value of the openid.mode parameter to use.</returns> - private static string GetMode(CheckIdRequest request) { + private static string GetMode(SignedResponseRequest request) { ErrorUtilities.VerifyArgumentNotNull(request, "request"); Protocol protocol = Protocol.Lookup(request.Version); diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs index a6e02fe..033ea54 100644 --- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs +++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs @@ -259,6 +259,15 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> + /// Looks up a localized string similar to The openid.identity and openid.claimed_id parameters must either be both present or both absent from the message.. + /// </summary> + internal static string IdentityAndClaimedIdentifierMustBeBothPresentOrAbsent { + get { + return ResourceManager.GetString("IdentityAndClaimedIdentifierMustBeBothPresentOrAbsent", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to The Provider requested association type '{0}' and session type '{1}', which are not compatible with each other.. /// </summary> internal static string IncompatibleAssociationAndSessionTypes { diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx index 95fe655..2b934f5 100644 --- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx +++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx @@ -301,4 +301,7 @@ Discovered endpoint info: <data name="UnsupportedChannelConfiguration" xml:space="preserve"> <value>This feature is unavailable due to an unrecognized channel configuration.</value> </data> + <data name="IdentityAndClaimedIdentifierMustBeBothPresentOrAbsent" xml:space="preserve"> + <value>The openid.identity and openid.claimed_id parameters must either be both present or both absent from the message.</value> + </data> </root>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequest.cs new file mode 100644 index 0000000..546db9f --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequest.cs @@ -0,0 +1,74 @@ +//----------------------------------------------------------------------- +// <copyright file="AnonymousRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System.Diagnostics.Contracts; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// Provides access to a host Provider to read an incoming extension-only checkid request message, + /// and supply extension responses or a cancellation message to the RP. + /// </summary> + internal class AnonymousRequest : HostProcessedRequest, IAnonymousRequest { + /// <summary> + /// The extension-response message to send, if the host site chooses to send it. + /// </summary> + private readonly IndirectSignedResponse positiveResponse; + + /// <summary> + /// Initializes a new instance of the <see cref="AnonymousRequest"/> class. + /// </summary> + /// <param name="provider">The provider that received the request.</param> + /// <param name="request">The incoming authentication request message.</param> + internal AnonymousRequest(OpenIdProvider provider, SignedResponseRequest request) + : base(provider, request) { + Contract.Requires(provider != null); + Contract.Requires(!(request is CheckIdRequest), "Instantiate " + typeof(AuthenticationRequest).Name + " to handle this kind of message."); + ErrorUtilities.VerifyInternal(!(request is CheckIdRequest), "Instantiate {0} to handle this kind of message.", typeof(AuthenticationRequest).Name); + + this.positiveResponse = new IndirectSignedResponse(request); + } + + #region IAnonymousRequest Members + + /// <summary> + /// Gets or sets a value indicating whether the user approved sending any data to the relying party. + /// </summary> + /// <value><c>true</c> if approved; otherwise, <c>false</c>.</value> + public bool? IsApproved { get; set; } + + #endregion + + #region Request members + + /// <summary> + /// Gets a value indicating whether the response is ready to be sent to the user agent. + /// </summary> + /// <remarks> + /// This property returns false if there are properties that must be set on this + /// request instance before the response can be sent. + /// </remarks> + public override bool IsResponseReady { + get { return this.IsApproved.HasValue; } + } + + /// <summary> + /// Gets the response message, once <see cref="IsResponseReady"/> is <c>true</c>. + /// </summary> + protected override IProtocolMessage ResponseMessage { + get { + if (this.IsApproved.HasValue) { + return this.IsApproved.Value ? (IProtocolMessage)this.positiveResponse : this.NegativeResponse; + } else { + return null; + } + } + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequestEventArgs.cs b/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequestEventArgs.cs new file mode 100644 index 0000000..cdd5311 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequestEventArgs.cs @@ -0,0 +1,32 @@ +//----------------------------------------------------------------------- +// <copyright file="AnonymousRequestEventArgs.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// The event arguments that include details of the incoming request. + /// </summary> + public class AnonymousRequestEventArgs : EventArgs { + /// <summary> + /// Initializes a new instance of the <see cref="AnonymousRequestEventArgs"/> class. + /// </summary> + /// <param name="request">The incoming OpenID request.</param> + internal AnonymousRequestEventArgs(IAnonymousRequest request) { + Contract.Requires(request != null); + ErrorUtilities.VerifyArgumentNotNull(request, "request"); + + this.Request = request; + } + + /// <summary> + /// Gets the incoming OpenID request. + /// </summary> + public IAnonymousRequest Request { get; private set; } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Provider/AuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/AuthenticationRequest.cs index 34ade49..7a547dd 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/AuthenticationRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/AuthenticationRequest.cs @@ -6,10 +6,7 @@ namespace DotNetOpenAuth.OpenId.Provider { using System; - using System.Collections.Generic; - using System.Linq; - using System.Net; - using System.Text; + using System.Diagnostics.Contracts; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.Messages; @@ -19,28 +16,23 @@ namespace DotNetOpenAuth.OpenId.Provider { /// requests. /// </summary> [Serializable] - internal class AuthenticationRequest : Request, IAuthenticationRequest { + internal class AuthenticationRequest : HostProcessedRequest, IAuthenticationRequest { /// <summary> /// The positive assertion to send, if the host site chooses to send it. /// </summary> private readonly PositiveAssertionResponse positiveResponse; /// <summary> - /// The negative assertion to send, if the host site chooses to send it. - /// </summary> - private readonly NegativeAssertionResponse negativeResponse; - - /// <summary> /// Initializes a new instance of the <see cref="AuthenticationRequest"/> class. /// </summary> /// <param name="provider">The provider that received the request.</param> /// <param name="request">The incoming authentication request message.</param> internal AuthenticationRequest(OpenIdProvider provider, CheckIdRequest request) - : base(request) { + : base(provider, request) { + Contract.Requires(provider != null); ErrorUtilities.VerifyArgumentNotNull(provider, "provider"); this.positiveResponse = new PositiveAssertionResponse(request); - this.negativeResponse = new NegativeAssertionResponse(request, provider.Channel); if (this.ClaimedIdentifier == Protocol.ClaimedIdentifierForOPIdentifier && Protocol.ClaimedIdentifierForOPIdentifier != null) { @@ -71,29 +63,6 @@ namespace DotNetOpenAuth.OpenId.Provider { #region IAuthenticationRequest Properties /// <summary> - /// Gets the version of OpenID being used by the relying party that sent the request. - /// </summary> - public ProtocolVersion RelyingPartyVersion { - get { return Protocol.Lookup(this.RequestMessage.Version).ProtocolVersion; } - } - - /// <summary> - /// Gets a value indicating whether the consumer demands an immediate response. - /// If false, the consumer is willing to wait for the identity provider - /// to authenticate the user. - /// </summary> - public bool Immediate { - get { return this.RequestMessage.Immediate; } - } - - /// <summary> - /// Gets the URL the consumer site claims to use as its 'base' address. - /// </summary> - public Realm Realm { - get { return this.RequestMessage.Realm; } - } - - /// <summary> /// Gets a value indicating whether the Provider should help the user /// select a Claimed Identifier to send back to the relying party. /// </summary> @@ -197,7 +166,7 @@ namespace DotNetOpenAuth.OpenId.Provider { protected override IProtocolMessage ResponseMessage { get { if (this.IsAuthenticated.HasValue) { - return this.IsAuthenticated.Value ? (IProtocolMessage)this.positiveResponse : this.negativeResponse; + return this.IsAuthenticated.Value ? (IProtocolMessage)this.positiveResponse : this.NegativeResponse; } else { return null; } @@ -231,53 +200,6 @@ namespace DotNetOpenAuth.OpenId.Provider { this.positiveResponse.ClaimedIdentifier = builder.Uri; } - /// <summary> - /// Gets a value indicating whether verification of the return URL claimed by the Relying Party - /// succeeded. - /// </summary> - /// <param name="requestHandler">The request handler to use to perform relying party discovery.</param> - /// <returns> - /// <c>true</c> if the Relying Party passed discovery verification; <c>false</c> otherwise. - /// </returns> - /// <remarks> - /// Return URL verification is only attempted if this property is queried. - /// The result of the verification is cached per request so calling this - /// property getter multiple times in one request is not a performance hit. - /// See OpenID Authentication 2.0 spec section 9.2.1. - /// </remarks> - public bool IsReturnUrlDiscoverable(IDirectWebRequestHandler requestHandler) { - ErrorUtilities.VerifyArgumentNotNull(requestHandler, "requestHandler"); - - ErrorUtilities.VerifyInternal(this.Realm != null, "Realm should have been read or derived by now."); - try { - foreach (var returnUrl in Realm.Discover(requestHandler, false)) { - Realm discoveredReturnToUrl = returnUrl.ReturnToEndpoint; - - // The spec requires that the return_to URLs given in an RPs XRDS doc - // do not contain wildcards. - if (discoveredReturnToUrl.DomainWildcard) { - Logger.Yadis.WarnFormat("Realm {0} contained return_to URL {1} which contains a wildcard, which is not allowed.", Realm, discoveredReturnToUrl); - continue; - } - - // Use the same rules as return_to/realm matching to check whether this - // URL fits the return_to URL we were given. - if (discoveredReturnToUrl.Contains(this.RequestMessage.ReturnTo)) { - // no need to keep looking after we find a match - return true; - } - } - } catch (ProtocolException ex) { - // Don't do anything else. We quietly fail at return_to verification and return false. - Logger.Yadis.InfoFormat("Relying party discovery at URL {0} failed. {1}", Realm, ex); - } catch (WebException ex) { - // Don't do anything else. We quietly fail at return_to verification and return false. - Logger.Yadis.InfoFormat("Relying party discovery at URL {0} failed. {1}", Realm, ex); - } - - return false; - } - #endregion } } diff --git a/src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs new file mode 100644 index 0000000..f4f42a4 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs @@ -0,0 +1,131 @@ +//----------------------------------------------------------------------- +// <copyright file="HostProcessedRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Net; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// A base class from which identity and non-identity RP requests can derive. + /// </summary> + internal abstract class HostProcessedRequest : Request, IHostProcessedRequest { + /// <summary> + /// The negative assertion to send, if the host site chooses to send it. + /// </summary> + private readonly NegativeAssertionResponse negativeResponse; + + /// <summary> + /// Initializes a new instance of the <see cref="HostProcessedRequest"/> class. + /// </summary> + /// <param name="provider">The provider that received the request.</param> + /// <param name="request">The incoming request message.</param> + protected HostProcessedRequest(OpenIdProvider provider, SignedResponseRequest request) + : base(request) { + Contract.Requires(provider != null); + + this.negativeResponse = new NegativeAssertionResponse(request, provider.Channel); + } + + #region IHostProcessedRequest Properties + + /// <summary> + /// Gets the version of OpenID being used by the relying party that sent the request. + /// </summary> + public ProtocolVersion RelyingPartyVersion { + get { return Protocol.Lookup(this.RequestMessage.Version).ProtocolVersion; } + } + + /// <summary> + /// Gets a value indicating whether the consumer demands an immediate response. + /// If false, the consumer is willing to wait for the identity provider + /// to authenticate the user. + /// </summary> + public bool Immediate { + get { return this.RequestMessage.Immediate; } + } + + /// <summary> + /// Gets the URL the consumer site claims to use as its 'base' address. + /// </summary> + public Realm Realm { + get { return this.RequestMessage.Realm; } + } + + #endregion + + /// <summary> + /// Gets the negative response. + /// </summary> + protected NegativeAssertionResponse NegativeResponse { + get { return this.negativeResponse; } + } + + /// <summary> + /// Gets the original request message. + /// </summary> + /// <value>This may be null in the case of an unrecognizable message.</value> + protected new SignedResponseRequest RequestMessage { + get { return (SignedResponseRequest)base.RequestMessage; } + } + + #region IHostProcessedRequest Methods + + /// <summary> + /// Gets a value indicating whether verification of the return URL claimed by the Relying Party + /// succeeded. + /// </summary> + /// <param name="requestHandler">The request handler to use to perform relying party discovery.</param> + /// <returns> + /// <c>true</c> if the Relying Party passed discovery verification; <c>false</c> otherwise. + /// </returns> + /// <remarks> + /// Return URL verification is only attempted if this property is queried. + /// The result of the verification is cached per request so calling this + /// property getter multiple times in one request is not a performance hit. + /// See OpenID Authentication 2.0 spec section 9.2.1. + /// </remarks> + public bool IsReturnUrlDiscoverable(IDirectWebRequestHandler requestHandler) { + ErrorUtilities.VerifyArgumentNotNull(requestHandler, "requestHandler"); + + ErrorUtilities.VerifyInternal(this.Realm != null, "Realm should have been read or derived by now."); + try { + foreach (var returnUrl in Realm.Discover(requestHandler, false)) { + Realm discoveredReturnToUrl = returnUrl.ReturnToEndpoint; + + // The spec requires that the return_to URLs given in an RPs XRDS doc + // do not contain wildcards. + if (discoveredReturnToUrl.DomainWildcard) { + Logger.Yadis.WarnFormat("Realm {0} contained return_to URL {1} which contains a wildcard, which is not allowed.", Realm, discoveredReturnToUrl); + continue; + } + + // Use the same rules as return_to/realm matching to check whether this + // URL fits the return_to URL we were given. + if (discoveredReturnToUrl.Contains(this.RequestMessage.ReturnTo)) { + // no need to keep looking after we find a match + return true; + } + } + } catch (ProtocolException ex) { + // Don't do anything else. We quietly fail at return_to verification and return false. + Logger.Yadis.InfoFormat("Relying party discovery at URL {0} failed. {1}", Realm, ex); + } catch (WebException ex) { + // Don't do anything else. We quietly fail at return_to verification and return false. + Logger.Yadis.InfoFormat("Relying party discovery at URL {0} failed. {1}", Realm, ex); + } + + return false; + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OpenId/Provider/IAnonymousRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/IAnonymousRequest.cs new file mode 100644 index 0000000..ec2c175 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Provider/IAnonymousRequest.cs @@ -0,0 +1,25 @@ +//----------------------------------------------------------------------- +// <copyright file="IAnonymousRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + /// <summary> + /// Instances of this interface represent incoming extension-only requests. + /// This interface provides the details of the request and allows setting + /// the response. + /// </summary> + public interface IAnonymousRequest : IHostProcessedRequest { + /// <summary> + /// Gets or sets a value indicating whether the user approved sending any data to the relying party. + /// </summary> + /// <value><c>true</c> if approved; otherwise, <c>false</c>.</value> + bool? IsApproved { get; set; } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Provider/IAuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/IAuthenticationRequest.cs index b1ef269..bb837b5 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/IAuthenticationRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/IAuthenticationRequest.cs @@ -15,24 +15,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// This interface provides the details of the request and allows setting /// the response. /// </summary> - public interface IAuthenticationRequest : IRequest { - /// <summary> - /// Gets the version of OpenID being used by the relying party that sent the request. - /// </summary> - ProtocolVersion RelyingPartyVersion { get; } - - /// <summary> - /// Gets a value indicating whether the consumer demands an immediate response. - /// If false, the consumer is willing to wait for the identity provider - /// to authenticate the user. - /// </summary> - bool Immediate { get; } - - /// <summary> - /// Gets the URL the consumer site claims to use as its 'base' address. - /// </summary> - Realm Realm { get; } - + public interface IAuthenticationRequest : IHostProcessedRequest { /// <summary> /// Gets a value indicating whether the Provider should help the user /// select a Claimed Identifier to send back to the relying party. @@ -109,18 +92,5 @@ namespace DotNetOpenAuth.OpenId.Provider { /// request before the <see cref="ClaimedIdentifier"/> property is set. /// </exception> void SetClaimedIdentifierFragment(string fragment); - - /// <summary> - /// Attempts to perform relying party discovery of the return URL claimed by the Relying Party. - /// </summary> - /// <param name="requestHandler">The request handler to use to perform relying party discovery.</param> - /// <returns> - /// <c>true</c> if the Relying Party passed discovery verification; <c>false</c> otherwise. - /// </returns> - /// <remarks> - /// <para>Return URL verification is only attempted if this method is called.</para> - /// <para>See OpenID Authentication 2.0 spec section 9.2.1.</para> - /// </remarks> - bool IsReturnUrlDiscoverable(IDirectWebRequestHandler requestHandler); } } diff --git a/src/DotNetOpenAuth/OpenId/Provider/IHostProcessedRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/IHostProcessedRequest.cs new file mode 100644 index 0000000..31479a1 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Provider/IHostProcessedRequest.cs @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------- +// <copyright file="IHostProcessedRequest.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Interface exposing incoming messages to the OpenID Provider that + /// require interaction with the host site. + /// </summary> + public interface IHostProcessedRequest : IRequest { + /// <summary> + /// Gets the version of OpenID being used by the relying party that sent the request. + /// </summary> + ProtocolVersion RelyingPartyVersion { get; } + + /// <summary> + /// Gets the URL the consumer site claims to use as its 'base' address. + /// </summary> + Realm Realm { get; } + + /// <summary> + /// Gets a value indicating whether the consumer demands an immediate response. + /// If false, the consumer is willing to wait for the identity provider + /// to authenticate the user. + /// </summary> + bool Immediate { get; } + + /// <summary> + /// Attempts to perform relying party discovery of the return URL claimed by the Relying Party. + /// </summary> + /// <param name="requestHandler">The request handler to use to perform relying party discovery.</param> + /// <returns> + /// <c>true</c> if the Relying Party passed discovery verification; <c>false</c> otherwise. + /// </returns> + /// <remarks> + /// <para>Return URL verification is only attempted if this method is called.</para> + /// <para>See OpenID Authentication 2.0 spec section 9.2.1.</para> + /// </remarks> + bool IsReturnUrlDiscoverable(IDirectWebRequestHandler requestHandler); + } +} diff --git a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs index c3780a9..7bb404c 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs @@ -204,6 +204,11 @@ namespace DotNetOpenAuth.OpenId.Provider { return new AuthenticationRequest(this, checkIdMessage); } + var extensionOnlyRequest = incomingMessage as SignedResponseRequest; + if (extensionOnlyRequest != null) { + return new AnonymousRequest(this, extensionOnlyRequest); + } + var checkAuthMessage = incomingMessage as CheckAuthenticationRequest; if (checkAuthMessage != null) { return new AutoResponsiveRequest(incomingMessage, new CheckAuthenticationResponse(checkAuthMessage, this)); diff --git a/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs b/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs index bec510b..91f10e6 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs @@ -27,7 +27,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <summary> /// The key used to store the pending authentication request in the ASP.NET session. /// </summary> - private const string PendingAuthenticationRequestKey = "pendingAuthenticationRequestKey"; + private const string PendingRequestKey = "pendingRequest"; /// <summary> /// The default value for the <see cref="Enabled"/> property. @@ -52,6 +52,12 @@ namespace DotNetOpenAuth.OpenId.Provider { public event EventHandler<AuthenticationChallengeEventArgs> AuthenticationChallenge; /// <summary> + /// Fired when an incoming OpenID message carries extension requests + /// but is not regarding any OpenID identifier. + /// </summary> + public event EventHandler<AnonymousRequestEventArgs> AnonymousRequest; + + /// <summary> /// Gets or sets the <see cref="OpenIdProvider"/> instance to use for all instances of this control. /// </summary> /// <value>The default value is an <see cref="OpenIdProvider"/> instance initialized according to the web.config file.</value> @@ -76,8 +82,36 @@ namespace DotNetOpenAuth.OpenId.Provider { /// before responding to the relying party's authentication request. /// </remarks> public static IAuthenticationRequest PendingAuthenticationRequest { - get { return HttpContext.Current.Session[PendingAuthenticationRequestKey] as IAuthenticationRequest; } - set { HttpContext.Current.Session[PendingAuthenticationRequestKey] = value; } + get { return HttpContext.Current.Session[PendingRequestKey] as IAuthenticationRequest; } + set { HttpContext.Current.Session[PendingRequestKey] = value; } + } + + /// <summary> + /// Gets or sets an incoming OpenID anonymous request that has not yet been responded to. + /// </summary> + /// <remarks> + /// This request is stored in the ASP.NET Session state, so it will survive across + /// redirects, postbacks, and transfers. This allows you to authenticate the user + /// yourself, and confirm his/her desire to provide data to the relying party site + /// before responding to the relying party's request. + /// </remarks> + public static IAnonymousRequest PendingAnonymousRequest { + get { return HttpContext.Current.Session[PendingRequestKey] as IAnonymousRequest; } + set { HttpContext.Current.Session[PendingRequestKey] = value; } + } + + /// <summary> + /// Gets or sets an incoming OpenID request that has not yet been responded to. + /// </summary> + /// <remarks> + /// This request is stored in the ASP.NET Session state, so it will survive across + /// redirects, postbacks, and transfers. This allows you to authenticate the user + /// yourself, and confirm his/her desire to provide data to the relying party site + /// before responding to the relying party's request. + /// </remarks> + public static IHostProcessedRequest PendingRequest { + get { return HttpContext.Current.Session[PendingRequestKey] as IHostProcessedRequest; } + set { HttpContext.Current.Session[PendingRequestKey] = value; } } /// <summary> @@ -100,8 +134,8 @@ namespace DotNetOpenAuth.OpenId.Provider { /// Sends the response for the <see cref="PendingAuthenticationRequest"/> and clears the property. /// </summary> public static void SendResponse() { - Provider.SendResponse(PendingAuthenticationRequest); - PendingAuthenticationRequest = null; + Provider.SendResponse(PendingRequest); + PendingRequest = null; } /// <summary> @@ -125,13 +159,22 @@ namespace DotNetOpenAuth.OpenId.Provider { // determine what incoming message was received IRequest request = provider.GetRequest(); if (request != null) { + PendingRequest = null; + // process the incoming message appropriately and send the response - if (!request.IsResponseReady) { - var idrequest = (IAuthenticationRequest)request; + IAuthenticationRequest idrequest; + IAnonymousRequest anonRequest; + if ((idrequest = request as IAuthenticationRequest) != null) { PendingAuthenticationRequest = idrequest; this.OnAuthenticationChallenge(idrequest); - } else { - PendingAuthenticationRequest = null; + } else if ((anonRequest = request as IAnonymousRequest) != null) { + PendingAnonymousRequest = anonRequest; + if (!this.OnAnonymousRequest(anonRequest)) { + // This is a feature not supported by the OP, so + // go ahead and set disapproved so we can send a response. + Logger.OpenId.Warn("An incoming anonymous OpenID request message was detected, but the ProviderEndpoint.AnonymousRequest event is not handled, so returning cancellation message to relying party."); + anonRequest.IsApproved = false; + } } if (request.IsResponseReady) { provider.SendResponse(request); @@ -154,6 +197,21 @@ namespace DotNetOpenAuth.OpenId.Provider { } /// <summary> + /// Fires the <see cref="AnonymousRequest"/> event. + /// </summary> + /// <param name="request">The request to include in the event args.</param> + /// <returns><c>true</c> if there were any anonymous request handlers.</returns> + protected virtual bool OnAnonymousRequest(IAnonymousRequest request) { + var anonymousRequest = this.AnonymousRequest; + if (anonymousRequest != null) { + anonymousRequest(this, new AnonymousRequestEventArgs(request)); + return true; + } else { + return false; + } + } + + /// <summary> /// Creates the default OpenIdProvider to use. /// </summary> /// <returns>The new instance of OpenIdProvider.</returns> diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs index 2e22aec..1f470b2 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs @@ -133,6 +133,16 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> + /// Gets or sets a value indicating whether this request only carries extensions + /// and is not a request to verify that the user controls some identifier. + /// </summary> + /// <value> + /// <c>true</c> if this request is merely a carrier of extensions and is not + /// about an OpenID identifier; otherwise, <c>false</c>. + /// </value> + public bool IsExtensionOnly { get; set; } + + /// <summary> /// Gets information about the OpenId Provider, as advertised by the /// OpenId discovery documents found at the <see cref="ClaimedIdentifier"/> /// location. @@ -401,16 +411,22 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> - /// Creates the authentication request message to send to the Provider, + /// Creates the request message to send to the Provider, /// based on the properties in this instance. /// </summary> /// <returns>The message to send to the Provider.</returns> - private CheckIdRequest CreateRequestMessage() { + private SignedResponseRequest CreateRequestMessage() { Association association = this.GetAssociation(); - CheckIdRequest request = new CheckIdRequest(this.endpoint.Protocol.Version, this.endpoint.ProviderEndpoint, this.Mode); - request.ClaimedIdentifier = this.endpoint.ClaimedIdentifier; - request.LocalIdentifier = this.endpoint.ProviderLocalIdentifier; + SignedResponseRequest request; + if (!this.IsExtensionOnly) { + CheckIdRequest authRequest = new CheckIdRequest(this.endpoint.Protocol.Version, this.endpoint.ProviderEndpoint, this.Mode); + authRequest.ClaimedIdentifier = this.endpoint.ClaimedIdentifier; + authRequest.LocalIdentifier = this.endpoint.ProviderLocalIdentifier; + request = authRequest; + } else { + request = new SignedResponseRequest(this.endpoint.Protocol.Version, this.endpoint.ProviderEndpoint, this.Mode); + } request.Realm = this.Realm; request.ReturnTo = this.ReturnToUrl; request.AssociationHandle = association != null ? association.Handle : null; diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationStatus.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationStatus.cs index c67355d..d9e5d0a 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationStatus.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationStatus.cs @@ -33,5 +33,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// Authentication is completed successfully. /// </summary> Authenticated, + + /// <summary> + /// The Provider sent a message that did not contain an identity assertion, + /// but may carry OpenID extensions. + /// </summary> + ExtensionsOnly, } } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequest.cs index 4892092..0bfcaf5 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequest.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequest.cs @@ -63,6 +63,30 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { bool IsDirectedIdentity { get; } /// <summary> + /// Gets or sets a value indicating whether this request only carries extensions + /// and is not a request to verify that the user controls some identifier. + /// </summary> + /// <value> + /// <c>true</c> if this request is merely a carrier of extensions and is not + /// about an OpenID identifier; otherwise, <c>false</c>. + /// </value> + /// <remarks> + /// <para>Although OpenID is first and primarily an authentication protocol, its extensions + /// can be interesting all by themselves. For instance, a relying party might want + /// to know that its user is over 21 years old, or perhaps a member of some organization. + /// OpenID extensions can provide this, without any need for asserting the identity of the user.</para> + /// <para>Constructing an OpenID request for only extensions can be done by calling + /// <see cref="OpenIdRelyingParty.CreateRequest(Identifier)"/> with any valid OpenID identifier + /// (claimed identifier or OP identifier). But once this property is set to <c>true</c>, + /// the claimed identifier value in the request is not included in the transmitted message.</para> + /// <para>It is anticipated that an RP would only issue these types of requests to OPs that + /// trusts to make assertions regarding the individual holding an account at that OP, so it + /// is not likely that the RP would allow the user to type in an arbitrary claimed identifier + /// without checking that it resolved to an OP endpoint the RP has on a trust whitelist.</para> + /// </remarks> + bool IsExtensionOnly { get; set; } + + /// <summary> /// Gets information about the OpenId Provider, as advertised by the /// OpenId discovery documents found at the <see cref="ClaimedIdentifier"/> /// location. diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs index 4bcaf10..3762602 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs @@ -342,8 +342,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { var message = this.Channel.ReadFromRequest(httpRequestInfo); PositiveAssertionResponse positiveAssertion; NegativeAssertionResponse negativeAssertion; + IndirectSignedResponse positiveExtensionOnly; if ((positiveAssertion = message as PositiveAssertionResponse) != null) { return new PositiveAuthenticationResponse(positiveAssertion, this); + } else if ((positiveExtensionOnly = message as IndirectSignedResponse) != null) { + return new PositiveAnonymousResponse(positiveExtensionOnly); } else if ((negativeAssertion = message as NegativeAssertionResponse) != null) { return new NegativeAuthenticationResponse(negativeAssertion); } else if (message != null) { diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs index c86647a..1eea315 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs @@ -1033,8 +1033,13 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { case AuthenticationStatus.Failed: this.OnFailed(response); break; + case AuthenticationStatus.ExtensionsOnly: default: - throw new InvalidOperationException("Unexpected response status code."); + // The NotApplicable (extension-only assertion) is NOT one that we support + // in this control because that scenario is primarily interesting to RPs + // that are asking a specific OP, and it is not user-initiated as this textbox + // is designed for. + throw new InvalidOperationException(MessagingStrings.UnexpectedMessageReceivedOfMany); } } } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs new file mode 100644 index 0000000..a28de12 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs @@ -0,0 +1,273 @@ +//----------------------------------------------------------------------- +// <copyright file="PositiveAnonymousResponse.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// Wraps an extension-only response from the OP in an <see cref="IAuthenticationResponse"/> instance + /// for public consumption by the host web site. + /// </summary> + internal class PositiveAnonymousResponse : IAuthenticationResponse { + /// <summary> + /// Backin field for the <see cref="Response"/> property. + /// </summary> + private readonly IndirectSignedResponse response; + + /// <summary> + /// Initializes a new instance of the <see cref="PositiveAnonymousResponse"/> class. + /// </summary> + /// <param name="response">The response message.</param> + protected internal PositiveAnonymousResponse(IndirectSignedResponse response) { + Contract.Requires(response != null); + ErrorUtilities.VerifyArgumentNotNull(response, "response"); + + this.response = response; + } + + #region IAuthenticationResponse Properties + + /// <summary> + /// Gets the Identifier that the end user claims to own. For use with user database storage and lookup. + /// May be null for some failed authentications (i.e. failed directed identity authentications). + /// </summary> + /// <remarks> + /// <para> + /// This is the secure identifier that should be used for database storage and lookup. + /// It is not always friendly (i.e. =Arnott becomes =!9B72.7DD1.50A9.5CCD), but it protects + /// user identities against spoofing and other attacks. + /// </para> + /// <para> + /// For user-friendly identifiers to display, use the + /// <see cref="FriendlyIdentifierForDisplay"/> property. + /// </para> + /// </remarks> + public virtual Identifier ClaimedIdentifier { + get { return null; } + } + + /// <summary> + /// Gets a user-friendly OpenID Identifier for display purposes ONLY. + /// </summary> + /// <value></value> + /// <remarks> + /// <para> + /// This <i>should</i> be put through <see cref="HttpUtility.HtmlEncode(string)"/> before + /// sending to a browser to secure against javascript injection attacks. + /// </para> + /// <para> + /// This property retains some aspects of the user-supplied identifier that get lost + /// in the <see cref="ClaimedIdentifier"/>. For example, XRIs used as user-supplied + /// identifiers (i.e. =Arnott) become unfriendly unique strings (i.e. =!9B72.7DD1.50A9.5CCD). + /// For display purposes, such as text on a web page that says "You're logged in as ...", + /// this property serves to provide the =Arnott string, or whatever else is the most friendly + /// string close to what the user originally typed in. + /// </para> + /// <para> + /// If the user-supplied identifier is a URI, this property will be the URI after all + /// redirects, and with the protocol and fragment trimmed off. + /// If the user-supplied identifier is an XRI, this property will be the original XRI. + /// If the user-supplied identifier is an OpenID Provider identifier (i.e. yahoo.com), + /// this property will be the Claimed Identifier, with the protocol stripped if it is a URI. + /// </para> + /// <para> + /// It is <b>very</b> important that this property <i>never</i> be used for database storage + /// or lookup to avoid identity spoofing and other security risks. For database storage + /// and lookup please use the <see cref="ClaimedIdentifier"/> property. + /// </para> + /// </remarks> + public virtual string FriendlyIdentifierForDisplay { + get { return null; } + } + + /// <summary> + /// Gets the detailed success or failure status of the authentication attempt. + /// </summary> + public virtual AuthenticationStatus Status { + get { return AuthenticationStatus.ExtensionsOnly; } + } + + /// <summary> + /// Gets the details regarding a failed authentication attempt, if available. + /// This will be set if and only if <see cref="Status"/> is <see cref="AuthenticationStatus.Failed"/>. + /// </summary> + /// <value></value> + public Exception Exception { + get { return null; } + } + + #endregion + + /// <summary> + /// Gets the positive extension-only message the Relying Party received that this instance wraps. + /// </summary> + protected internal IndirectSignedResponse Response { + get { return this.response; } + } + + #region IAuthenticationResponse methods + + /// <summary> + /// Gets a callback argument's value that was previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. + /// </summary> + /// <param name="key">The name of the parameter whose value is sought.</param> + /// <returns> + /// The value of the argument, or null if the named parameter could not be found. + /// </returns> + /// <remarks> + /// Callback parameters are only available if they are complete and untampered with + /// since the original request message (as proven by a signature). + /// If the relying party is operating in stateless mode <c>null</c> is always + /// returned since the callback arguments could not be signed to protect against + /// tampering. + /// </remarks> + public string GetCallbackArgument(string key) { + if (this.response.ReturnToParametersSignatureValidated) { + return this.response.GetReturnToArgument(key); + } else { + return null; + } + } + + /// <summary> + /// Gets all the callback arguments that were previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part + /// of the return_to URL. + /// </summary> + /// <returns>A name-value dictionary. Never null.</returns> + /// <remarks> + /// Callback parameters are only available if they are complete and untampered with + /// since the original request message (as proven by a signature). + /// If the relying party is operating in stateless mode an empty dictionary is always + /// returned since the callback arguments could not be signed to protect against + /// tampering. + /// </remarks> + public IDictionary<string, string> GetCallbackArguments() { + if (this.response.ReturnToParametersSignatureValidated) { + var args = new Dictionary<string, string>(); + + // Return all the return_to arguments, except for the OpenID-supporting ones. + // The only arguments that should be returned here are the ones that the host + // web site adds explicitly. + foreach (string key in this.response.GetReturnToParameterNames().Where(key => !OpenIdRelyingParty.IsOpenIdSupportingParameter(key))) { + args[key] = this.response.GetReturnToArgument(key); + } + + return args; + } else { + return EmptyDictionary<string, string>.Instance; + } + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response. + /// </summary> + /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="GetUntrustedExtension<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.SignedExtensions.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 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(); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs index 32980f5..e3740db 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs @@ -5,14 +5,11 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Collections.Generic; using System.Diagnostics; + using System.Diagnostics.Contracts; using System.Linq; - using System.Text; using System.Web; using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.Extensions; using DotNetOpenAuth.OpenId.Messages; /// <summary> @@ -20,17 +17,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// for public consumption by the host web site. /// </summary> [DebuggerDisplay("Status: {Status}, ClaimedIdentifier: {ClaimedIdentifier}")] - internal class PositiveAuthenticationResponse : IAuthenticationResponse { - /// <summary> - /// The positive assertion message the Relying Party received that this instance wraps. - /// </summary> - private readonly PositiveAssertionResponse response; - - /// <summary> - /// The relying party that created this request object. - /// </summary> - private readonly OpenIdRelyingParty relyingParty; - + internal class PositiveAuthenticationResponse : PositiveAnonymousResponse { /// <summary> /// The OpenID service endpoint reconstructed from the assertion message. /// </summary> @@ -47,22 +34,20 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> /// <param name="response">The positive assertion response that was just received by the Relying Party.</param> /// <param name="relyingParty">The relying party.</param> - internal PositiveAuthenticationResponse(PositiveAssertionResponse response, OpenIdRelyingParty relyingParty) { - ErrorUtilities.VerifyArgumentNotNull(response, "response"); + internal PositiveAuthenticationResponse(PositiveAssertionResponse response, OpenIdRelyingParty relyingParty) + : base(response) { + Contract.Requires(relyingParty != null); ErrorUtilities.VerifyArgumentNotNull(relyingParty, "relyingParty"); - this.response = response; - this.relyingParty = relyingParty; - this.endpoint = ServiceEndpoint.CreateForClaimedIdentifier( - this.response.ClaimedIdentifier, - this.response.GetReturnToArgument(AuthenticationRequest.UserSuppliedIdentifierParameterName), - this.response.LocalIdentifier, - new ProviderEndpointDescription(this.response.ProviderEndpoint, this.response.Version), + this.Response.ClaimedIdentifier, + this.Response.GetReturnToArgument(AuthenticationRequest.UserSuppliedIdentifierParameterName), + this.Response.LocalIdentifier, + new ProviderEndpointDescription(this.Response.ProviderEndpoint, this.Response.Version), null, null); - this.VerifyDiscoveryMatchesAssertion(); + this.VerifyDiscoveryMatchesAssertion(relyingParty); } #region IAuthenticationResponse Properties @@ -83,14 +68,13 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <see cref="FriendlyIdentifierForDisplay"/> property. /// </para> /// </remarks> - public Identifier ClaimedIdentifier { + public override Identifier ClaimedIdentifier { get { return this.endpoint.ClaimedIdentifier; } } /// <summary> /// Gets a user-friendly OpenID Identifier for display purposes ONLY. /// </summary> - /// <value></value> /// <remarks> /// <para> /// This <i>should</i> be put through <see cref="HttpUtility.HtmlEncode(string)"/> before @@ -117,203 +101,38 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// and lookup please use the <see cref="ClaimedIdentifier"/> property. /// </para> /// </remarks> - public string FriendlyIdentifierForDisplay { + public override string FriendlyIdentifierForDisplay { get { return this.endpoint.FriendlyIdentifierForDisplay; } } /// <summary> /// Gets the detailed success or failure status of the authentication attempt. /// </summary> - /// <value></value> - public AuthenticationStatus Status { + public override AuthenticationStatus Status { get { return AuthenticationStatus.Authenticated; } } - /// <summary> - /// Gets the details regarding a failed authentication attempt, if available. - /// This will be set if and only if <see cref="Status"/> is <see cref="AuthenticationStatus.Failed"/>. - /// </summary> - /// <value></value> - public Exception Exception { - get { return null; } - } - #endregion /// <summary> /// Gets the positive assertion response message. /// </summary> - internal PositiveAssertionResponse Response { - get { return this.response; } - } - - #region IAuthenticationResponse methods - - /// <summary> - /// Gets a callback argument's value that was previously added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. - /// </summary> - /// <param name="key">The name of the parameter whose value is sought.</param> - /// <returns> - /// The value of the argument, or null if the named parameter could not be found. - /// </returns> - /// <remarks> - /// Callback parameters are only available if they are complete and untampered with - /// since the original request message (as proven by a signature). - /// If the relying party is operating in stateless mode <c>null</c> is always - /// returned since the callback arguments could not be signed to protect against - /// tampering. - /// </remarks> - public string GetCallbackArgument(string key) { - if (this.response.ReturnToParametersSignatureValidated) { - return this.response.GetReturnToArgument(key); - } else { - return null; - } - } - - /// <summary> - /// Gets all the callback arguments that were previously added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part - /// of the return_to URL. - /// </summary> - /// <returns>A name-value dictionary. Never null.</returns> - /// <remarks> - /// Callback parameters are only available if they are complete and untampered with - /// since the original request message (as proven by a signature). - /// If the relying party is operating in stateless mode an empty dictionary is always - /// returned since the callback arguments could not be signed to protect against - /// tampering. - /// </remarks> - public IDictionary<string, string> GetCallbackArguments() { - if (this.response.ReturnToParametersSignatureValidated) { - var args = new Dictionary<string, string>(); - - // Return all the return_to arguments, except for the OpenID-supporting ones. - // The only arguments that should be returned here are the ones that the host - // web site adds explicitly. - foreach (string key in this.response.GetReturnToParameterNames().Where(key => !OpenIdRelyingParty.IsOpenIdSupportingParameter(key))) { - args[key] = this.response.GetReturnToArgument(key); - } - - return args; - } else { - return EmptyDictionary<string, string>.Instance; - } - } - - /// <summary> - /// Tries to get an OpenID extension that may be present in the response. - /// </summary> - /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> - /// <returns> - /// The extension, if it is found. Null otherwise. - /// </returns> - /// <remarks> - /// <para>Extensions are returned only if the Provider signed them. - /// Relying parties that do not care if the values were modified in - /// transit should use the <see cref="GetUntrustedExtension<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.SignedExtensions.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 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(); + protected internal new PositiveAssertionResponse Response { + get { return (PositiveAssertionResponse)base.Response; } } - #endregion - /// <summary> /// Verifies that the positive assertion data matches the results of /// discovery on the Claimed Identifier. /// </summary> + /// <param name="relyingParty">The relying party.</param> /// <exception cref="ProtocolException"> /// Thrown when the Provider is asserting that a user controls an Identifier /// when discovery on that Identifier contradicts what the Provider says. /// This would be an indication of either a misconfigured Provider or /// an attempt by someone to spoof another user's identity with a rogue Provider. /// </exception> - private void VerifyDiscoveryMatchesAssertion() { + private void VerifyDiscoveryMatchesAssertion(OpenIdRelyingParty relyingParty) { Logger.OpenId.Debug("Verifying assertion matches identifier discovery results..."); // While it LOOKS like we're performing discovery over HTTP again @@ -325,8 +144,12 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { // is signed by the RP before it's considered reliable. In 1.x stateless mode, this RP // doesn't (and can't) sign its own return_to URL, so its cached discovery information // is merely a hint that must be verified by performing discovery again here. - var discoveryResults = this.response.ClaimedIdentifier.Discover(this.relyingParty.WebRequestHandler); - ErrorUtilities.VerifyProtocol(discoveryResults.Contains(this.endpoint), OpenIdStrings.IssuedAssertionFailsIdentifierDiscovery, this.endpoint, discoveryResults.ToStringDeferred(true)); + var discoveryResults = this.Response.ClaimedIdentifier.Discover(relyingParty.WebRequestHandler); + ErrorUtilities.VerifyProtocol( + discoveryResults.Contains(this.endpoint), + OpenIdStrings.IssuedAssertionFailsIdentifierDiscovery, + this.endpoint, + discoveryResults.ToStringDeferred(true)); } } } |