//----------------------------------------------------------------------- // // Copyright (c) Andrew Arnott. All rights reserved. // //----------------------------------------------------------------------- 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; /// /// A base class from which identity and non-identity RP requests can derive. /// [Serializable] internal abstract class HostProcessedRequest : Request, IHostProcessedRequest { /// /// The negative assertion to send, if the host site chooses to send it. /// private readonly NegativeAssertionResponse 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.negativeResponse = new NegativeAssertionResponse(request, provider.Channel); 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 negative response. /// protected NegativeAssertionResponse NegativeResponse { get { return this.negativeResponse; } } /// /// 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 request handler. /// /// 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 RelyingPartyDiscoveryResult IsReturnUrlDiscoverable(IDirectWebRequestHandler requestHandler) { if (!this.realmDiscoveryResult.HasValue) { this.realmDiscoveryResult = this.IsReturnUrlDiscoverableCore(requestHandler); } return this.realmDiscoveryResult.Value; } /// /// Gets a value indicating whether verification of the return URL claimed by the Relying Party /// succeeded. /// /// The request handler. /// /// Result of realm discovery. /// private RelyingPartyDiscoveryResult IsReturnUrlDiscoverableCore(IDirectWebRequestHandler requestHandler) { Requires.NotNull(requestHandler, "requestHandler"); 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 = this.Realm.DiscoverReturnToEndpoints(requestHandler, false); 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; } #endregion } }