//----------------------------------------------------------------------- // // Copyright (c) Andrew Arnott. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOpenAuth.OpenId.RelyingParty { using System; using System.Diagnostics; using System.Diagnostics.Contracts; using System.Linq; using System.Web; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.Messages; /// /// Wraps a positive assertion response in an instance /// for public consumption by the host web site. /// [DebuggerDisplay("Status: {Status}, ClaimedIdentifier: {ClaimedIdentifier}")] internal class PositiveAuthenticationResponse : PositiveAnonymousResponse { /// /// Initializes a new instance of the class. /// /// The positive assertion response that was just received by the Relying Party. /// The relying party. internal PositiveAuthenticationResponse(PositiveAssertionResponse response, OpenIdRelyingParty relyingParty) : base(response) { Requires.NotNull(relyingParty, "relyingParty"); this.Endpoint = IdentifierDiscoveryResult.CreateForClaimedIdentifier( this.Response.ClaimedIdentifier, this.Response.GetReturnToArgument(AuthenticationRequest.UserSuppliedIdentifierParameterName), this.Response.LocalIdentifier, new ProviderEndpointDescription(this.Response.ProviderEndpoint, this.Response.Version), null, null); this.VerifyDiscoveryMatchesAssertion(relyingParty); Logger.OpenId.InfoFormat("Received identity assertion for {0} via {1}.", this.Response.ClaimedIdentifier, this.Provider.Uri); } #region IAuthenticationResponse Properties /// /// 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). /// /// /// /// /// 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. /// /// /// For user-friendly identifiers to display, use the /// property. /// /// public override Identifier ClaimedIdentifier { get { return this.Endpoint.ClaimedIdentifier; } } /// /// Gets a user-friendly OpenID Identifier for display purposes ONLY. /// /// /// /// This should be put through before /// sending to a browser to secure against javascript injection attacks. /// /// /// This property retains some aspects of the user-supplied identifier that get lost /// in the . 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. /// /// /// 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. /// /// /// It is very important that this property never be used for database storage /// or lookup to avoid identity spoofing and other security risks. For database storage /// and lookup please use the property. /// /// public override string FriendlyIdentifierForDisplay { get { return this.Endpoint.FriendlyIdentifierForDisplay; } } /// /// Gets the detailed success or failure status of the authentication attempt. /// public override AuthenticationStatus Status { get { return AuthenticationStatus.Authenticated; } } #endregion /// /// Gets the OpenID service endpoint reconstructed from the assertion message. /// /// /// This information is straight from the Provider, and therefore must not /// be trusted until verified as matching the discovery information for /// the claimed identifier to avoid a Provider asserting an Identifier /// for which it has no authority. /// internal IdentifierDiscoveryResult Endpoint { get; private set; } /// /// Gets the positive assertion response message. /// protected internal new PositiveAssertionResponse Response { get { return (PositiveAssertionResponse)base.Response; } } /// /// Verifies that the positive assertion data matches the results of /// discovery on the Claimed Identifier. /// /// The relying party. /// /// 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. /// private void VerifyDiscoveryMatchesAssertion(OpenIdRelyingParty relyingParty) { Logger.OpenId.Debug("Verifying assertion matches identifier discovery results..."); // Ensure that we abide by the RP's rules regarding RequireSsl for this discovery step. Identifier claimedId = this.Response.ClaimedIdentifier; if (relyingParty.SecuritySettings.RequireSsl) { if (!claimedId.TryRequireSsl(out claimedId)) { Logger.OpenId.ErrorFormat("This site is configured to accept only SSL-protected OpenIDs, but {0} was asserted and must be rejected.", this.Response.ClaimedIdentifier); ErrorUtilities.ThrowProtocol(OpenIdStrings.RequireSslNotSatisfiedByAssertedClaimedId, this.Response.ClaimedIdentifier); } } // Check whether this particular identifier presents a problem with HTTP discovery // due to limitations in the .NET Uri class. UriIdentifier claimedIdUri = claimedId as UriIdentifier; if (claimedIdUri != null && claimedIdUri.ProblematicNormalization) { ErrorUtilities.VerifyProtocol(relyingParty.SecuritySettings.AllowApproximateIdentifierDiscovery, OpenIdStrings.ClaimedIdentifierDefiesDotNetNormalization); Logger.OpenId.WarnFormat("Positive assertion for claimed identifier {0} cannot be precisely verified under partial trust hosting due to .NET limitation. An approximate verification will be attempted.", claimedId); } // While it LOOKS like we're performing discovery over HTTP again // Yadis.IdentifierDiscoveryCachePolicy is set to HttpRequestCacheLevel.CacheIfAvailable // which means that the .NET runtime is caching our discoveries for us. This turns out // to be very fast and keeps our code clean and easily verifiable as correct and secure. // CAUTION: if this discovery is ever made to be skipped based on previous discovery // data that was saved to the return_to URL, be careful to verify that that information // 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 = relyingParty.Discover(claimedId); ErrorUtilities.VerifyProtocol( discoveryResults.Contains(this.Endpoint), OpenIdStrings.IssuedAssertionFailsIdentifierDiscovery, this.Endpoint, discoveryResults.ToStringDeferred(true)); } } }