//----------------------------------------------------------------------- // // Copyright (c) Outercurve Foundation. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOpenAuth.OpenId.Provider { using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Runtime.Serialization; using System.Text; using System.Threading; using System.Threading.Tasks; using DotNetOpenAuth.Logging; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.Messages; using Validation; /// /// A base class from which identity and non-identity RP requests can derive. /// [Serializable] internal abstract class HostProcessedRequest : Request, IHostProcessedRequest, IDeserializationCallback { /// /// The negative assertion to send, if the host site chooses to send it. /// [NonSerialized] private Lazy> negativeResponse; /// /// A cache of the result from discovery of the Realm URL. /// private RelyingPartyDiscoveryResult? realmDiscoveryResult; /// /// Initializes a new instance of the class. /// /// The provider that received the request. /// The incoming request message. protected HostProcessedRequest(OpenIdProvider provider, SignedResponseRequest request) : base(request, provider.SecuritySettings) { Requires.NotNull(provider, "provider"); this.SharedInitialization(provider); Reporting.RecordEventOccurrence(this, request.Realm); } #region IHostProcessedRequest Properties /// /// Gets the version of OpenID being used by the relying party that sent the request. /// public ProtocolVersion RelyingPartyVersion { get { return Protocol.Lookup(this.RequestMessage.Version).ProtocolVersion; } } /// /// 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. /// public bool Immediate { get { return this.RequestMessage.Immediate; } } /// /// Gets the URL the consumer site claims to use as its 'base' address. /// public Realm Realm { get { return this.RequestMessage.Realm; } } /// /// Gets or sets the provider endpoint. /// /// /// The default value is the URL that the request came in on from the relying party. /// public abstract Uri ProviderEndpoint { get; set; } #endregion /// /// Gets a value indicating whether realm discovery been performed. /// internal bool HasRealmDiscoveryBeenPerformed { get { return this.realmDiscoveryResult.HasValue; } } /// /// Gets the original request message. /// /// This may be null in the case of an unrecognizable message. protected new SignedResponseRequest RequestMessage { get { return (SignedResponseRequest)base.RequestMessage; } } #region IHostProcessedRequest Methods /// /// Gets a value indicating whether verification of the return URL claimed by the Relying Party /// succeeded. /// /// The host factories. /// The cancellation token. /// /// Result of realm discovery. /// /// /// 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. /// public async Task IsReturnUrlDiscoverableAsync(IHostFactories hostFactories, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNull(hostFactories, "hostFactories"); if (!this.realmDiscoveryResult.HasValue) { this.realmDiscoveryResult = await this.IsReturnUrlDiscoverableCoreAsync(hostFactories, cancellationToken); } return this.realmDiscoveryResult.Value; } #endregion /// /// Runs when the entire object graph has been deserialized. /// /// The object that initiated the callback. The functionality for this parameter is not currently implemented. void IDeserializationCallback.OnDeserialization(object sender) { // Bug: user_setup_url won't be created for OpenID 1.1 RPs in this path. this.SharedInitialization(null); } /// /// Gets the negative response. /// /// The negative assertion message. protected Task GetNegativeResponseAsync() { return this.negativeResponse.Value; } /// /// Gets a value indicating whether verification of the return URL claimed by the Relying Party /// succeeded. /// /// The host factories. /// The cancellation token. /// /// Result of realm discovery. /// private async Task IsReturnUrlDiscoverableCoreAsync(IHostFactories hostFactories, CancellationToken cancellationToken) { Requires.NotNull(hostFactories, "hostFactories"); ErrorUtilities.VerifyInternal(this.Realm != null, "Realm should have been read or derived by now."); try { if (this.SecuritySettings.RequireSsl && this.Realm.Scheme != Uri.UriSchemeHttps) { Logger.OpenId.WarnFormat("RP discovery failed because RequireSsl is true and RP discovery would begin at insecure URL {0}.", this.Realm); return RelyingPartyDiscoveryResult.NoServiceDocument; } var returnToEndpoints = await this.Realm.DiscoverReturnToEndpointsAsync(hostFactories, false, cancellationToken); if (returnToEndpoints == null) { return RelyingPartyDiscoveryResult.NoServiceDocument; } foreach (var returnUrl in returnToEndpoints) { 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 RelyingPartyDiscoveryResult.Success; } } } 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); return RelyingPartyDiscoveryResult.NoServiceDocument; } 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 RelyingPartyDiscoveryResult.NoServiceDocument; } return RelyingPartyDiscoveryResult.NoMatchingReturnTo; } /// /// Performs initialization common to construction and deserialization. /// /// The provider. private void SharedInitialization(OpenIdProvider provider) { this.negativeResponse = new Lazy>(() => NegativeAssertionResponse.CreateAsync(this.RequestMessage, CancellationToken.None, provider.Channel)); } } }