summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/PositiveAuthenticationResponse.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/PositiveAuthenticationResponse.cs')
-rw-r--r--src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/PositiveAuthenticationResponse.cs174
1 files changed, 174 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/PositiveAuthenticationResponse.cs b/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/PositiveAuthenticationResponse.cs
new file mode 100644
index 0000000..3e2298c
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/PositiveAuthenticationResponse.cs
@@ -0,0 +1,174 @@
+//-----------------------------------------------------------------------
+// <copyright file="PositiveAuthenticationResponse.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+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;
+
+ /// <summary>
+ /// Wraps a positive assertion response in an <see cref="IAuthenticationResponse"/> instance
+ /// for public consumption by the host web site.
+ /// </summary>
+ [DebuggerDisplay("Status: {Status}, ClaimedIdentifier: {ClaimedIdentifier}")]
+ internal class PositiveAuthenticationResponse : PositiveAnonymousResponse {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PositiveAuthenticationResponse"/> class.
+ /// </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)
+ : base(response) {
+ Contract.Requires<ArgumentNullException>(relyingParty != null);
+
+ 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
+
+ /// <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>
+ /// <value></value>
+ /// <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 override Identifier ClaimedIdentifier {
+ get { return this.Endpoint.ClaimedIdentifier; }
+ }
+
+ /// <summary>
+ /// Gets a user-friendly OpenID Identifier for display purposes ONLY.
+ /// </summary>
+ /// <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 override string FriendlyIdentifierForDisplay {
+ get { return this.Endpoint.FriendlyIdentifierForDisplay; }
+ }
+
+ /// <summary>
+ /// Gets the detailed success or failure status of the authentication attempt.
+ /// </summary>
+ public override AuthenticationStatus Status {
+ get { return AuthenticationStatus.Authenticated; }
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Gets the OpenID service endpoint reconstructed from the assertion message.
+ /// </summary>
+ /// <remarks>
+ /// 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.
+ /// </remarks>
+ internal IdentifierDiscoveryResult Endpoint { get; private set; }
+
+ /// <summary>
+ /// Gets the positive assertion response message.
+ /// </summary>
+ protected internal new PositiveAssertionResponse Response {
+ get { return (PositiveAssertionResponse)base.Response; }
+ }
+
+ /// <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(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));
+ }
+ }
+}