summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty')
-rw-r--r--src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/AssociationManager.cs246
-rw-r--r--src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/AssociationPreference.cs42
-rw-r--r--src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Associations.cs127
-rw-r--r--src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/AuthenticationRequest.cs594
-rw-r--r--src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Behaviors/AXFetchAsSregTransform.cs76
-rw-r--r--src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Behaviors/GsaIcamProfile.cs124
-rw-r--r--src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/CryptoKeyStoreAsRelyingPartyAssociationStore.cs87
-rw-r--r--src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/DuplicateRequestedHostsComparer.cs74
-rw-r--r--src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Extensions/ExtensionsInteropHelper.cs152
-rw-r--r--src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Extensions/UIUtilities.cs42
-rw-r--r--src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/FailedAuthenticationResponse.cs299
-rw-r--r--src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/IRelyingPartyAssociationStore.cs153
-rw-r--r--src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/ISetupRequiredAuthenticationResponse.cs51
-rw-r--r--src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/NegativeAuthenticationResponse.cs313
-rw-r--r--src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/OpenIdRelyingParty.cd1
-rw-r--r--src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/OpenIdRelyingParty.cs903
-rw-r--r--src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/PositiveAnonymousResponse.cs347
-rw-r--r--src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/PositiveAuthenticationResponse.cs174
-rw-r--r--src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/PositiveAuthenticationResponseSnapshot.cs304
-rw-r--r--src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/SimpleXrdsProviderEndpoint.cs78
-rw-r--r--src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/StandardRelyingPartyApplicationStore.cs110
-rw-r--r--src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/WellKnownProviders.cs52
22 files changed, 4349 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/AssociationManager.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/AssociationManager.cs
new file mode 100644
index 0000000..eb501c5
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/AssociationManager.cs
@@ -0,0 +1,246 @@
+//-----------------------------------------------------------------------
+// <copyright file="AssociationManager.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.Net;
+ using System.Security;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.ChannelElements;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// Manages the establishment, storage and retrieval of associations at the relying party.
+ /// </summary>
+ internal class AssociationManager {
+ /// <summary>
+ /// The storage to use for saving and retrieving associations. May be null.
+ /// </summary>
+ private readonly IRelyingPartyAssociationStore associationStore;
+
+ /// <summary>
+ /// Backing field for the <see cref="Channel"/> property.
+ /// </summary>
+ private Channel channel;
+
+ /// <summary>
+ /// Backing field for the <see cref="SecuritySettings"/> property.
+ /// </summary>
+ private RelyingPartySecuritySettings securitySettings;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AssociationManager"/> class.
+ /// </summary>
+ /// <param name="channel">The channel the relying party is using.</param>
+ /// <param name="associationStore">The association store. May be null for dumb mode relying parties.</param>
+ /// <param name="securitySettings">The security settings.</param>
+ internal AssociationManager(Channel channel, IRelyingPartyAssociationStore associationStore, RelyingPartySecuritySettings securitySettings) {
+ Requires.NotNull(channel, "channel");
+ Requires.NotNull(securitySettings, "securitySettings");
+
+ this.channel = channel;
+ this.associationStore = associationStore;
+ this.securitySettings = securitySettings;
+ }
+
+ /// <summary>
+ /// Gets or sets the channel to use for establishing associations.
+ /// </summary>
+ /// <value>The channel.</value>
+ internal Channel Channel {
+ get {
+ return this.channel;
+ }
+
+ set {
+ Requires.NotNull(value, "value");
+ this.channel = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the security settings to apply in choosing association types to support.
+ /// </summary>
+ internal RelyingPartySecuritySettings SecuritySettings {
+ get {
+ return this.securitySettings;
+ }
+
+ set {
+ Requires.NotNull(value, "value");
+ this.securitySettings = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this instance has an association store.
+ /// </summary>
+ /// <value>
+ /// <c>true</c> if the relying party can act in 'smart' mode;
+ /// <c>false</c> if the relying party must always act in 'dumb' mode.
+ /// </value>
+ internal bool HasAssociationStore {
+ get { return this.associationStore != null; }
+ }
+
+ /// <summary>
+ /// Gets the storage to use for saving and retrieving associations. May be null.
+ /// </summary>
+ internal IRelyingPartyAssociationStore AssociationStoreTestHook {
+ get { return this.associationStore; }
+ }
+
+ /// <summary>
+ /// Gets an association between this Relying Party and a given Provider
+ /// if it already exists in the association store.
+ /// </summary>
+ /// <param name="provider">The provider to create an association with.</param>
+ /// <returns>The association if one exists and has useful life remaining. Otherwise <c>null</c>.</returns>
+ internal Association GetExistingAssociation(IProviderEndpoint provider) {
+ Requires.NotNull(provider, "provider");
+
+ // If the RP has no application store for associations, there's no point in creating one.
+ if (this.associationStore == null) {
+ return null;
+ }
+
+ Association association = this.associationStore.GetAssociation(provider.Uri, this.SecuritySettings);
+
+ // If the returned association does not fulfill security requirements, ignore it.
+ if (association != null && !this.SecuritySettings.IsAssociationInPermittedRange(association)) {
+ association = null;
+ }
+
+ if (association != null && !association.HasUsefulLifeRemaining) {
+ association = null;
+ }
+
+ return association;
+ }
+
+ /// <summary>
+ /// Gets an existing association with the specified Provider, or attempts to create
+ /// a new association of one does not already exist.
+ /// </summary>
+ /// <param name="provider">The provider to get an association for.</param>
+ /// <returns>The existing or new association; <c>null</c> if none existed and one could not be created.</returns>
+ internal Association GetOrCreateAssociation(IProviderEndpoint provider) {
+ return this.GetExistingAssociation(provider) ?? this.CreateNewAssociation(provider);
+ }
+
+ /// <summary>
+ /// Creates a new association with a given Provider.
+ /// </summary>
+ /// <param name="provider">The provider to create an association with.</param>
+ /// <returns>
+ /// The newly created association, or null if no association can be created with
+ /// the given Provider given the current security settings.
+ /// </returns>
+ /// <remarks>
+ /// A new association is created and returned even if one already exists in the
+ /// association store.
+ /// Any new association is automatically added to the <see cref="associationStore"/>.
+ /// </remarks>
+ private Association CreateNewAssociation(IProviderEndpoint provider) {
+ Requires.NotNull(provider, "provider");
+
+ // If there is no association store, there is no point in creating an association.
+ if (this.associationStore == null) {
+ return null;
+ }
+
+ try {
+ var associateRequest = AssociateRequestRelyingParty.Create(this.securitySettings, provider);
+
+ const int RenegotiateRetries = 1;
+ return this.CreateNewAssociation(provider, associateRequest, RenegotiateRetries);
+ } catch (VerificationException ex) {
+ // See Trac ticket #163. In partial trust host environments, the
+ // Diffie-Hellman implementation we're using for HTTP OP endpoints
+ // sometimes causes the CLR to throw:
+ // "VerificationException: Operation could destabilize the runtime."
+ // Just give up and use dumb mode in this case.
+ Logger.OpenId.ErrorFormat("VerificationException occurred while trying to create an association with {0}. {1}", provider.Uri, ex);
+ return null;
+ }
+ }
+
+ /// <summary>
+ /// Creates a new association with a given Provider.
+ /// </summary>
+ /// <param name="provider">The provider to create an association with.</param>
+ /// <param name="associateRequest">The associate request. May be <c>null</c>, which will always result in a <c>null</c> return value..</param>
+ /// <param name="retriesRemaining">The number of times to try the associate request again if the Provider suggests it.</param>
+ /// <returns>
+ /// The newly created association, or null if no association can be created with
+ /// the given Provider given the current security settings.
+ /// </returns>
+ private Association CreateNewAssociation(IProviderEndpoint provider, AssociateRequest associateRequest, int retriesRemaining) {
+ Requires.NotNull(provider, "provider");
+
+ if (associateRequest == null || retriesRemaining < 0) {
+ // this can happen if security requirements and protocol conflict
+ // to where there are no association types to choose from.
+ return null;
+ }
+
+ try {
+ var associateResponse = this.channel.Request(associateRequest);
+ var associateSuccessfulResponse = associateResponse as IAssociateSuccessfulResponseRelyingParty;
+ var associateUnsuccessfulResponse = associateResponse as AssociateUnsuccessfulResponse;
+ if (associateSuccessfulResponse != null) {
+ Association association = associateSuccessfulResponse.CreateAssociationAtRelyingParty(associateRequest);
+ this.associationStore.StoreAssociation(provider.Uri, association);
+ return association;
+ } else if (associateUnsuccessfulResponse != null) {
+ if (string.IsNullOrEmpty(associateUnsuccessfulResponse.AssociationType)) {
+ Logger.OpenId.Debug("Provider rejected an association request and gave no suggestion as to an alternative association type. Giving up.");
+ return null;
+ }
+
+ if (!this.securitySettings.IsAssociationInPermittedRange(Protocol.Lookup(provider.Version), associateUnsuccessfulResponse.AssociationType)) {
+ Logger.OpenId.DebugFormat("Provider rejected an association request and suggested '{0}' as an association to try, which this Relying Party does not support. Giving up.", associateUnsuccessfulResponse.AssociationType);
+ return null;
+ }
+
+ if (retriesRemaining <= 0) {
+ Logger.OpenId.Debug("Unable to agree on an association type with the Provider in the allowed number of retries. Giving up.");
+ return null;
+ }
+
+ // Make sure the Provider isn't suggesting an incompatible pair of association/session types.
+ Protocol protocol = Protocol.Lookup(provider.Version);
+ ErrorUtilities.VerifyProtocol(
+ HmacShaAssociation.IsDHSessionCompatible(protocol, associateUnsuccessfulResponse.AssociationType, associateUnsuccessfulResponse.SessionType),
+ OpenIdStrings.IncompatibleAssociationAndSessionTypes,
+ associateUnsuccessfulResponse.AssociationType,
+ associateUnsuccessfulResponse.SessionType);
+
+ associateRequest = AssociateRequestRelyingParty.Create(this.securitySettings, provider, associateUnsuccessfulResponse.AssociationType, associateUnsuccessfulResponse.SessionType);
+ return this.CreateNewAssociation(provider, associateRequest, retriesRemaining - 1);
+ } else {
+ throw new ProtocolException(MessagingStrings.UnexpectedMessageReceivedOfMany);
+ }
+ } catch (ProtocolException ex) {
+ // If the association failed because the remote server can't handle Expect: 100 Continue headers,
+ // then our web request handler should have already accomodated for future calls. Go ahead and
+ // immediately make one of those future calls now to try to get the association to succeed.
+ if (StandardWebRequestHandler.IsExceptionFrom417ExpectationFailed(ex)) {
+ return this.CreateNewAssociation(provider, associateRequest, retriesRemaining - 1);
+ }
+
+ // Since having associations with OPs is not totally critical, we'll log and eat
+ // the exception so that auth may continue in dumb mode.
+ Logger.OpenId.ErrorFormat("An error occurred while trying to create an association with {0}. {1}", provider.Uri, ex);
+ return null;
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/AssociationPreference.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/AssociationPreference.cs
new file mode 100644
index 0000000..9f4a21f
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/AssociationPreference.cs
@@ -0,0 +1,42 @@
+//-----------------------------------------------------------------------
+// <copyright file="AssociationPreference.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.RelyingParty {
+ using System;
+ using System.Collections.Generic;
+ using System.Text;
+
+ /// <summary>
+ /// Preferences regarding creation and use of an association between a relying party
+ /// and provider for authentication.
+ /// </summary>
+ internal enum AssociationPreference {
+ /// <summary>
+ /// Indicates that an association should be created for use in authentication
+ /// if one has not already been established between the relying party and the
+ /// selected provider.
+ /// </summary>
+ /// <remarks>
+ /// Even with this value, if an association attempt fails or the relying party
+ /// has no application store to recall associations, the authentication may
+ /// proceed without an association.
+ /// </remarks>
+ IfPossible,
+
+ /// <summary>
+ /// Indicates that an association should be used for authentication only if
+ /// it happens to already exist.
+ /// </summary>
+ IfAlreadyEstablished,
+
+ /// <summary>
+ /// Indicates that an authentication attempt should NOT use an OpenID association
+ /// between the relying party and the provider, even if an association was previously
+ /// created.
+ /// </summary>
+ Never,
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Associations.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Associations.cs
new file mode 100644
index 0000000..e65750f
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Associations.cs
@@ -0,0 +1,127 @@
+//-----------------------------------------------------------------------
+// <copyright file="Associations.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.RelyingParty {
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.ObjectModel;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// A dictionary of handle/Association pairs.
+ /// </summary>
+ /// <remarks>
+ /// Each method is locked, even if it is only one line, so that they are thread safe
+ /// against each other, particularly the ones that enumerate over the list, since they
+ /// can break if the collection is changed by another thread during enumeration.
+ /// </remarks>
+ [DebuggerDisplay("Count = {assocs.Count}")]
+ [ContractVerification(true)]
+ internal class Associations {
+ /// <summary>
+ /// The lookup table where keys are the association handles and values are the associations themselves.
+ /// </summary>
+ [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+ private readonly KeyedCollection<string, Association> associations = new KeyedCollectionDelegate<string, Association>(assoc => assoc.Handle);
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Associations"/> class.
+ /// </summary>
+ public Associations() {
+ }
+
+ /// <summary>
+ /// Gets the <see cref="Association"/>s ordered in order of descending issue date
+ /// (most recently issued comes first). An empty sequence if no valid associations exist.
+ /// </summary>
+ /// <remarks>
+ /// This property is used by relying parties that are initiating authentication requests.
+ /// It does not apply to Providers, which always need a specific association by handle.
+ /// </remarks>
+ public IEnumerable<Association> Best {
+ get {
+ Contract.Ensures(Contract.Result<IEnumerable<Association>>() != null);
+
+ lock (this.associations) {
+ return this.associations.OrderByDescending(assoc => assoc.Issued);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Stores an <see cref="Association"/> in the collection.
+ /// </summary>
+ /// <param name="association">The association to add to the collection.</param>
+ public void Set(Association association) {
+ Requires.NotNull(association, "association");
+ Contract.Ensures(this.Get(association.Handle) == association);
+ lock (this.associations) {
+ this.associations.Remove(association.Handle); // just in case one already exists.
+ this.associations.Add(association);
+ }
+
+ Contract.Assume(this.Get(association.Handle) == association);
+ }
+
+ /// <summary>
+ /// Returns the <see cref="Association"/> with the given handle. Null if not found.
+ /// </summary>
+ /// <param name="handle">The handle to the required association.</param>
+ /// <returns>The desired association, or null if none with the given handle could be found.</returns>
+ [Pure]
+ public Association Get(string handle) {
+ Requires.NotNullOrEmpty(handle, "handle");
+
+ lock (this.associations) {
+ if (this.associations.Contains(handle)) {
+ return this.associations[handle];
+ } else {
+ return null;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Removes the <see cref="Association"/> with the given handle.
+ /// </summary>
+ /// <param name="handle">The handle to the required association.</param>
+ /// <returns>Whether an <see cref="Association"/> with the given handle was in the collection for removal.</returns>
+ public bool Remove(string handle) {
+ Requires.NotNullOrEmpty(handle, "handle");
+ lock (this.associations) {
+ return this.associations.Remove(handle);
+ }
+ }
+
+ /// <summary>
+ /// Removes all expired associations from the collection.
+ /// </summary>
+ public void ClearExpired() {
+ lock (this.associations) {
+ var expireds = this.associations.Where(assoc => assoc.IsExpired).ToList();
+ foreach (Association assoc in expireds) {
+ this.associations.Remove(assoc.Handle);
+ }
+ }
+ }
+
+#if CONTRACTS_FULL
+ /// <summary>
+ /// Verifies conditions that should be true for any valid state of this object.
+ /// </summary>
+ [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")]
+ [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")]
+ [ContractInvariantMethod]
+ private void ObjectInvariant() {
+ Contract.Invariant(this.associations != null);
+ }
+#endif
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/AuthenticationRequest.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/AuthenticationRequest.cs
new file mode 100644
index 0000000..c85c0c5
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/AuthenticationRequest.cs
@@ -0,0 +1,594 @@
+//-----------------------------------------------------------------------
+// <copyright file="AuthenticationRequest.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.RelyingParty {
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.Specialized;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Text;
+ using System.Threading;
+ using System.Web;
+
+ using DotNetOpenAuth.Configuration;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.ChannelElements;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// Facilitates customization and creation and an authentication request
+ /// that a Relying Party is preparing to send.
+ /// </summary>
+ internal class AuthenticationRequest : IAuthenticationRequest {
+ /// <summary>
+ /// The name of the internal callback parameter to use to store the user-supplied identifier.
+ /// </summary>
+ internal const string UserSuppliedIdentifierParameterName = OpenIdUtilities.CustomParameterPrefix + "userSuppliedIdentifier";
+
+ /// <summary>
+ /// The relying party that created this request object.
+ /// </summary>
+ private readonly OpenIdRelyingParty RelyingParty;
+
+ /// <summary>
+ /// How an association may or should be created or used in the formulation of the
+ /// authentication request.
+ /// </summary>
+ private AssociationPreference associationPreference = AssociationPreference.IfPossible;
+
+ /// <summary>
+ /// The extensions that have been added to this authentication request.
+ /// </summary>
+ private List<IOpenIdMessageExtension> extensions = new List<IOpenIdMessageExtension>();
+
+ /// <summary>
+ /// Arguments to add to the return_to part of the query string, so that
+ /// these values come back to the consumer when the user agent returns.
+ /// </summary>
+ private Dictionary<string, string> returnToArgs = new Dictionary<string, string>();
+
+ /// <summary>
+ /// A value indicating whether the return_to callback arguments must be signed.
+ /// </summary>
+ /// <remarks>
+ /// This field defaults to false, but is set to true as soon as the first callback argument
+ /// is added that indicates it must be signed. At which point, all arguments are signed
+ /// even if individual ones did not need to be.
+ /// </remarks>
+ private bool returnToArgsMustBeSigned;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AuthenticationRequest"/> class.
+ /// </summary>
+ /// <param name="discoveryResult">The endpoint that describes the OpenID Identifier and Provider that will complete the authentication.</param>
+ /// <param name="realm">The realm, or root URL, of the host web site.</param>
+ /// <param name="returnToUrl">The base return_to URL that the Provider should return the user to to complete authentication. This should not include callback parameters as these should be added using the <see cref="AddCallbackArguments(string, string)"/> method.</param>
+ /// <param name="relyingParty">The relying party that created this instance.</param>
+ private AuthenticationRequest(IdentifierDiscoveryResult discoveryResult, Realm realm, Uri returnToUrl, OpenIdRelyingParty relyingParty) {
+ Requires.NotNull(discoveryResult, "discoveryResult");
+ Requires.NotNull(realm, "realm");
+ Requires.NotNull(returnToUrl, "returnToUrl");
+ Requires.NotNull(relyingParty, "relyingParty");
+
+ this.DiscoveryResult = discoveryResult;
+ this.RelyingParty = relyingParty;
+ this.Realm = realm;
+ this.ReturnToUrl = returnToUrl;
+
+ this.Mode = AuthenticationRequestMode.Setup;
+ }
+
+ #region IAuthenticationRequest Members
+
+ /// <summary>
+ /// Gets or sets the mode the Provider should use during authentication.
+ /// </summary>
+ /// <value></value>
+ public AuthenticationRequestMode Mode { get; set; }
+
+ /// <summary>
+ /// Gets the HTTP response the relying party should send to the user agent
+ /// to redirect it to the OpenID Provider to start the OpenID authentication process.
+ /// </summary>
+ /// <value></value>
+ public OutgoingWebResponse RedirectingResponse {
+ get {
+ foreach (var behavior in this.RelyingParty.Behaviors) {
+ behavior.OnOutgoingAuthenticationRequest(this);
+ }
+
+ return this.RelyingParty.Channel.PrepareResponse(this.CreateRequestMessage());
+ }
+ }
+
+ /// <summary>
+ /// Gets the URL that the user agent will return to after authentication
+ /// completes or fails at the Provider.
+ /// </summary>
+ /// <value></value>
+ public Uri ReturnToUrl { get; private set; }
+
+ /// <summary>
+ /// Gets the URL that identifies this consumer web application that
+ /// the Provider will display to the end user.
+ /// </summary>
+ public Realm Realm { get; private set; }
+
+ /// <summary>
+ /// Gets the Claimed Identifier that the User Supplied Identifier
+ /// resolved to. Null if the user provided an OP Identifier
+ /// (directed identity).
+ /// </summary>
+ /// <value></value>
+ /// <remarks>
+ /// Null is returned if the user is using the directed identity feature
+ /// of OpenID 2.0 to make it nearly impossible for a relying party site
+ /// to improperly store the reserved OpenID URL used for directed identity
+ /// as a user's own Identifier.
+ /// However, to test for the Directed Identity feature, please test the
+ /// <see cref="IsDirectedIdentity"/> property rather than testing this
+ /// property for a null value.
+ /// </remarks>
+ public Identifier ClaimedIdentifier {
+ get { return this.IsDirectedIdentity ? null : this.DiscoveryResult.ClaimedIdentifier; }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether the authenticating user has chosen to let the Provider
+ /// determine and send the ClaimedIdentifier after authentication.
+ /// </summary>
+ public bool IsDirectedIdentity {
+ get { return this.DiscoveryResult.ClaimedIdentifier == this.DiscoveryResult.Protocol.ClaimedIdentifierForOPIdentifier; }
+ }
+
+ /// <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.
+ /// </summary>
+ public IProviderEndpoint Provider {
+ get { return this.DiscoveryResult; }
+ }
+
+ /// <summary>
+ /// Gets the discovery result leading to the formulation of this request.
+ /// </summary>
+ /// <value>The discovery result.</value>
+ public IdentifierDiscoveryResult DiscoveryResult { get; private set; }
+
+ #endregion
+
+ /// <summary>
+ /// Gets or sets how an association may or should be created or used
+ /// in the formulation of the authentication request.
+ /// </summary>
+ internal AssociationPreference AssociationPreference {
+ get { return this.associationPreference; }
+ set { this.associationPreference = value; }
+ }
+
+ /// <summary>
+ /// Gets the extensions that have been added to the request.
+ /// </summary>
+ internal IEnumerable<IOpenIdMessageExtension> AppliedExtensions {
+ get { return this.extensions; }
+ }
+
+ /// <summary>
+ /// Gets the list of extensions for this request.
+ /// </summary>
+ internal IList<IOpenIdMessageExtension> Extensions {
+ get { return this.extensions; }
+ }
+
+ #region IAuthenticationRequest methods
+
+ /// <summary>
+ /// Makes a dictionary of key/value pairs available when the authentication is completed.
+ /// </summary>
+ /// <param name="arguments">The arguments to add to the request's return_to URI.</param>
+ /// <remarks>
+ /// <para>Note that these values are NOT protected against eavesdropping in transit. No
+ /// privacy-sensitive data should be stored using this method.</para>
+ /// <para>The values stored here can be retrieved using
+ /// <see cref="IAuthenticationResponse.GetCallbackArguments"/>, which will only return the value
+ /// if it hasn't been tampered with in transit.</para>
+ /// <para>Since the data set here is sent in the querystring of the request and some
+ /// servers place limits on the size of a request URL, this data should be kept relatively
+ /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para>
+ /// </remarks>
+ public void AddCallbackArguments(IDictionary<string, string> arguments) {
+ ErrorUtilities.VerifyOperation(this.RelyingParty.CanSignCallbackArguments, OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IRelyingPartyAssociationStore).Name, typeof(OpenIdRelyingParty).Name);
+
+ this.returnToArgsMustBeSigned = true;
+ foreach (var pair in arguments) {
+ ErrorUtilities.VerifyArgument(!string.IsNullOrEmpty(pair.Key), MessagingStrings.UnexpectedNullOrEmptyKey);
+ ErrorUtilities.VerifyArgument(pair.Value != null, MessagingStrings.UnexpectedNullValue, pair.Key);
+
+ this.returnToArgs.Add(pair.Key, pair.Value);
+ }
+ }
+
+ /// <summary>
+ /// Makes a key/value pair available when the authentication is completed.
+ /// </summary>
+ /// <param name="key">The parameter name.</param>
+ /// <param name="value">The value of the argument.</param>
+ /// <remarks>
+ /// <para>Note that these values are NOT protected against eavesdropping in transit. No
+ /// privacy-sensitive data should be stored using this method.</para>
+ /// <para>The value stored here can be retrieved using
+ /// <see cref="IAuthenticationResponse.GetCallbackArgument"/>, which will only return the value
+ /// if it hasn't been tampered with in transit.</para>
+ /// <para>Since the data set here is sent in the querystring of the request and some
+ /// servers place limits on the size of a request URL, this data should be kept relatively
+ /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para>
+ /// </remarks>
+ public void AddCallbackArguments(string key, string value) {
+ ErrorUtilities.VerifyOperation(this.RelyingParty.CanSignCallbackArguments, OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IRelyingPartyAssociationStore).Name, typeof(OpenIdRelyingParty).Name);
+
+ this.returnToArgsMustBeSigned = true;
+ this.returnToArgs.Add(key, value);
+ }
+
+ /// <summary>
+ /// Makes a key/value pair available when the authentication is completed.
+ /// </summary>
+ /// <param name="key">The parameter name.</param>
+ /// <param name="value">The value of the argument. Must not be null.</param>
+ /// <remarks>
+ /// <para>Note that these values are NOT protected against tampering in transit. No
+ /// security-sensitive data should be stored using this method.</para>
+ /// <para>The value stored here can be retrieved using
+ /// <see cref="IAuthenticationResponse.GetCallbackArgument"/>.</para>
+ /// <para>Since the data set here is sent in the querystring of the request and some
+ /// servers place limits on the size of a request URL, this data should be kept relatively
+ /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para>
+ /// </remarks>
+ public void SetCallbackArgument(string key, string value) {
+ ErrorUtilities.VerifyOperation(this.RelyingParty.CanSignCallbackArguments, OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IRelyingPartyAssociationStore).Name, typeof(OpenIdRelyingParty).Name);
+
+ this.returnToArgsMustBeSigned = true;
+ this.returnToArgs[key] = value;
+ }
+
+ /// <summary>
+ /// Makes a key/value pair available when the authentication is completed without
+ /// requiring a return_to signature to protect against tampering of the callback argument.
+ /// </summary>
+ /// <param name="key">The parameter name.</param>
+ /// <param name="value">The value of the argument. Must not be null.</param>
+ /// <remarks>
+ /// <para>Note that these values are NOT protected against eavesdropping or tampering in transit. No
+ /// security-sensitive data should be stored using this method. </para>
+ /// <para>The value stored here can be retrieved using
+ /// <see cref="IAuthenticationResponse.GetCallbackArgument"/>.</para>
+ /// <para>Since the data set here is sent in the querystring of the request and some
+ /// servers place limits on the size of a request URL, this data should be kept relatively
+ /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para>
+ /// </remarks>
+ public void SetUntrustedCallbackArgument(string key, string value) {
+ this.returnToArgs[key] = value;
+ }
+
+ /// <summary>
+ /// Adds an OpenID extension to the request directed at the OpenID provider.
+ /// </summary>
+ /// <param name="extension">The initialized extension to add to the request.</param>
+ public void AddExtension(IOpenIdMessageExtension extension) {
+ this.extensions.Add(extension);
+ }
+
+ /// <summary>
+ /// Redirects the user agent to the provider for authentication.
+ /// </summary>
+ /// <remarks>
+ /// This method requires an ASP.NET HttpContext.
+ /// </remarks>
+ public void RedirectToProvider() {
+ this.RedirectingResponse.Send();
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Performs identifier discovery, creates associations and generates authentication requests
+ /// on-demand for as long as new ones can be generated based on the results of Identifier discovery.
+ /// </summary>
+ /// <param name="userSuppliedIdentifier">The user supplied identifier.</param>
+ /// <param name="relyingParty">The relying party.</param>
+ /// <param name="realm">The realm.</param>
+ /// <param name="returnToUrl">The return_to base URL.</param>
+ /// <param name="createNewAssociationsAsNeeded">if set to <c>true</c>, associations that do not exist between this Relying Party and the asserting Providers are created before the authentication request is created.</param>
+ /// <returns>
+ /// A sequence of authentication requests, any of which constitutes a valid identity assertion on the Claimed Identifier.
+ /// Never null, but may be empty.
+ /// </returns>
+ internal static IEnumerable<AuthenticationRequest> Create(Identifier userSuppliedIdentifier, OpenIdRelyingParty relyingParty, Realm realm, Uri returnToUrl, bool createNewAssociationsAsNeeded) {
+ Requires.NotNull(userSuppliedIdentifier, "userSuppliedIdentifier");
+ Requires.NotNull(relyingParty, "relyingParty");
+ Requires.NotNull(realm, "realm");
+ Contract.Ensures(Contract.Result<IEnumerable<AuthenticationRequest>>() != null);
+
+ // Normalize the portion of the return_to path that correlates to the realm for capitalization.
+ // (so that if a web app base path is /MyApp/, but the URL of this request happens to be
+ // /myapp/login.aspx, we bump up the return_to Url to use /MyApp/ so it matches the realm.
+ UriBuilder returnTo = new UriBuilder(returnToUrl);
+ if (returnTo.Path.StartsWith(realm.AbsolutePath, StringComparison.OrdinalIgnoreCase) &&
+ !returnTo.Path.StartsWith(realm.AbsolutePath, StringComparison.Ordinal)) {
+ returnTo.Path = realm.AbsolutePath + returnTo.Path.Substring(realm.AbsolutePath.Length);
+ returnToUrl = returnTo.Uri;
+ }
+
+ userSuppliedIdentifier = userSuppliedIdentifier.TrimFragment();
+ if (relyingParty.SecuritySettings.RequireSsl) {
+ // Rather than check for successful SSL conversion at this stage,
+ // We'll wait for secure discovery to fail on the new identifier.
+ if (!userSuppliedIdentifier.TryRequireSsl(out userSuppliedIdentifier)) {
+ // But at least log the failure.
+ Logger.OpenId.WarnFormat("RequireSsl mode is on, so discovery on insecure identifier {0} will yield no results.", userSuppliedIdentifier);
+ }
+ }
+
+ if (Logger.OpenId.IsWarnEnabled && returnToUrl.Query != null) {
+ NameValueCollection returnToArgs = HttpUtility.ParseQueryString(returnToUrl.Query);
+ foreach (string key in returnToArgs) {
+ if (OpenIdRelyingParty.IsOpenIdSupportingParameter(key)) {
+ Logger.OpenId.WarnFormat("OpenID argument \"{0}\" found in return_to URL. This can corrupt an OpenID response.", key);
+ }
+ }
+ }
+
+ // Throw an exception now if the realm and the return_to URLs don't match
+ // as required by the provider. We could wait for the provider to test this and
+ // fail, but this will be faster and give us a better error message.
+ ErrorUtilities.VerifyProtocol(realm.Contains(returnToUrl), OpenIdStrings.ReturnToNotUnderRealm, returnToUrl, realm);
+
+ // Perform discovery right now (not deferred).
+ IEnumerable<IdentifierDiscoveryResult> serviceEndpoints;
+ try {
+ var results = relyingParty.Discover(userSuppliedIdentifier).CacheGeneratedResults();
+
+ // If any OP Identifier service elements were found, we must not proceed
+ // to use any Claimed Identifier services, per OpenID 2.0 sections 7.3.2.2 and 11.2.
+ // For a discussion on this topic, see
+ // http://groups.google.com/group/dotnetopenid/browse_thread/thread/4b5a8c6b2210f387/5e25910e4d2252c8
+ // Usually the Discover method we called will automatically filter this for us, but
+ // just to be sure, we'll do it here as well since the RP may be configured to allow
+ // these dual identifiers for assertion verification purposes.
+ var opIdentifiers = results.Where(result => result.ClaimedIdentifier == result.Protocol.ClaimedIdentifierForOPIdentifier).CacheGeneratedResults();
+ var claimedIdentifiers = results.Where(result => result.ClaimedIdentifier != result.Protocol.ClaimedIdentifierForOPIdentifier);
+ serviceEndpoints = opIdentifiers.Any() ? opIdentifiers : claimedIdentifiers;
+ } catch (ProtocolException ex) {
+ Logger.Yadis.ErrorFormat("Error while performing discovery on: \"{0}\": {1}", userSuppliedIdentifier, ex);
+ serviceEndpoints = Enumerable.Empty<IdentifierDiscoveryResult>();
+ }
+
+ // Filter disallowed endpoints.
+ serviceEndpoints = relyingParty.SecuritySettings.FilterEndpoints(serviceEndpoints);
+
+ // Call another method that defers request generation.
+ return CreateInternal(userSuppliedIdentifier, relyingParty, realm, returnToUrl, serviceEndpoints, createNewAssociationsAsNeeded);
+ }
+
+ /// <summary>
+ /// Creates an instance of <see cref="AuthenticationRequest"/> FOR TESTING PURPOSES ONLY.
+ /// </summary>
+ /// <param name="discoveryResult">The discovery result.</param>
+ /// <param name="realm">The realm.</param>
+ /// <param name="returnTo">The return to.</param>
+ /// <param name="rp">The relying party.</param>
+ /// <returns>The instantiated <see cref="AuthenticationRequest"/>.</returns>
+ internal static AuthenticationRequest CreateForTest(IdentifierDiscoveryResult discoveryResult, Realm realm, Uri returnTo, OpenIdRelyingParty rp) {
+ return new AuthenticationRequest(discoveryResult, realm, returnTo, rp);
+ }
+
+ /// <summary>
+ /// 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>
+ internal SignedResponseRequest CreateRequestMessageTestHook()
+ {
+ return this.CreateRequestMessage();
+ }
+
+ /// <summary>
+ /// Performs deferred request generation for the <see cref="Create"/> method.
+ /// </summary>
+ /// <param name="userSuppliedIdentifier">The user supplied identifier.</param>
+ /// <param name="relyingParty">The relying party.</param>
+ /// <param name="realm">The realm.</param>
+ /// <param name="returnToUrl">The return_to base URL.</param>
+ /// <param name="serviceEndpoints">The discovered service endpoints on the Claimed Identifier.</param>
+ /// <param name="createNewAssociationsAsNeeded">if set to <c>true</c>, associations that do not exist between this Relying Party and the asserting Providers are created before the authentication request is created.</param>
+ /// <returns>
+ /// A sequence of authentication requests, any of which constitutes a valid identity assertion on the Claimed Identifier.
+ /// Never null, but may be empty.
+ /// </returns>
+ /// <remarks>
+ /// All data validation and cleansing steps must have ALREADY taken place
+ /// before calling this method.
+ /// </remarks>
+ private static IEnumerable<AuthenticationRequest> CreateInternal(Identifier userSuppliedIdentifier, OpenIdRelyingParty relyingParty, Realm realm, Uri returnToUrl, IEnumerable<IdentifierDiscoveryResult> serviceEndpoints, bool createNewAssociationsAsNeeded) {
+ // DO NOT USE CODE CONTRACTS IN THIS METHOD, since it uses yield return
+ ErrorUtilities.VerifyArgumentNotNull(userSuppliedIdentifier, "userSuppliedIdentifier");
+ ErrorUtilities.VerifyArgumentNotNull(relyingParty, "relyingParty");
+ ErrorUtilities.VerifyArgumentNotNull(realm, "realm");
+ ErrorUtilities.VerifyArgumentNotNull(serviceEndpoints, "serviceEndpoints");
+ ////Contract.Ensures(Contract.Result<IEnumerable<AuthenticationRequest>>() != null);
+
+ // If shared associations are required, then we had better have an association store.
+ ErrorUtilities.VerifyOperation(!relyingParty.SecuritySettings.RequireAssociation || relyingParty.AssociationManager.HasAssociationStore, OpenIdStrings.AssociationStoreRequired);
+
+ Logger.Yadis.InfoFormat("Performing discovery on user-supplied identifier: {0}", userSuppliedIdentifier);
+ IEnumerable<IdentifierDiscoveryResult> endpoints = FilterAndSortEndpoints(serviceEndpoints, relyingParty);
+
+ // Maintain a list of endpoints that we could not form an association with.
+ // We'll fallback to generating requests to these if the ones we CAN create
+ // an association with run out.
+ var failedAssociationEndpoints = new List<IdentifierDiscoveryResult>(0);
+
+ foreach (var endpoint in endpoints) {
+ Logger.OpenId.DebugFormat("Creating authentication request for user supplied Identifier: {0}", userSuppliedIdentifier);
+
+ // The strategy here is to prefer endpoints with whom we can create associations.
+ Association association = null;
+ if (relyingParty.AssociationManager.HasAssociationStore) {
+ // In some scenarios (like the AJAX control wanting ALL auth requests possible),
+ // we don't want to create associations with every Provider. But we'll use
+ // associations where they are already formed from previous authentications.
+ association = createNewAssociationsAsNeeded ? relyingParty.AssociationManager.GetOrCreateAssociation(endpoint) : relyingParty.AssociationManager.GetExistingAssociation(endpoint);
+ if (association == null && createNewAssociationsAsNeeded) {
+ Logger.OpenId.WarnFormat("Failed to create association with {0}. Skipping to next endpoint.", endpoint.ProviderEndpoint);
+
+ // No association could be created. Add it to the list of failed association
+ // endpoints and skip to the next available endpoint.
+ failedAssociationEndpoints.Add(endpoint);
+ continue;
+ }
+ }
+
+ yield return new AuthenticationRequest(endpoint, realm, returnToUrl, relyingParty);
+ }
+
+ // Now that we've run out of endpoints that respond to association requests,
+ // since we apparently are still running, the caller must want another request.
+ // We'll go ahead and generate the requests to OPs that may be down --
+ // unless associations are set as required in our security settings.
+ if (failedAssociationEndpoints.Count > 0) {
+ if (relyingParty.SecuritySettings.RequireAssociation) {
+ Logger.OpenId.Warn("Associations could not be formed with some Providers. Security settings require shared associations for authentication requests so these will be skipped.");
+ } else {
+ Logger.OpenId.Debug("Now generating requests for Provider endpoints that failed initial association attempts.");
+
+ foreach (var endpoint in failedAssociationEndpoints) {
+ Logger.OpenId.DebugFormat("Creating authentication request for user supplied Identifier: {0} at endpoint: {1}", userSuppliedIdentifier, endpoint.ProviderEndpoint.AbsoluteUri);
+
+ // Create the auth request, but prevent it from attempting to create an association
+ // because we've already tried. Let's not have it waste time trying again.
+ var authRequest = new AuthenticationRequest(endpoint, realm, returnToUrl, relyingParty);
+ authRequest.associationPreference = AssociationPreference.IfAlreadyEstablished;
+ yield return authRequest;
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Returns a filtered and sorted list of the available OP endpoints for a discovered Identifier.
+ /// </summary>
+ /// <param name="endpoints">The endpoints.</param>
+ /// <param name="relyingParty">The relying party.</param>
+ /// <returns>A filtered and sorted list of endpoints; may be empty if the input was empty or the filter removed all endpoints.</returns>
+ private static List<IdentifierDiscoveryResult> FilterAndSortEndpoints(IEnumerable<IdentifierDiscoveryResult> endpoints, OpenIdRelyingParty relyingParty) {
+ Requires.NotNull(endpoints, "endpoints");
+ Requires.NotNull(relyingParty, "relyingParty");
+
+ bool anyFilteredOut = false;
+ var filteredEndpoints = new List<IdentifierDiscoveryResult>();
+ foreach (var endpoint in endpoints) {
+ if (relyingParty.FilterEndpoint(endpoint)) {
+ filteredEndpoints.Add(endpoint);
+ } else {
+ anyFilteredOut = true;
+ }
+ }
+
+ // Sort endpoints so that the first one in the list is the most preferred one.
+ filteredEndpoints.OrderBy(ep => ep, relyingParty.EndpointOrder);
+
+ var endpointList = new List<IdentifierDiscoveryResult>(filteredEndpoints.Count);
+ foreach (var endpoint in filteredEndpoints) {
+ endpointList.Add(endpoint);
+ }
+
+ if (anyFilteredOut) {
+ Logger.Yadis.DebugFormat("Some endpoints were filtered out. Total endpoints remaining: {0}", filteredEndpoints.Count);
+ }
+ if (Logger.Yadis.IsDebugEnabled) {
+ if (MessagingUtilities.AreEquivalent(endpoints, endpointList)) {
+ Logger.Yadis.Debug("Filtering and sorting of endpoints did not affect the list.");
+ } else {
+ Logger.Yadis.Debug("After filtering and sorting service endpoints, this is the new prioritized list:");
+ Logger.Yadis.Debug(Util.ToStringDeferred(filteredEndpoints, true));
+ }
+ }
+
+ return endpointList;
+ }
+
+ /// <summary>
+ /// 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 SignedResponseRequest CreateRequestMessage() {
+ Association association = this.GetAssociation();
+
+ SignedResponseRequest request;
+ if (!this.IsExtensionOnly) {
+ CheckIdRequest authRequest = new CheckIdRequest(this.DiscoveryResult.Version, this.DiscoveryResult.ProviderEndpoint, this.Mode);
+ authRequest.ClaimedIdentifier = this.DiscoveryResult.ClaimedIdentifier;
+ authRequest.LocalIdentifier = this.DiscoveryResult.ProviderLocalIdentifier;
+ request = authRequest;
+ } else {
+ request = new SignedResponseRequest(this.DiscoveryResult.Version, this.DiscoveryResult.ProviderEndpoint, this.Mode);
+ }
+ request.Realm = this.Realm;
+ request.ReturnTo = this.ReturnToUrl;
+ request.AssociationHandle = association != null ? association.Handle : null;
+ request.SignReturnTo = this.returnToArgsMustBeSigned;
+ request.AddReturnToArguments(this.returnToArgs);
+ if (this.DiscoveryResult.UserSuppliedIdentifier != null && OpenIdElement.Configuration.RelyingParty.PreserveUserSuppliedIdentifier) {
+ request.AddReturnToArguments(UserSuppliedIdentifierParameterName, this.DiscoveryResult.UserSuppliedIdentifier.OriginalString);
+ }
+ foreach (IOpenIdMessageExtension extension in this.extensions) {
+ request.Extensions.Add(extension);
+ }
+
+ return request;
+ }
+
+ /// <summary>
+ /// Gets the association to use for this authentication request.
+ /// </summary>
+ /// <returns>The association to use; <c>null</c> to use 'dumb mode'.</returns>
+ private Association GetAssociation() {
+ Association association = null;
+ switch (this.associationPreference) {
+ case AssociationPreference.IfPossible:
+ association = this.RelyingParty.AssociationManager.GetOrCreateAssociation(this.DiscoveryResult);
+ if (association == null) {
+ // Avoid trying to create the association again if the redirecting response
+ // is generated again.
+ this.associationPreference = AssociationPreference.IfAlreadyEstablished;
+ }
+ break;
+ case AssociationPreference.IfAlreadyEstablished:
+ association = this.RelyingParty.AssociationManager.GetExistingAssociation(this.DiscoveryResult);
+ break;
+ case AssociationPreference.Never:
+ break;
+ default:
+ throw new InternalErrorException();
+ }
+
+ return association;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Behaviors/AXFetchAsSregTransform.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Behaviors/AXFetchAsSregTransform.cs
new file mode 100644
index 0000000..b2794ec
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Behaviors/AXFetchAsSregTransform.cs
@@ -0,0 +1,76 @@
+//-----------------------------------------------------------------------
+// <copyright file="AXFetchAsSregTransform.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.RelyingParty.Behaviors {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Behaviors;
+ using DotNetOpenAuth.OpenId.Extensions;
+ using DotNetOpenAuth.OpenId.Extensions.AttributeExchange;
+ using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+ using DotNetOpenAuth.OpenId.RelyingParty.Extensions;
+
+ /// <summary>
+ /// An Attribute Exchange and Simple Registration filter to make all incoming attribute
+ /// requests look like Simple Registration requests, and to convert the response
+ /// to the originally requested extension and format.
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sreg", Justification = "Abbreviation")]
+ public sealed class AXFetchAsSregTransform : AXFetchAsSregTransformBase, IRelyingPartyBehavior {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AXFetchAsSregTransform"/> class.
+ /// </summary>
+ public AXFetchAsSregTransform() {
+ }
+
+ #region IRelyingPartyBehavior Members
+
+ /// <summary>
+ /// Applies a well known set of security requirements to a default set of security settings.
+ /// </summary>
+ /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param>
+ /// <remarks>
+ /// Care should be taken to never decrease security when applying a profile.
+ /// Profiles should only enhance security requirements to avoid being
+ /// incompatible with each other.
+ /// </remarks>
+ void IRelyingPartyBehavior.ApplySecuritySettings(RelyingPartySecuritySettings securitySettings) {
+ }
+
+ /// <summary>
+ /// Called when an authentication request is about to be sent.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ /// <remarks>
+ /// Implementations should be prepared to be called multiple times on the same outgoing message
+ /// without malfunctioning.
+ /// </remarks>
+ void IRelyingPartyBehavior.OnOutgoingAuthenticationRequest(RelyingParty.IAuthenticationRequest request) {
+ // Don't create AX extensions for OpenID 1.x messages, since AX requires OpenID 2.0.
+ if (request.Provider.Version.Major >= 2) {
+ request.SpreadSregToAX(this.AXFormats);
+ }
+ }
+
+ /// <summary>
+ /// Called when an incoming positive assertion is received.
+ /// </summary>
+ /// <param name="assertion">The positive assertion.</param>
+ void IRelyingPartyBehavior.OnIncomingPositiveAssertion(IAuthenticationResponse assertion) {
+ if (assertion.GetExtension<ClaimsResponse>() == null) {
+ ClaimsResponse sreg = assertion.UnifyExtensionsAsSreg(true);
+ ((PositiveAnonymousResponse)assertion).Response.Extensions.Add(sreg);
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Behaviors/GsaIcamProfile.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Behaviors/GsaIcamProfile.cs
new file mode 100644
index 0000000..5a1ddaa
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Behaviors/GsaIcamProfile.cs
@@ -0,0 +1,124 @@
+//-----------------------------------------------------------------------
+// <copyright file="GsaIcamProfile.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.RelyingParty.Behaviors {
+ using System;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using DotNetOpenAuth.Configuration;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Behaviors;
+ using DotNetOpenAuth.OpenId.Extensions.AttributeExchange;
+ using DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy;
+ using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+
+ /// <summary>
+ /// Implements the Identity, Credential, &amp; Access Management (ICAM) OpenID 2.0 Profile
+ /// for the General Services Administration (GSA).
+ /// </summary>
+ /// <remarks>
+ /// <para>Relying parties that include this profile are always held to the terms required by the profile,
+ /// but Providers are only affected by the special behaviors of the profile when the RP specifically
+ /// indicates that they want to use this profile. </para>
+ /// </remarks>
+ [Serializable]
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Icam", Justification = "Acronym")]
+ public sealed class GsaIcamProfile : GsaIcamProfileBase, IRelyingPartyBehavior {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="GsaIcamProfile"/> class.
+ /// </summary>
+ public GsaIcamProfile() {
+ if (DisableSslRequirement) {
+ Logger.OpenId.Warn("GSA level 1 behavior has its RequireSsl requirement disabled.");
+ }
+ }
+
+ #region IRelyingPartyBehavior Members
+
+ /// <summary>
+ /// Applies a well known set of security requirements.
+ /// </summary>
+ /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param>
+ /// <remarks>
+ /// Care should be taken to never decrease security when applying a profile.
+ /// Profiles should only enhance security requirements to avoid being
+ /// incompatible with each other.
+ /// </remarks>
+ void IRelyingPartyBehavior.ApplySecuritySettings(RelyingPartySecuritySettings securitySettings) {
+ if (securitySettings.MaximumHashBitLength < 256) {
+ securitySettings.MaximumHashBitLength = 256;
+ }
+
+ securitySettings.RequireSsl = !DisableSslRequirement;
+ securitySettings.RequireDirectedIdentity = true;
+ securitySettings.RequireAssociation = true;
+ securitySettings.RejectDelegatingIdentifiers = true;
+ securitySettings.IgnoreUnsignedExtensions = true;
+ securitySettings.MinimumRequiredOpenIdVersion = ProtocolVersion.V20;
+ }
+
+ /// <summary>
+ /// Called when an authentication request is about to be sent.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ void IRelyingPartyBehavior.OnOutgoingAuthenticationRequest(RelyingParty.IAuthenticationRequest request) {
+ RelyingParty.AuthenticationRequest requestInternal = (RelyingParty.AuthenticationRequest)request;
+ ErrorUtilities.VerifyProtocol(string.Equals(request.Realm.Scheme, Uri.UriSchemeHttps, StringComparison.Ordinal) || DisableSslRequirement, BehaviorStrings.RealmMustBeHttps);
+
+ var pape = requestInternal.AppliedExtensions.OfType<PolicyRequest>().SingleOrDefault();
+ if (pape == null) {
+ request.AddExtension(pape = new PolicyRequest());
+ }
+
+ if (!pape.PreferredPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier)) {
+ pape.PreferredPolicies.Add(AuthenticationPolicies.PrivatePersonalIdentifier);
+ }
+
+ if (!pape.PreferredPolicies.Contains(AuthenticationPolicies.USGovernmentTrustLevel1)) {
+ pape.PreferredPolicies.Add(AuthenticationPolicies.USGovernmentTrustLevel1);
+ }
+
+ if (!AllowPersonallyIdentifiableInformation && !pape.PreferredPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation)) {
+ pape.PreferredPolicies.Add(AuthenticationPolicies.NoPersonallyIdentifiableInformation);
+ }
+
+ if (pape.PreferredPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation)) {
+ ErrorUtilities.VerifyProtocol(
+ (!requestInternal.AppliedExtensions.OfType<ClaimsRequest>().Any() &&
+ !requestInternal.AppliedExtensions.OfType<FetchRequest>().Any()),
+ BehaviorStrings.PiiIncludedWithNoPiiPolicy);
+ }
+
+ Reporting.RecordEventOccurrence(this, "RP");
+ }
+
+ /// <summary>
+ /// Called when an incoming positive assertion is received.
+ /// </summary>
+ /// <param name="assertion">The positive assertion.</param>
+ void IRelyingPartyBehavior.OnIncomingPositiveAssertion(IAuthenticationResponse assertion) {
+ PolicyResponse pape = assertion.GetExtension<PolicyResponse>();
+ ErrorUtilities.VerifyProtocol(
+ pape != null &&
+ pape.ActualPolicies.Contains(AuthenticationPolicies.USGovernmentTrustLevel1) &&
+ pape.ActualPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier),
+ BehaviorStrings.PapeResponseOrRequiredPoliciesMissing);
+
+ ErrorUtilities.VerifyProtocol(AllowPersonallyIdentifiableInformation || pape.ActualPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation), BehaviorStrings.PapeResponseOrRequiredPoliciesMissing);
+
+ if (pape.ActualPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation)) {
+ ErrorUtilities.VerifyProtocol(
+ assertion.GetExtension<ClaimsResponse>() == null &&
+ assertion.GetExtension<FetchResponse>() == null,
+ BehaviorStrings.PiiIncludedWithNoPiiPolicy);
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/CryptoKeyStoreAsRelyingPartyAssociationStore.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/CryptoKeyStoreAsRelyingPartyAssociationStore.cs
new file mode 100644
index 0000000..1614145
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/CryptoKeyStoreAsRelyingPartyAssociationStore.cs
@@ -0,0 +1,87 @@
+//-----------------------------------------------------------------------
+// <copyright file="CryptoKeyStoreAsRelyingPartyAssociationStore.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.RelyingParty {
+ using System;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using DotNetOpenAuth.Messaging.Bindings;
+
+ /// <summary>
+ /// Wraps a standard <see cref="ICryptoKeyStore"/> so that it behaves as an association store.
+ /// </summary>
+ internal class CryptoKeyStoreAsRelyingPartyAssociationStore : IRelyingPartyAssociationStore {
+ /// <summary>
+ /// The underlying key store.
+ /// </summary>
+ private readonly ICryptoKeyStore keyStore;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CryptoKeyStoreAsRelyingPartyAssociationStore"/> class.
+ /// </summary>
+ /// <param name="keyStore">The key store.</param>
+ internal CryptoKeyStoreAsRelyingPartyAssociationStore(ICryptoKeyStore keyStore) {
+ Requires.NotNull(keyStore, "keyStore");
+ Contract.Ensures(this.keyStore == keyStore);
+ this.keyStore = keyStore;
+ }
+
+ /// <summary>
+ /// Saves an <see cref="Association"/> for later recall.
+ /// </summary>
+ /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param>
+ /// <param name="association">The association to store.</param>
+ public void StoreAssociation(Uri providerEndpoint, Association association) {
+ var cryptoKey = new CryptoKey(association.SerializePrivateData(), association.Expires);
+ this.keyStore.StoreKey(providerEndpoint.AbsoluteUri, association.Handle, cryptoKey);
+ }
+
+ /// <summary>
+ /// Gets the best association (the one with the longest remaining life) for a given key.
+ /// </summary>
+ /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param>
+ /// <param name="securityRequirements">The security requirements that the returned association must meet.</param>
+ /// <returns>
+ /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key.
+ /// </returns>
+ public Association GetAssociation(Uri providerEndpoint, SecuritySettings securityRequirements) {
+ var matches = from cryptoKey in this.keyStore.GetKeys(providerEndpoint.AbsoluteUri)
+ where cryptoKey.Value.ExpiresUtc > DateTime.UtcNow
+ orderby cryptoKey.Value.ExpiresUtc descending
+ let assoc = Association.Deserialize(cryptoKey.Key, cryptoKey.Value.ExpiresUtc, cryptoKey.Value.Key)
+ where assoc.HashBitLength >= securityRequirements.MinimumHashBitLength
+ where assoc.HashBitLength <= securityRequirements.MaximumHashBitLength
+ select assoc;
+ return matches.FirstOrDefault();
+ }
+
+ /// <summary>
+ /// Gets the association for a given key and handle.
+ /// </summary>
+ /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param>
+ /// <param name="handle">The handle of the specific association that must be recalled.</param>
+ /// <returns>
+ /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key and handle.
+ /// </returns>
+ public Association GetAssociation(Uri providerEndpoint, string handle) {
+ var cryptoKey = this.keyStore.GetKey(providerEndpoint.AbsoluteUri, handle);
+ return cryptoKey != null ? Association.Deserialize(handle, cryptoKey.ExpiresUtc, cryptoKey.Key) : null;
+ }
+
+ /// <summary>
+ /// Removes a specified handle that may exist in the store.
+ /// </summary>
+ /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param>
+ /// <param name="handle">The handle of the specific association that must be deleted.</param>
+ /// <returns>
+ /// True if the association existed in this store previous to this call.
+ /// </returns>
+ public bool RemoveAssociation(Uri providerEndpoint, string handle) {
+ this.keyStore.RemoveKey(providerEndpoint.AbsoluteUri, handle);
+ return true; // return value isn't used by DNOA.
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/DuplicateRequestedHostsComparer.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/DuplicateRequestedHostsComparer.cs
new file mode 100644
index 0000000..94eb5ba
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/DuplicateRequestedHostsComparer.cs
@@ -0,0 +1,74 @@
+//-----------------------------------------------------------------------
+// <copyright file="DuplicateRequestedHostsComparer.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.RelyingParty {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+
+ /// <summary>
+ /// An authentication request comparer that judges equality solely on the OP endpoint hostname.
+ /// </summary>
+ internal class DuplicateRequestedHostsComparer : IEqualityComparer<IAuthenticationRequest> {
+ /// <summary>
+ /// The singleton instance of this comparer.
+ /// </summary>
+ private static IEqualityComparer<IAuthenticationRequest> instance = new DuplicateRequestedHostsComparer();
+
+ /// <summary>
+ /// Prevents a default instance of the <see cref="DuplicateRequestedHostsComparer"/> class from being created.
+ /// </summary>
+ private DuplicateRequestedHostsComparer() {
+ }
+
+ /// <summary>
+ /// Gets the singleton instance of this comparer.
+ /// </summary>
+ internal static IEqualityComparer<IAuthenticationRequest> Instance {
+ get { return instance; }
+ }
+
+ #region IEqualityComparer<IAuthenticationRequest> Members
+
+ /// <summary>
+ /// Determines whether the specified objects are equal.
+ /// </summary>
+ /// <param name="x">The first object to compare.</param>
+ /// <param name="y">The second object to compare.</param>
+ /// <returns>
+ /// true if the specified objects are equal; otherwise, false.
+ /// </returns>
+ public bool Equals(IAuthenticationRequest x, IAuthenticationRequest y) {
+ if (x == null && y == null) {
+ return true;
+ }
+
+ if (x == null || y == null) {
+ return false;
+ }
+
+ // We'll distinguish based on the host name only, which
+ // admittedly is only a heuristic, but if we remove one that really wasn't a duplicate, well,
+ // this multiple OP attempt thing was just a convenience feature anyway.
+ return string.Equals(x.Provider.Uri.Host, y.Provider.Uri.Host, StringComparison.OrdinalIgnoreCase);
+ }
+
+ /// <summary>
+ /// Returns a hash code for the specified object.
+ /// </summary>
+ /// <param name="obj">The <see cref="T:System.Object"/> for which a hash code is to be returned.</param>
+ /// <returns>A hash code for the specified object.</returns>
+ /// <exception cref="T:System.ArgumentNullException">
+ /// The type of <paramref name="obj"/> is a reference type and <paramref name="obj"/> is null.
+ /// </exception>
+ public int GetHashCode(IAuthenticationRequest obj) {
+ return obj.Provider.Uri.Host.GetHashCode();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Extensions/ExtensionsInteropHelper.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Extensions/ExtensionsInteropHelper.cs
new file mode 100644
index 0000000..0f7778f
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Extensions/ExtensionsInteropHelper.cs
@@ -0,0 +1,152 @@
+//-----------------------------------------------------------------------
+// <copyright file="ExtensionsInteropHelper.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.RelyingParty.Extensions {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Extensions;
+ using DotNetOpenAuth.OpenId.Extensions.AttributeExchange;
+ using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// A set of methods designed to assist in improving interop across different
+ /// OpenID implementations and their extensions.
+ /// </summary>
+ public static class ExtensionsInteropHelper {
+ /// <summary>
+ /// Adds an Attribute Exchange (AX) extension to the authentication request
+ /// that asks for the same attributes as the Simple Registration (sreg) extension
+ /// that is already applied.
+ /// </summary>
+ /// <param name="request">The authentication request.</param>
+ /// <param name="attributeFormats">The attribute formats to use in the AX request.</param>
+ /// <remarks>
+ /// <para>If discovery on the user-supplied identifier yields hints regarding which
+ /// extensions and attribute formats the Provider supports, this method MAY ignore the
+ /// <paramref name="attributeFormats"/> argument and accomodate the Provider to minimize
+ /// the size of the request.</para>
+ /// <para>If the request does not carry an sreg extension, the method logs a warning but
+ /// otherwise quietly returns doing nothing.</para>
+ /// </remarks>
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sreg", Justification = "Abbreviation")]
+ public static void SpreadSregToAX(this RelyingParty.IAuthenticationRequest request, AXAttributeFormats attributeFormats) {
+ Requires.NotNull(request, "request");
+
+ var req = (RelyingParty.AuthenticationRequest)request;
+ var sreg = req.AppliedExtensions.OfType<ClaimsRequest>().SingleOrDefault();
+ if (sreg == null) {
+ Logger.OpenId.Debug("No Simple Registration (ClaimsRequest) extension present in the request to spread to AX.");
+ return;
+ }
+
+ if (req.DiscoveryResult.IsExtensionSupported<ClaimsRequest>()) {
+ Logger.OpenId.Debug("Skipping generation of AX request because the Identifier advertises the Provider supports the Sreg extension.");
+ return;
+ }
+
+ var ax = req.AppliedExtensions.OfType<FetchRequest>().SingleOrDefault();
+ if (ax == null) {
+ ax = new FetchRequest();
+ req.AddExtension(ax);
+ }
+
+ // Try to use just one AX Type URI format if we can figure out which type the OP accepts.
+ AXAttributeFormats detectedFormat;
+ if (TryDetectOPAttributeFormat(request, out detectedFormat)) {
+ Logger.OpenId.Debug("Detected OP support for AX but not for Sreg. Removing Sreg extension request and using AX instead.");
+ attributeFormats = detectedFormat;
+ req.Extensions.Remove(sreg);
+ } else {
+ Logger.OpenId.Debug("Could not determine whether OP supported Sreg or AX. Using both extensions.");
+ }
+
+ foreach (AXAttributeFormats format in OpenIdExtensionsInteropHelper.ForEachFormat(attributeFormats)) {
+ OpenIdExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.BirthDate.WholeBirthDate, sreg.BirthDate);
+ OpenIdExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Contact.HomeAddress.Country, sreg.Country);
+ OpenIdExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Contact.Email, sreg.Email);
+ OpenIdExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Name.FullName, sreg.FullName);
+ OpenIdExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Person.Gender, sreg.Gender);
+ OpenIdExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Preferences.Language, sreg.Language);
+ OpenIdExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Name.Alias, sreg.Nickname);
+ OpenIdExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Contact.HomeAddress.PostalCode, sreg.PostalCode);
+ OpenIdExtensionsInteropHelper.FetchAttribute(ax, format, WellKnownAttributes.Preferences.TimeZone, sreg.TimeZone);
+ }
+ }
+
+ /// <summary>
+ /// Looks for Simple Registration and Attribute Exchange (all known formats)
+ /// response extensions and returns them as a Simple Registration extension.
+ /// </summary>
+ /// <param name="response">The authentication response.</param>
+ /// <param name="allowUnsigned">if set to <c>true</c> unsigned extensions will be included in the search.</param>
+ /// <returns>
+ /// The Simple Registration response if found,
+ /// or a fabricated one based on the Attribute Exchange extension if found,
+ /// or just an empty <see cref="ClaimsResponse"/> if there was no data.
+ /// Never <c>null</c>.</returns>
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sreg", Justification = "Abbreviation")]
+ public static ClaimsResponse UnifyExtensionsAsSreg(this RelyingParty.IAuthenticationResponse response, bool allowUnsigned) {
+ Requires.NotNull(response, "response");
+
+ var resp = (RelyingParty.IAuthenticationResponse)response;
+ var sreg = allowUnsigned ? resp.GetUntrustedExtension<ClaimsResponse>() : resp.GetExtension<ClaimsResponse>();
+ if (sreg != null) {
+ return sreg;
+ }
+
+ AXAttributeFormats formats = AXAttributeFormats.All;
+ sreg = new ClaimsResponse();
+ var fetchResponse = allowUnsigned ? resp.GetUntrustedExtension<FetchResponse>() : resp.GetExtension<FetchResponse>();
+ if (fetchResponse != null) {
+ ((IOpenIdMessageExtension)sreg).IsSignedByRemoteParty = fetchResponse.IsSignedByProvider;
+ sreg.BirthDateRaw = fetchResponse.GetAttributeValue(WellKnownAttributes.BirthDate.WholeBirthDate, formats);
+ sreg.Country = fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.HomeAddress.Country, formats);
+ sreg.PostalCode = fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.HomeAddress.PostalCode, formats);
+ sreg.Email = fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.Email, formats);
+ sreg.FullName = fetchResponse.GetAttributeValue(WellKnownAttributes.Name.FullName, formats);
+ sreg.Language = fetchResponse.GetAttributeValue(WellKnownAttributes.Preferences.Language, formats);
+ sreg.Nickname = fetchResponse.GetAttributeValue(WellKnownAttributes.Name.Alias, formats);
+ sreg.TimeZone = fetchResponse.GetAttributeValue(WellKnownAttributes.Preferences.TimeZone, formats);
+ string gender = fetchResponse.GetAttributeValue(WellKnownAttributes.Person.Gender, formats);
+ if (gender != null) {
+ sreg.Gender = (Gender)OpenIdExtensionsInteropHelper.GenderEncoder.Decode(gender);
+ }
+ }
+
+ return sreg;
+ }
+
+ /// <summary>
+ /// Gets the attribute value if available.
+ /// </summary>
+ /// <param name="fetchResponse">The AX fetch response extension to look for the attribute value.</param>
+ /// <param name="typeUri">The type URI of the attribute, using the axschema.org format of <see cref="WellKnownAttributes"/>.</param>
+ /// <param name="formats">The AX type URI formats to search.</param>
+ /// <returns>
+ /// The first value of the attribute, if available.
+ /// </returns>
+ internal static string GetAttributeValue(this FetchResponse fetchResponse, string typeUri, AXAttributeFormats formats) {
+ return OpenIdExtensionsInteropHelper.ForEachFormat(formats).Select(format => fetchResponse.GetAttributeValue(OpenIdExtensionsInteropHelper.TransformAXFormat(typeUri, format))).FirstOrDefault(s => s != null);
+ }
+
+ /// <summary>
+ /// Tries to find the exact format of AX attribute Type URI supported by the Provider.
+ /// </summary>
+ /// <param name="request">The authentication request.</param>
+ /// <param name="attributeFormat">The attribute formats the RP will try if this discovery fails.</param>
+ /// <returns>The AX format(s) to use based on the Provider's advertised AX support.</returns>
+ private static bool TryDetectOPAttributeFormat(RelyingParty.IAuthenticationRequest request, out AXAttributeFormats attributeFormat) {
+ Requires.NotNull(request, "request");
+ attributeFormat = OpenIdExtensionsInteropHelper.DetectAXFormat(request.DiscoveryResult.Capabilities);
+ return attributeFormat != AXAttributeFormats.None;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Extensions/UIUtilities.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Extensions/UIUtilities.cs
new file mode 100644
index 0000000..4606bf7
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/Extensions/UIUtilities.cs
@@ -0,0 +1,42 @@
+//-----------------------------------------------------------------------
+// <copyright file="UIUtilities.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.RelyingParty.Extensions.UI {
+ using System;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+
+ /// <summary>
+ /// Constants used in implementing support for the UI extension.
+ /// </summary>
+ internal static class UIUtilities {
+ /// <summary>
+ /// Gets the <c>window.open</c> javascript snippet to use to open a popup window
+ /// compliant with the UI extension.
+ /// </summary>
+ /// <param name="relyingParty">The relying party.</param>
+ /// <param name="request">The authentication request to place in the window.</param>
+ /// <param name="windowName">The name to assign to the popup window.</param>
+ /// <returns>A string starting with 'window.open' and forming just that one method call.</returns>
+ internal static string GetWindowPopupScript(OpenIdRelyingParty relyingParty, IAuthenticationRequest request, string windowName) {
+ Requires.NotNull(relyingParty, "relyingParty");
+ Requires.NotNull(request, "request");
+ Requires.NotNullOrEmpty(windowName, "windowName");
+
+ Uri popupUrl = request.RedirectingResponse.GetDirectUriRequest(relyingParty.Channel);
+
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ "(window.showModalDialog ? window.showModalDialog({0}, {1}, 'status:0;resizable:1;scroll:1;center:1;dialogWidth:{2}px; dialogHeight:{3}') : window.open({0}, {1}, 'status=0,toolbar=0,location=1,resizable=1,scrollbars=1,left=' + ((screen.width - {2}) / 2) + ',top=' + ((screen.height - {3}) / 2) + ',width={2},height={3}'));",
+ MessagingUtilities.GetSafeJavascriptValue(popupUrl.AbsoluteUri),
+ MessagingUtilities.GetSafeJavascriptValue(windowName),
+ OpenId.Extensions.UI.UIUtilities.PopupWidth,
+ OpenId.Extensions.UI.UIUtilities.PopupHeight);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/FailedAuthenticationResponse.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/FailedAuthenticationResponse.cs
new file mode 100644
index 0000000..b9266d3
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/FailedAuthenticationResponse.cs
@@ -0,0 +1,299 @@
+//-----------------------------------------------------------------------
+// <copyright file="FailedAuthenticationResponse.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;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.Text;
+ using System.Web;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId;
+ using DotNetOpenAuth.OpenId.Messages;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+
+ /// <summary>
+ /// Wraps a failed authentication response in an <see cref="IAuthenticationResponse"/> instance
+ /// for public consumption by the host web site.
+ /// </summary>
+ [DebuggerDisplay("{Exception.Message}")]
+ internal class FailedAuthenticationResponse : IAuthenticationResponse {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="FailedAuthenticationResponse"/> class.
+ /// </summary>
+ /// <param name="exception">The exception that resulted in the failed authentication.</param>
+ internal FailedAuthenticationResponse(Exception exception) {
+ Requires.NotNull(exception, "exception");
+
+ this.Exception = exception;
+
+ string category = string.Empty;
+ if (Reporting.Enabled) {
+ var pe = exception as ProtocolException;
+ if (pe != null) {
+ var responseMessage = pe.FaultedMessage as IndirectSignedResponse;
+ if (responseMessage != null && responseMessage.ProviderEndpoint != null) { // check "required" parts because this is a failure after all
+ category = responseMessage.ProviderEndpoint.AbsoluteUri;
+ }
+ }
+
+ Reporting.RecordEventOccurrence(this, category);
+ }
+ }
+
+ #region IAuthenticationResponse Members
+
+ /// <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 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 string FriendlyIdentifierForDisplay {
+ get { return null; }
+ }
+
+ /// <summary>
+ /// Gets the detailed success or failure status of the authentication attempt.
+ /// </summary>
+ /// <value></value>
+ public AuthenticationStatus Status {
+ get { return AuthenticationStatus.Failed; }
+ }
+
+ /// <summary>
+ /// Gets information about the OpenId Provider, as advertised by the
+ /// OpenID discovery documents found at the <see cref="ClaimedIdentifier"/>
+ /// location.
+ /// </summary>
+ /// <value>
+ /// The Provider endpoint that issued the positive assertion;
+ /// or <c>null</c> if information about the Provider is unavailable.
+ /// </value>
+ public IProviderEndpoint Provider {
+ get { return null; }
+ }
+
+ /// <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>
+ public Exception Exception { get; private set; }
+
+ /// <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>
+ /// <para>This MAY return any argument on the querystring that came with the authentication response,
+ /// which may include parameters not explicitly added using
+ /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.</para>
+ /// <para>Note that these values are NOT protected against tampering in transit.</para>
+ /// </remarks>
+ public IDictionary<string, string> GetCallbackArguments() {
+ return EmptyDictionary<string, string>.Instance;
+ }
+
+ /// <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 even if the RP is in stateless mode,
+ /// or the callback parameters are otherwise unverifiable as untampered with.
+ /// Therefore, use this method only when the callback argument is not to be
+ /// used to make a security-sensitive decision.
+ /// </remarks>
+ public IDictionary<string, string> GetUntrustedCallbackArguments() {
+ return EmptyDictionary<string, string>.Instance;
+ }
+
+ /// <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>
+ /// <para>This may return any argument on the querystring that came with the authentication response,
+ /// which may include parameters not explicitly added using
+ /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.</para>
+ /// <para>Note that these values are NOT protected against tampering in transit.</para>
+ /// </remarks>
+ public string GetCallbackArgument(string key) {
+ return null;
+ }
+
+ /// <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 even if the RP is in stateless mode,
+ /// or the callback parameters are otherwise unverifiable as untampered with.
+ /// Therefore, use this method only when the callback argument is not to be
+ /// used to make a security-sensitive decision.
+ /// </remarks>
+ public string GetUntrustedCallbackArgument(string key) {
+ return null;
+ }
+
+ /// <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&lt;T&gt;"/> 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 default(T);
+ }
+
+ /// <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) {
+ return null;
+ }
+
+ /// <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&lt;T&gt;"/> 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 default(T);
+ }
+
+ /// <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) {
+ return null;
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/IRelyingPartyAssociationStore.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/IRelyingPartyAssociationStore.cs
new file mode 100644
index 0000000..52e828c
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/IRelyingPartyAssociationStore.cs
@@ -0,0 +1,153 @@
+//-----------------------------------------------------------------------
+// <copyright file="IRelyingPartyAssociationStore.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.RelyingParty {
+ using System;
+ using System.Diagnostics.Contracts;
+
+ /// <summary>
+ /// Stores <see cref="Association"/>s for lookup by their handle, keeping
+ /// associations separated by a given OP Endpoint.
+ /// </summary>
+ /// <remarks>
+ /// Expired associations should be periodically cleared out of an association store.
+ /// This should be done frequently enough to avoid a memory leak, but sparingly enough
+ /// to not be a performance drain. Because this balance can vary by host, it is the
+ /// responsibility of the host to initiate this cleaning.
+ /// </remarks>
+ [ContractClass(typeof(IRelyingPartyAssociationStoreContract))]
+ public interface IRelyingPartyAssociationStore {
+ /// <summary>
+ /// Saves an <see cref="Association"/> for later recall.
+ /// </summary>
+ /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param>
+ /// <param name="association">The association to store.</param>
+ /// <remarks>
+ /// If the new association conflicts (in OP endpoint and association handle) with an existing association,
+ /// (which should never happen by the way) implementations may overwrite the previously saved association.
+ /// </remarks>
+ void StoreAssociation(Uri providerEndpoint, Association association);
+
+ /// <summary>
+ /// Gets the best association (the one with the longest remaining life) for a given key.
+ /// </summary>
+ /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param>
+ /// <param name="securityRequirements">The security requirements that the returned association must meet.</param>
+ /// <returns>
+ /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key.
+ /// </returns>
+ /// <remarks>
+ /// In the event that multiple associations exist for the given
+ /// <paramref name="providerEndpoint"/>, it is important for the
+ /// implementation for this method to use the <paramref name="securityRequirements"/>
+ /// to pick the best (highest grade or longest living as the host's policy may dictate)
+ /// association that fits the security requirements.
+ /// Associations that are returned that do not meet the security requirements will be
+ /// ignored and a new association created.
+ /// </remarks>
+ Association GetAssociation(Uri providerEndpoint, SecuritySettings securityRequirements);
+
+ /// <summary>
+ /// Gets the association for a given key and handle.
+ /// </summary>
+ /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param>
+ /// <param name="handle">The handle of the specific association that must be recalled.</param>
+ /// <returns>The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key and handle.</returns>
+ Association GetAssociation(Uri providerEndpoint, string handle);
+
+ /// <summary>Removes a specified handle that may exist in the store.</summary>
+ /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param>
+ /// <param name="handle">The handle of the specific association that must be deleted.</param>
+ /// <returns>
+ /// Deprecated. The return value is insignificant.
+ /// Previously: True if the association existed in this store previous to this call.
+ /// </returns>
+ /// <remarks>
+ /// No exception should be thrown if the association does not exist in the store
+ /// before this call.
+ /// </remarks>
+ bool RemoveAssociation(Uri providerEndpoint, string handle);
+ }
+
+ /// <summary>
+ /// Code Contract for the <see cref="IRelyingPartyAssociationStore"/> class.
+ /// </summary>
+ [ContractClassFor(typeof(IRelyingPartyAssociationStore))]
+ internal abstract class IRelyingPartyAssociationStoreContract : IRelyingPartyAssociationStore {
+ #region IAssociationStore Members
+
+ /// <summary>
+ /// Saves an <see cref="Association"/> for later recall.
+ /// </summary>
+ /// <param name="providerEndpoint">The Uri (for relying parties) or Smart/Dumb (for providers).</param>
+ /// <param name="association">The association to store.</param>
+ /// <remarks>
+ /// TODO: what should implementations do on association handle conflict?
+ /// </remarks>
+ void IRelyingPartyAssociationStore.StoreAssociation(Uri providerEndpoint, Association association) {
+ Requires.NotNull(providerEndpoint, "providerEndpoint");
+ Requires.NotNull(association, "association");
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Gets the best association (the one with the longest remaining life) for a given key.
+ /// </summary>
+ /// <param name="providerEndpoint">The Uri (for relying parties) or Smart/Dumb (for Providers).</param>
+ /// <param name="securityRequirements">The security requirements that the returned association must meet.</param>
+ /// <returns>
+ /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key.
+ /// </returns>
+ /// <remarks>
+ /// In the event that multiple associations exist for the given
+ /// <paramref name="providerEndpoint"/>, it is important for the
+ /// implementation for this method to use the <paramref name="securityRequirements"/>
+ /// to pick the best (highest grade or longest living as the host's policy may dictate)
+ /// association that fits the security requirements.
+ /// Associations that are returned that do not meet the security requirements will be
+ /// ignored and a new association created.
+ /// </remarks>
+ Association IRelyingPartyAssociationStore.GetAssociation(Uri providerEndpoint, SecuritySettings securityRequirements) {
+ Requires.NotNull(providerEndpoint, "providerEndpoint");
+ Requires.NotNull(securityRequirements, "securityRequirements");
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Gets the association for a given key and handle.
+ /// </summary>
+ /// <param name="providerEndpoint">The Uri (for relying parties) or Smart/Dumb (for Providers).</param>
+ /// <param name="handle">The handle of the specific association that must be recalled.</param>
+ /// <returns>
+ /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key and handle.
+ /// </returns>
+ Association IRelyingPartyAssociationStore.GetAssociation(Uri providerEndpoint, string handle) {
+ Requires.NotNull(providerEndpoint, "providerEndpoint");
+ Contract.Requires(!String.IsNullOrEmpty(handle));
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Removes a specified handle that may exist in the store.
+ /// </summary>
+ /// <param name="providerEndpoint">The Uri (for relying parties) or Smart/Dumb (for Providers).</param>
+ /// <param name="handle">The handle of the specific association that must be deleted.</param>
+ /// <returns>
+ /// True if the association existed in this store previous to this call.
+ /// </returns>
+ /// <remarks>
+ /// No exception should be thrown if the association does not exist in the store
+ /// before this call.
+ /// </remarks>
+ bool IRelyingPartyAssociationStore.RemoveAssociation(Uri providerEndpoint, string handle) {
+ Requires.NotNull(providerEndpoint, "providerEndpoint");
+ Contract.Requires(!String.IsNullOrEmpty(handle));
+ throw new NotImplementedException();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/ISetupRequiredAuthenticationResponse.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/ISetupRequiredAuthenticationResponse.cs
new file mode 100644
index 0000000..2942c9d
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/ISetupRequiredAuthenticationResponse.cs
@@ -0,0 +1,51 @@
+//-----------------------------------------------------------------------
+// <copyright file="ISetupRequiredAuthenticationResponse.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.RelyingParty {
+ using System;
+ using System.Diagnostics.Contracts;
+
+ /// <summary>
+ /// An interface to expose useful properties and functionality for handling
+ /// authentication responses that are returned from Immediate authentication
+ /// requests that require a subsequent request to be made in non-immediate mode.
+ /// </summary>
+ [ContractClass(typeof(ISetupRequiredAuthenticationResponseContract))]
+ public interface ISetupRequiredAuthenticationResponse {
+ /// <summary>
+ /// Gets the <see cref="Identifier"/> to pass to <see cref="OpenIdRelyingParty.CreateRequest(Identifier)"/>
+ /// in a subsequent authentication attempt.
+ /// </summary>
+ Identifier UserSuppliedIdentifier { get; }
+ }
+
+ /// <summary>
+ /// Code contract class for the <see cref="ISetupRequiredAuthenticationResponse"/> type.
+ /// </summary>
+ [ContractClassFor(typeof(ISetupRequiredAuthenticationResponse))]
+ internal abstract class ISetupRequiredAuthenticationResponseContract : ISetupRequiredAuthenticationResponse {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ISetupRequiredAuthenticationResponseContract"/> class.
+ /// </summary>
+ protected ISetupRequiredAuthenticationResponseContract() {
+ }
+
+ #region ISetupRequiredAuthenticationResponse Members
+
+ /// <summary>
+ /// Gets the <see cref="Identifier"/> to pass to <see cref="OpenIdRelyingParty.CreateRequest(Identifier)"/>
+ /// in a subsequent authentication attempt.
+ /// </summary>
+ Identifier ISetupRequiredAuthenticationResponse.UserSuppliedIdentifier {
+ get {
+ Requires.ValidState(((IAuthenticationResponse)this).Status == AuthenticationStatus.SetupRequired, OpenIdStrings.OperationOnlyValidForSetupRequiredState);
+ throw new System.NotImplementedException();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/NegativeAuthenticationResponse.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/NegativeAuthenticationResponse.cs
new file mode 100644
index 0000000..4b21fea
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/NegativeAuthenticationResponse.cs
@@ -0,0 +1,313 @@
+//-----------------------------------------------------------------------
+// <copyright file="NegativeAuthenticationResponse.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 a negative assertion response in an <see cref="IAuthenticationResponse"/> instance
+ /// for public consumption by the host web site.
+ /// </summary>
+ internal class NegativeAuthenticationResponse : IAuthenticationResponse, ISetupRequiredAuthenticationResponse {
+ /// <summary>
+ /// The negative assertion message that was received by the RP that was used
+ /// to create this instance.
+ /// </summary>
+ private readonly NegativeAssertionResponse response;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="NegativeAuthenticationResponse"/> class.
+ /// </summary>
+ /// <param name="response">The negative assertion response received by the Relying Party.</param>
+ internal NegativeAuthenticationResponse(NegativeAssertionResponse response) {
+ Requires.NotNull(response, "response");
+ this.response = response;
+
+ Reporting.RecordEventOccurrence(this, string.Empty);
+ }
+
+ #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 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 string FriendlyIdentifierForDisplay {
+ get { return null; }
+ }
+
+ /// <summary>
+ /// Gets the detailed success or failure status of the authentication attempt.
+ /// </summary>
+ /// <value></value>
+ public AuthenticationStatus Status {
+ get { return this.response.Immediate ? AuthenticationStatus.SetupRequired : AuthenticationStatus.Canceled; }
+ }
+
+ /// <summary>
+ /// Gets information about the OpenId Provider, as advertised by the
+ /// OpenID discovery documents found at the <see cref="ClaimedIdentifier"/>
+ /// location.
+ /// </summary>
+ /// <value>
+ /// The Provider endpoint that issued the positive assertion;
+ /// or <c>null</c> if information about the Provider is unavailable.
+ /// </value>
+ public IProviderEndpoint Provider {
+ get { return null; }
+ }
+
+ /// <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
+
+ #region ISetupRequiredAuthenticationResponse Members
+
+ /// <summary>
+ /// Gets the <see cref="Identifier"/> to pass to <see cref="OpenIdRelyingParty.CreateRequest(Identifier)"/>
+ /// in a subsequent authentication attempt.
+ /// </summary>
+ /// <value></value>
+ public Identifier UserSuppliedIdentifier {
+ get {
+ ErrorUtilities.VerifyOperation(((IAuthenticationResponse)this).Status == AuthenticationStatus.SetupRequired, OpenIdStrings.OperationOnlyValidForSetupRequiredState);
+ string userSuppliedIdentifier;
+ this.response.ExtraData.TryGetValue(AuthenticationRequest.UserSuppliedIdentifierParameterName, out userSuppliedIdentifier);
+ return userSuppliedIdentifier;
+ }
+ }
+
+ #endregion
+
+ #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>
+ /// <para>This may return any argument on the querystring that came with the authentication response,
+ /// which may include parameters not explicitly added using
+ /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.</para>
+ /// <para>Note that these values are NOT protected against tampering in transit.</para>
+ /// </remarks>
+ public string GetCallbackArgument(string key) {
+ return null;
+ }
+
+ /// <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 even if the RP is in stateless mode,
+ /// or the callback parameters are otherwise unverifiable as untampered with.
+ /// Therefore, use this method only when the callback argument is not to be
+ /// used to make a security-sensitive decision.
+ /// </remarks>
+ public string GetUntrustedCallbackArgument(string key) {
+ 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>
+ /// <para>This MAY return any argument on the querystring that came with the authentication response,
+ /// which may include parameters not explicitly added using
+ /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.</para>
+ /// <para>Note that these values are NOT protected against tampering in transit.</para>
+ /// </remarks>
+ public IDictionary<string, string> GetCallbackArguments() {
+ return EmptyDictionary<string, string>.Instance;
+ }
+
+ /// <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 even if the RP is in stateless mode,
+ /// or the callback parameters are otherwise unverifiable as untampered with.
+ /// Therefore, use this method only when the callback argument is not to be
+ /// used to make a security-sensitive decision.
+ /// </remarks>
+ public IDictionary<string, string> GetUntrustedCallbackArguments() {
+ 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&lt;T&gt;"/> 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 default(T);
+ }
+
+ /// <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) {
+ return null;
+ }
+
+ /// <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&lt;T&gt;"/> 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) {
+ return this.response.Extensions.OfType<IOpenIdMessageExtension>().Where(ext => extensionType.IsInstanceOfType(ext)).FirstOrDefault();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/OpenIdRelyingParty.cd b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/OpenIdRelyingParty.cd
new file mode 100644
index 0000000..0519ecb
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/OpenIdRelyingParty.cd
@@ -0,0 +1 @@
+ \ No newline at end of file
diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/OpenIdRelyingParty.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/OpenIdRelyingParty.cs
new file mode 100644
index 0000000..509e319
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/OpenIdRelyingParty.cs
@@ -0,0 +1,903 @@
+//-----------------------------------------------------------------------
+// <copyright file="OpenIdRelyingParty.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.RelyingParty {
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.ObjectModel;
+ using System.Collections.Specialized;
+ using System.ComponentModel;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.Linq;
+ using System.Net;
+ using System.Net.Mime;
+ using System.Text;
+ using System.Web;
+ using DotNetOpenAuth.Configuration;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
+ using DotNetOpenAuth.OpenId.ChannelElements;
+ using DotNetOpenAuth.OpenId.Extensions;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// A delegate that decides whether a given OpenID Provider endpoint may be
+ /// considered for authenticating a user.
+ /// </summary>
+ /// <param name="endpoint">The endpoint for consideration.</param>
+ /// <returns>
+ /// <c>True</c> if the endpoint should be considered.
+ /// <c>False</c> to remove it from the pool of acceptable providers.
+ /// </returns>
+ public delegate bool EndpointSelector(IProviderEndpoint endpoint);
+
+ /// <summary>
+ /// Provides the programmatic facilities to act as an OpenID relying party.
+ /// </summary>
+ [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Unavoidable")]
+ [ContractVerification(true)]
+ public class OpenIdRelyingParty : IDisposable {
+ /// <summary>
+ /// The name of the key to use in the HttpApplication cache to store the
+ /// instance of <see cref="StandardRelyingPartyApplicationStore"/> to use.
+ /// </summary>
+ private const string ApplicationStoreKey = "DotNetOpenAuth.OpenId.RelyingParty.OpenIdRelyingParty.HttpApplicationStore";
+
+ /// <summary>
+ /// Backing store for the <see cref="Behaviors"/> property.
+ /// </summary>
+ private readonly ObservableCollection<IRelyingPartyBehavior> behaviors = new ObservableCollection<IRelyingPartyBehavior>();
+
+ /// <summary>
+ /// Backing field for the <see cref="DiscoveryServices"/> property.
+ /// </summary>
+ private readonly IList<IIdentifierDiscoveryService> discoveryServices = new List<IIdentifierDiscoveryService>(2);
+
+ /// <summary>
+ /// A type initializer that ensures that another type initializer runs in order to guarantee that
+ /// types are serializable.
+ /// </summary>
+ private static Identifier dummyIdentifierToInvokeStaticCtor = "http://localhost/";
+
+ /// <summary>
+ /// A type initializer that ensures that another type initializer runs in order to guarantee that
+ /// types are serializable.
+ /// </summary>
+ private static Realm dummyRealmToInvokeStaticCtor = "http://localhost/";
+
+ /// <summary>
+ /// Backing field for the <see cref="NonVerifyingRelyingParty"/> property.
+ /// </summary>
+ private OpenIdRelyingParty nonVerifyingRelyingParty;
+
+ /// <summary>
+ /// The lock to obtain when initializing the <see cref="nonVerifyingRelyingParty"/> member.
+ /// </summary>
+ private object nonVerifyingRelyingPartyInitLock = new object();
+
+ /// <summary>
+ /// A dictionary of extension response types and the javascript member
+ /// name to map them to on the user agent.
+ /// </summary>
+ private Dictionary<Type, string> clientScriptExtensions = new Dictionary<Type, string>();
+
+ /// <summary>
+ /// Backing field for the <see cref="SecuritySettings"/> property.
+ /// </summary>
+ private RelyingPartySecuritySettings securitySettings;
+
+ /// <summary>
+ /// Backing store for the <see cref="EndpointOrder"/> property.
+ /// </summary>
+ private Comparison<IdentifierDiscoveryResult> endpointOrder = DefaultEndpointOrder;
+
+ /// <summary>
+ /// Backing field for the <see cref="Channel"/> property.
+ /// </summary>
+ private Channel channel;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OpenIdRelyingParty"/> class.
+ /// </summary>
+ public OpenIdRelyingParty()
+ : this(OpenIdElement.Configuration.RelyingParty.ApplicationStore.CreateInstance(HttpApplicationStore)) {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OpenIdRelyingParty"/> class.
+ /// </summary>
+ /// <param name="applicationStore">The application store. If <c>null</c>, the relying party will always operate in "dumb mode".</param>
+ public OpenIdRelyingParty(IOpenIdApplicationStore applicationStore)
+ : this(applicationStore, applicationStore) {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OpenIdRelyingParty"/> class.
+ /// </summary>
+ /// <param name="cryptoKeyStore">The association store. If null, the relying party will always operate in "dumb mode".</param>
+ /// <param name="nonceStore">The nonce store to use. If null, the relying party will always operate in "dumb mode".</param>
+ [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Unavoidable")]
+ private OpenIdRelyingParty(ICryptoKeyStore cryptoKeyStore, INonceStore nonceStore) {
+ // If we are a smart-mode RP (supporting associations), then we MUST also be
+ // capable of storing nonces to prevent replay attacks.
+ // If we're a dumb-mode RP, then 2.0 OPs are responsible for preventing replays.
+ Requires.True(cryptoKeyStore == null || nonceStore != null, null, OpenIdStrings.AssociationStoreRequiresNonceStore);
+
+ this.securitySettings = OpenIdElement.Configuration.RelyingParty.SecuritySettings.CreateSecuritySettings();
+
+ foreach (var discoveryService in OpenIdElement.Configuration.RelyingParty.DiscoveryServices.CreateInstances(true)) {
+ this.discoveryServices.Add(discoveryService);
+ }
+
+ this.behaviors.CollectionChanged += this.OnBehaviorsChanged;
+ foreach (var behavior in OpenIdElement.Configuration.RelyingParty.Behaviors.CreateInstances(false)) {
+ this.behaviors.Add(behavior);
+ }
+
+ // Without a nonce store, we must rely on the Provider to protect against
+ // replay attacks. But only 2.0+ Providers can be expected to provide
+ // replay protection.
+ if (nonceStore == null &&
+ this.SecuritySettings.ProtectDownlevelReplayAttacks &&
+ this.SecuritySettings.MinimumRequiredOpenIdVersion < ProtocolVersion.V20) {
+ Logger.OpenId.Warn("Raising minimum OpenID version requirement for Providers to 2.0 to protect this stateless RP from replay attacks.");
+ this.SecuritySettings.MinimumRequiredOpenIdVersion = ProtocolVersion.V20;
+ }
+
+ if (cryptoKeyStore == null) {
+ cryptoKeyStore = new MemoryCryptoKeyStore();
+ }
+
+ this.channel = new OpenIdRelyingPartyChannel(cryptoKeyStore, nonceStore, this.SecuritySettings);
+ this.AssociationManager = new AssociationManager(this.Channel, new CryptoKeyStoreAsRelyingPartyAssociationStore(cryptoKeyStore), this.SecuritySettings);
+
+ Reporting.RecordFeatureAndDependencyUse(this, cryptoKeyStore, nonceStore);
+ }
+
+ /// <summary>
+ /// Gets an XRDS sorting routine that uses the XRDS Service/@Priority
+ /// attribute to determine order.
+ /// </summary>
+ /// <remarks>
+ /// Endpoints lacking any priority value are sorted to the end of the list.
+ /// </remarks>
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
+ public static Comparison<IdentifierDiscoveryResult> DefaultEndpointOrder {
+ get { return IdentifierDiscoveryResult.EndpointOrder; }
+ }
+
+ /// <summary>
+ /// Gets the standard state storage mechanism that uses ASP.NET's
+ /// HttpApplication state dictionary to store associations and nonces.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
+ public static IOpenIdApplicationStore HttpApplicationStore {
+ get {
+ Contract.Ensures(Contract.Result<IOpenIdApplicationStore>() != null);
+
+ HttpContext context = HttpContext.Current;
+ ErrorUtilities.VerifyOperation(context != null, Strings.StoreRequiredWhenNoHttpContextAvailable, typeof(IOpenIdApplicationStore).Name);
+ var store = (IOpenIdApplicationStore)context.Application[ApplicationStoreKey];
+ if (store == null) {
+ context.Application.Lock();
+ try {
+ if ((store = (IOpenIdApplicationStore)context.Application[ApplicationStoreKey]) == null) {
+ context.Application[ApplicationStoreKey] = store = new StandardRelyingPartyApplicationStore();
+ }
+ } finally {
+ context.Application.UnLock();
+ }
+ }
+
+ return store;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the channel to use for sending/receiving messages.
+ /// </summary>
+ public Channel Channel {
+ get {
+ return this.channel;
+ }
+
+ set {
+ Requires.NotNull(value, "value");
+ this.channel = value;
+ this.AssociationManager.Channel = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets the security settings used by this Relying Party.
+ /// </summary>
+ public RelyingPartySecuritySettings SecuritySettings {
+ get {
+ Contract.Ensures(Contract.Result<RelyingPartySecuritySettings>() != null);
+ return this.securitySettings;
+ }
+
+ internal set {
+ Requires.NotNull(value, "value");
+ this.securitySettings = value;
+ this.AssociationManager.SecuritySettings = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the optional Provider Endpoint filter to use.
+ /// </summary>
+ /// <remarks>
+ /// Provides a way to optionally filter the providers that may be used in authenticating a user.
+ /// If provided, the delegate should return true to accept an endpoint, and false to reject it.
+ /// If null, all identity providers will be accepted. This is the default.
+ /// </remarks>
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
+ public EndpointSelector EndpointFilter { get; set; }
+
+ /// <summary>
+ /// Gets or sets the ordering routine that will determine which XRDS
+ /// Service element to try first
+ /// </summary>
+ /// <value>Default is <see cref="DefaultEndpointOrder"/>.</value>
+ /// <remarks>
+ /// This may never be null. To reset to default behavior this property
+ /// can be set to the value of <see cref="DefaultEndpointOrder"/>.
+ /// </remarks>
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
+ public Comparison<IdentifierDiscoveryResult> EndpointOrder {
+ get {
+ return this.endpointOrder;
+ }
+
+ set {
+ Requires.NotNull(value, "value");
+ this.endpointOrder = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets the extension factories.
+ /// </summary>
+ public IList<IOpenIdExtensionFactory> ExtensionFactories {
+ get { return this.Channel.GetExtensionFactories(); }
+ }
+
+ /// <summary>
+ /// Gets a list of custom behaviors to apply to OpenID actions.
+ /// </summary>
+ /// <remarks>
+ /// Adding behaviors can impact the security settings of this <see cref="OpenIdRelyingParty"/>
+ /// instance in ways that subsequently removing the behaviors will not reverse.
+ /// </remarks>
+ public ICollection<IRelyingPartyBehavior> Behaviors {
+ get { return this.behaviors; }
+ }
+
+ /// <summary>
+ /// Gets the list of services that can perform discovery on identifiers given to this relying party.
+ /// </summary>
+ public IList<IIdentifierDiscoveryService> DiscoveryServices {
+ get { return this.discoveryServices; }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this Relying Party can sign its return_to
+ /// parameter in outgoing authentication requests.
+ /// </summary>
+ internal bool CanSignCallbackArguments {
+ get { return this.Channel.BindingElements.OfType<ReturnToSignatureBindingElement>().Any(); }
+ }
+
+ /// <summary>
+ /// Gets the web request handler to use for discovery and the part of
+ /// authentication where direct messages are sent to an untrusted remote party.
+ /// </summary>
+ internal IDirectWebRequestHandler WebRequestHandler {
+ get { return this.Channel.WebRequestHandler; }
+ }
+
+ /// <summary>
+ /// Gets the association manager.
+ /// </summary>
+ internal AssociationManager AssociationManager { get; private set; }
+
+ /// <summary>
+ /// Gets the <see cref="OpenIdRelyingParty"/> instance used to process authentication responses
+ /// without verifying the assertion or consuming nonces.
+ /// </summary>
+ protected OpenIdRelyingParty NonVerifyingRelyingParty {
+ get {
+ if (this.nonVerifyingRelyingParty == null) {
+ lock (this.nonVerifyingRelyingPartyInitLock) {
+ if (this.nonVerifyingRelyingParty == null) {
+ this.nonVerifyingRelyingParty = OpenIdRelyingParty.CreateNonVerifying();
+ }
+ }
+ }
+
+ return this.nonVerifyingRelyingParty;
+ }
+ }
+
+ /// <summary>
+ /// Creates an authentication request to verify that a user controls
+ /// some given Identifier.
+ /// </summary>
+ /// <param name="userSuppliedIdentifier">
+ /// The Identifier supplied by the user. This may be a URL, an XRI or i-name.
+ /// </param>
+ /// <param name="realm">
+ /// The shorest URL that describes this relying party web site's address.
+ /// For example, if your login page is found at https://www.example.com/login.aspx,
+ /// your realm would typically be https://www.example.com/.
+ /// </param>
+ /// <param name="returnToUrl">
+ /// The URL of the login page, or the page prepared to receive authentication
+ /// responses from the OpenID Provider.
+ /// </param>
+ /// <returns>
+ /// An authentication request object to customize the request and generate
+ /// an object to send to the user agent to initiate the authentication.
+ /// </returns>
+ /// <exception cref="ProtocolException">Thrown if no OpenID endpoint could be found.</exception>
+ public IAuthenticationRequest CreateRequest(Identifier userSuppliedIdentifier, Realm realm, Uri returnToUrl) {
+ Requires.NotNull(userSuppliedIdentifier, "userSuppliedIdentifier");
+ Requires.NotNull(realm, "realm");
+ Requires.NotNull(returnToUrl, "returnToUrl");
+ Contract.Ensures(Contract.Result<IAuthenticationRequest>() != null);
+ try {
+ return this.CreateRequests(userSuppliedIdentifier, realm, returnToUrl).First();
+ } catch (InvalidOperationException ex) {
+ throw ErrorUtilities.Wrap(ex, OpenIdStrings.OpenIdEndpointNotFound);
+ }
+ }
+
+ /// <summary>
+ /// Creates an authentication request to verify that a user controls
+ /// some given Identifier.
+ /// </summary>
+ /// <param name="userSuppliedIdentifier">
+ /// The Identifier supplied by the user. This may be a URL, an XRI or i-name.
+ /// </param>
+ /// <param name="realm">
+ /// The shorest URL that describes this relying party web site's address.
+ /// For example, if your login page is found at https://www.example.com/login.aspx,
+ /// your realm would typically be https://www.example.com/.
+ /// </param>
+ /// <returns>
+ /// An authentication request object that describes the HTTP response to
+ /// send to the user agent to initiate the authentication.
+ /// </returns>
+ /// <remarks>
+ /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para>
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if no OpenID endpoint could be found.</exception>
+ /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception>
+ public IAuthenticationRequest CreateRequest(Identifier userSuppliedIdentifier, Realm realm) {
+ Requires.NotNull(userSuppliedIdentifier, "userSuppliedIdentifier");
+ Requires.NotNull(realm, "realm");
+ Contract.Ensures(Contract.Result<IAuthenticationRequest>() != null);
+ try {
+ var result = this.CreateRequests(userSuppliedIdentifier, realm).First();
+ Contract.Assume(result != null);
+ return result;
+ } catch (InvalidOperationException ex) {
+ throw ErrorUtilities.Wrap(ex, OpenIdStrings.OpenIdEndpointNotFound);
+ }
+ }
+
+ /// <summary>
+ /// Creates an authentication request to verify that a user controls
+ /// some given Identifier.
+ /// </summary>
+ /// <param name="userSuppliedIdentifier">
+ /// The Identifier supplied by the user. This may be a URL, an XRI or i-name.
+ /// </param>
+ /// <returns>
+ /// An authentication request object that describes the HTTP response to
+ /// send to the user agent to initiate the authentication.
+ /// </returns>
+ /// <remarks>
+ /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para>
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if no OpenID endpoint could be found.</exception>
+ /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception>
+ public IAuthenticationRequest CreateRequest(Identifier userSuppliedIdentifier) {
+ Requires.NotNull(userSuppliedIdentifier, "userSuppliedIdentifier");
+ Contract.Ensures(Contract.Result<IAuthenticationRequest>() != null);
+ try {
+ return this.CreateRequests(userSuppliedIdentifier).First();
+ } catch (InvalidOperationException ex) {
+ throw ErrorUtilities.Wrap(ex, OpenIdStrings.OpenIdEndpointNotFound);
+ }
+ }
+
+ /// <summary>
+ /// Generates the authentication requests that can satisfy the requirements of some OpenID Identifier.
+ /// </summary>
+ /// <param name="userSuppliedIdentifier">
+ /// The Identifier supplied by the user. This may be a URL, an XRI or i-name.
+ /// </param>
+ /// <param name="realm">
+ /// The shorest URL that describes this relying party web site's address.
+ /// For example, if your login page is found at https://www.example.com/login.aspx,
+ /// your realm would typically be https://www.example.com/.
+ /// </param>
+ /// <param name="returnToUrl">
+ /// The URL of the login page, or the page prepared to receive authentication
+ /// responses from the OpenID Provider.
+ /// </param>
+ /// <returns>
+ /// A sequence of authentication requests, any of which constitutes a valid identity assertion on the Claimed Identifier.
+ /// Never null, but may be empty.
+ /// </returns>
+ /// <remarks>
+ /// <para>Any individual generated request can satisfy the authentication.
+ /// The generated requests are sorted in preferred order.
+ /// Each request is generated as it is enumerated to. Associations are created only as
+ /// <see cref="IAuthenticationRequest.RedirectingResponse"/> is called.</para>
+ /// <para>No exception is thrown if no OpenID endpoints were discovered.
+ /// An empty enumerable is returned instead.</para>
+ /// </remarks>
+ public virtual IEnumerable<IAuthenticationRequest> CreateRequests(Identifier userSuppliedIdentifier, Realm realm, Uri returnToUrl) {
+ Requires.NotNull(userSuppliedIdentifier, "userSuppliedIdentifier");
+ Requires.NotNull(realm, "realm");
+ Requires.NotNull(returnToUrl, "returnToUrl");
+ Contract.Ensures(Contract.Result<IEnumerable<IAuthenticationRequest>>() != null);
+
+ return AuthenticationRequest.Create(userSuppliedIdentifier, this, realm, returnToUrl, true).Cast<IAuthenticationRequest>().CacheGeneratedResults();
+ }
+
+ /// <summary>
+ /// Generates the authentication requests that can satisfy the requirements of some OpenID Identifier.
+ /// </summary>
+ /// <param name="userSuppliedIdentifier">
+ /// The Identifier supplied by the user. This may be a URL, an XRI or i-name.
+ /// </param>
+ /// <param name="realm">
+ /// The shorest URL that describes this relying party web site's address.
+ /// For example, if your login page is found at https://www.example.com/login.aspx,
+ /// your realm would typically be https://www.example.com/.
+ /// </param>
+ /// <returns>
+ /// A sequence of authentication requests, any of which constitutes a valid identity assertion on the Claimed Identifier.
+ /// Never null, but may be empty.
+ /// </returns>
+ /// <remarks>
+ /// <para>Any individual generated request can satisfy the authentication.
+ /// The generated requests are sorted in preferred order.
+ /// Each request is generated as it is enumerated to. Associations are created only as
+ /// <see cref="IAuthenticationRequest.RedirectingResponse"/> is called.</para>
+ /// <para>No exception is thrown if no OpenID endpoints were discovered.
+ /// An empty enumerable is returned instead.</para>
+ /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para>
+ /// </remarks>
+ /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception>
+ public IEnumerable<IAuthenticationRequest> CreateRequests(Identifier userSuppliedIdentifier, Realm realm) {
+ Requires.ValidState(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired);
+ Requires.NotNull(userSuppliedIdentifier, "userSuppliedIdentifier");
+ Requires.NotNull(realm, "realm");
+ Contract.Ensures(Contract.Result<IEnumerable<IAuthenticationRequest>>() != null);
+
+ // This next code contract is a BAD idea, because it causes each authentication request to be generated
+ // at least an extra time.
+ ////Contract.Ensures(Contract.ForAll(Contract.Result<IEnumerable<IAuthenticationRequest>>(), el => el != null));
+
+ // Build the return_to URL
+ UriBuilder returnTo = new UriBuilder(this.Channel.GetRequestFromContext().UrlBeforeRewriting);
+
+ // Trim off any parameters with an "openid." prefix, and a few known others
+ // to avoid carrying state from a prior login attempt.
+ returnTo.Query = string.Empty;
+ NameValueCollection queryParams = this.Channel.GetRequestFromContext().QueryStringBeforeRewriting;
+ var returnToParams = new Dictionary<string, string>(queryParams.Count);
+ foreach (string key in queryParams) {
+ if (!IsOpenIdSupportingParameter(key) && key != null) {
+ returnToParams.Add(key, queryParams[key]);
+ }
+ }
+ returnTo.AppendQueryArgs(returnToParams);
+
+ return this.CreateRequests(userSuppliedIdentifier, realm, returnTo.Uri);
+ }
+
+ /// <summary>
+ /// Generates the authentication requests that can satisfy the requirements of some OpenID Identifier.
+ /// </summary>
+ /// <param name="userSuppliedIdentifier">
+ /// The Identifier supplied by the user. This may be a URL, an XRI or i-name.
+ /// </param>
+ /// <returns>
+ /// A sequence of authentication requests, any of which constitutes a valid identity assertion on the Claimed Identifier.
+ /// Never null, but may be empty.
+ /// </returns>
+ /// <remarks>
+ /// <para>Any individual generated request can satisfy the authentication.
+ /// The generated requests are sorted in preferred order.
+ /// Each request is generated as it is enumerated to. Associations are created only as
+ /// <see cref="IAuthenticationRequest.RedirectingResponse"/> is called.</para>
+ /// <para>No exception is thrown if no OpenID endpoints were discovered.
+ /// An empty enumerable is returned instead.</para>
+ /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para>
+ /// </remarks>
+ /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception>
+ public IEnumerable<IAuthenticationRequest> CreateRequests(Identifier userSuppliedIdentifier) {
+ Requires.NotNull(userSuppliedIdentifier, "userSuppliedIdentifier");
+ Requires.ValidState(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired);
+ Contract.Ensures(Contract.Result<IEnumerable<IAuthenticationRequest>>() != null);
+
+ return this.CreateRequests(userSuppliedIdentifier, Realm.AutoDetect);
+ }
+
+ /// <summary>
+ /// Gets an authentication response from a Provider.
+ /// </summary>
+ /// <returns>The processed authentication response if there is any; <c>null</c> otherwise.</returns>
+ /// <remarks>
+ /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para>
+ /// </remarks>
+ public IAuthenticationResponse GetResponse() {
+ Requires.ValidState(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired);
+ return this.GetResponse(this.Channel.GetRequestFromContext());
+ }
+
+ /// <summary>
+ /// Gets an authentication response from a Provider.
+ /// </summary>
+ /// <param name="httpRequestInfo">The HTTP request that may be carrying an authentication response from the Provider.</param>
+ /// <returns>The processed authentication response if there is any; <c>null</c> otherwise.</returns>
+ public IAuthenticationResponse GetResponse(HttpRequestInfo httpRequestInfo) {
+ Requires.NotNull(httpRequestInfo, "httpRequestInfo");
+ try {
+ var message = this.Channel.ReadFromRequest(httpRequestInfo);
+ PositiveAssertionResponse positiveAssertion;
+ NegativeAssertionResponse negativeAssertion;
+ IndirectSignedResponse positiveExtensionOnly;
+ if ((positiveAssertion = message as PositiveAssertionResponse) != null) {
+ // We need to make sure that this assertion is coming from an endpoint
+ // that the host deems acceptable.
+ var providerEndpoint = new SimpleXrdsProviderEndpoint(positiveAssertion);
+ ErrorUtilities.VerifyProtocol(
+ this.FilterEndpoint(providerEndpoint),
+ OpenIdStrings.PositiveAssertionFromNonQualifiedProvider,
+ providerEndpoint.Uri);
+
+ var response = new PositiveAuthenticationResponse(positiveAssertion, this);
+ foreach (var behavior in this.Behaviors) {
+ behavior.OnIncomingPositiveAssertion(response);
+ }
+
+ return response;
+ } 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) {
+ Logger.OpenId.WarnFormat("Received unexpected message type {0} when expecting an assertion message.", message.GetType().Name);
+ }
+
+ return null;
+ } catch (ProtocolException ex) {
+ return new FailedAuthenticationResponse(ex);
+ }
+ }
+
+ /// <summary>
+ /// Processes the response received in a popup window or iframe to an AJAX-directed OpenID authentication.
+ /// </summary>
+ /// <returns>The HTTP response to send to this HTTP request.</returns>
+ /// <remarks>
+ /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para>
+ /// </remarks>
+ public OutgoingWebResponse ProcessResponseFromPopup() {
+ Requires.ValidState(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired);
+ Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null);
+
+ return this.ProcessResponseFromPopup(this.Channel.GetRequestFromContext());
+ }
+
+ /// <summary>
+ /// Processes the response received in a popup window or iframe to an AJAX-directed OpenID authentication.
+ /// </summary>
+ /// <param name="request">The incoming HTTP request that is expected to carry an OpenID authentication response.</param>
+ /// <returns>The HTTP response to send to this HTTP request.</returns>
+ public OutgoingWebResponse ProcessResponseFromPopup(HttpRequestInfo request) {
+ Requires.NotNull(request, "request");
+ Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null);
+
+ return this.ProcessResponseFromPopup(request, null);
+ }
+
+ /// <summary>
+ /// Allows an OpenID extension to read data out of an unverified positive authentication assertion
+ /// and send it down to the client browser so that Javascript running on the page can perform
+ /// some preprocessing on the extension data.
+ /// </summary>
+ /// <typeparam name="T">The extension <i>response</i> type that will read data from the assertion.</typeparam>
+ /// <param name="propertyName">The property name on the openid_identifier input box object that will be used to store the extension data. For example: sreg</param>
+ /// <remarks>
+ /// This method should be called before <see cref="ProcessResponseFromPopup()"/>.
+ /// </remarks>
+ [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "By design")]
+ public void RegisterClientScriptExtension<T>(string propertyName) where T : IClientScriptExtensionResponse {
+ Requires.NotNullOrEmpty(propertyName, "propertyName");
+ ErrorUtilities.VerifyArgumentNamed(!this.clientScriptExtensions.ContainsValue(propertyName), "propertyName", OpenIdStrings.ClientScriptExtensionPropertyNameCollision, propertyName);
+ foreach (var ext in this.clientScriptExtensions.Keys) {
+ ErrorUtilities.VerifyArgument(ext != typeof(T), OpenIdStrings.ClientScriptExtensionTypeCollision, typeof(T).FullName);
+ }
+ this.clientScriptExtensions.Add(typeof(T), propertyName);
+ }
+
+ #region IDisposable Members
+
+ /// <summary>
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ /// </summary>
+ public void Dispose() {
+ this.Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Determines whether some parameter name belongs to OpenID or this library
+ /// as a protocol or internal parameter name.
+ /// </summary>
+ /// <param name="parameterName">Name of the parameter.</param>
+ /// <returns>
+ /// <c>true</c> if the named parameter is a library- or protocol-specific parameter; otherwise, <c>false</c>.
+ /// </returns>
+ internal static bool IsOpenIdSupportingParameter(string parameterName) {
+ // Yes, it is possible with some query strings to have a null or empty parameter name
+ if (string.IsNullOrEmpty(parameterName)) {
+ return false;
+ }
+
+ Protocol protocol = Protocol.Default;
+ return parameterName.StartsWith(protocol.openid.Prefix, StringComparison.OrdinalIgnoreCase)
+ || parameterName.StartsWith(OpenIdUtilities.CustomParameterPrefix, StringComparison.Ordinal);
+ }
+
+ /// <summary>
+ /// Creates a relying party that does not verify incoming messages against
+ /// nonce or association stores.
+ /// </summary>
+ /// <returns>The instantiated <see cref="OpenIdRelyingParty"/>.</returns>
+ /// <remarks>
+ /// Useful for previewing messages while
+ /// allowing them to be fully processed and verified later.
+ /// </remarks>
+ internal static OpenIdRelyingParty CreateNonVerifying() {
+ OpenIdRelyingParty rp = new OpenIdRelyingParty();
+ try {
+ rp.Channel = OpenIdRelyingPartyChannel.CreateNonVerifyingChannel();
+ return rp;
+ } catch {
+ rp.Dispose();
+ throw;
+ }
+ }
+
+ /// <summary>
+ /// Processes the response received in a popup window or iframe to an AJAX-directed OpenID authentication.
+ /// </summary>
+ /// <param name="request">The incoming HTTP request that is expected to carry an OpenID authentication response.</param>
+ /// <param name="callback">The callback fired after the response status has been determined but before the Javascript response is formulated.</param>
+ /// <returns>
+ /// The HTTP response to send to this HTTP request.
+ /// </returns>
+ [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "OpenID", Justification = "real word"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "iframe", Justification = "Code contracts")]
+ internal OutgoingWebResponse ProcessResponseFromPopup(HttpRequestInfo request, Action<AuthenticationStatus> callback) {
+ Requires.NotNull(request, "request");
+ Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null);
+
+ string extensionsJson = null;
+ var authResponse = this.NonVerifyingRelyingParty.GetResponse();
+ ErrorUtilities.VerifyProtocol(authResponse != null, OpenIdStrings.PopupRedirectMissingResponse);
+
+ // Give the caller a chance to notify the hosting page and fill up the clientScriptExtensions collection.
+ if (callback != null) {
+ callback(authResponse.Status);
+ }
+
+ Logger.OpenId.DebugFormat("Popup or iframe callback from OP: {0}", request.Url);
+ Logger.Controls.DebugFormat(
+ "An authentication response was found in a popup window or iframe using a non-verifying RP with status: {0}",
+ authResponse.Status);
+ if (authResponse.Status == AuthenticationStatus.Authenticated) {
+ var extensionsDictionary = new Dictionary<string, string>();
+ foreach (var pair in this.clientScriptExtensions) {
+ IClientScriptExtensionResponse extension = (IClientScriptExtensionResponse)authResponse.GetExtension(pair.Key);
+ if (extension == null) {
+ continue;
+ }
+ var positiveResponse = (PositiveAuthenticationResponse)authResponse;
+ string js = extension.InitializeJavaScriptData(positiveResponse.Response);
+ if (!string.IsNullOrEmpty(js)) {
+ extensionsDictionary[pair.Value] = js;
+ }
+ }
+
+ extensionsJson = MessagingUtilities.CreateJsonObject(extensionsDictionary, true);
+ }
+
+ string payload = "document.URL";
+ if (request.HttpMethod == "POST") {
+ // Promote all form variables to the query string, but since it won't be passed
+ // to any server (this is a javascript window-to-window transfer) the length of
+ // it can be arbitrarily long, whereas it was POSTed here probably because it
+ // was too long for HTTP transit.
+ UriBuilder payloadUri = new UriBuilder(request.Url);
+ payloadUri.AppendQueryArgs(request.Form.ToDictionary());
+ payload = MessagingUtilities.GetSafeJavascriptValue(payloadUri.Uri.AbsoluteUri);
+ }
+
+ if (!string.IsNullOrEmpty(extensionsJson)) {
+ payload += ", " + extensionsJson;
+ }
+
+ return InvokeParentPageScript("dnoa_internal.processAuthorizationResult(" + payload + ")");
+ }
+
+ /// <summary>
+ /// Performs discovery on the specified identifier.
+ /// </summary>
+ /// <param name="identifier">The identifier to discover services for.</param>
+ /// <returns>A non-null sequence of services discovered for the identifier.</returns>
+ internal IEnumerable<IdentifierDiscoveryResult> Discover(Identifier identifier) {
+ Requires.NotNull(identifier, "identifier");
+ Contract.Ensures(Contract.Result<IEnumerable<IdentifierDiscoveryResult>>() != null);
+
+ IEnumerable<IdentifierDiscoveryResult> results = Enumerable.Empty<IdentifierDiscoveryResult>();
+ foreach (var discoverer in this.DiscoveryServices) {
+ bool abortDiscoveryChain;
+ var discoveryResults = discoverer.Discover(identifier, this.WebRequestHandler, out abortDiscoveryChain).CacheGeneratedResults();
+ results = results.Concat(discoveryResults);
+ if (abortDiscoveryChain) {
+ Logger.OpenId.InfoFormat("Further discovery on '{0}' was stopped by the {1} discovery service.", identifier, discoverer.GetType().Name);
+ break;
+ }
+ }
+
+ // If any OP Identifier service elements were found, we must not proceed
+ // to use any Claimed Identifier services, per OpenID 2.0 sections 7.3.2.2 and 11.2.
+ // For a discussion on this topic, see
+ // http://groups.google.com/group/dotnetopenid/browse_thread/thread/4b5a8c6b2210f387/5e25910e4d2252c8
+ // Sometimes the IIdentifierDiscoveryService will automatically filter this for us, but
+ // just to be sure, we'll do it here as well.
+ if (!this.SecuritySettings.AllowDualPurposeIdentifiers) {
+ results = results.CacheGeneratedResults(); // avoid performing discovery repeatedly
+ var opIdentifiers = results.Where(result => result.ClaimedIdentifier == result.Protocol.ClaimedIdentifierForOPIdentifier);
+ var claimedIdentifiers = results.Where(result => result.ClaimedIdentifier != result.Protocol.ClaimedIdentifierForOPIdentifier);
+ results = opIdentifiers.Any() ? opIdentifiers : claimedIdentifiers;
+ }
+
+ return results;
+ }
+
+ /// <summary>
+ /// Checks whether a given OP Endpoint is permitted by the host relying party.
+ /// </summary>
+ /// <param name="endpoint">The OP endpoint.</param>
+ /// <returns><c>true</c> if the OP Endpoint is allowed; <c>false</c> otherwise.</returns>
+ protected internal bool FilterEndpoint(IProviderEndpoint endpoint) {
+ if (this.SecuritySettings.RejectAssertionsFromUntrustedProviders) {
+ if (!this.SecuritySettings.TrustedProviderEndpoints.Contains(endpoint.Uri)) {
+ Logger.OpenId.InfoFormat("Filtering out OP endpoint {0} because it is not on the exclusive trusted provider whitelist.", endpoint.Uri.AbsoluteUri);
+ return false;
+ }
+ }
+
+ if (endpoint.Version < Protocol.Lookup(this.SecuritySettings.MinimumRequiredOpenIdVersion).Version) {
+ Logger.OpenId.InfoFormat(
+ "Filtering out OP endpoint {0} because it implements OpenID {1} but this relying party requires OpenID {2} or later.",
+ endpoint.Uri.AbsoluteUri,
+ endpoint.Version,
+ Protocol.Lookup(this.SecuritySettings.MinimumRequiredOpenIdVersion).Version);
+ return false;
+ }
+
+ if (this.EndpointFilter != null) {
+ if (!this.EndpointFilter(endpoint)) {
+ Logger.OpenId.InfoFormat("Filtering out OP endpoint {0} because the host rejected it.", endpoint.Uri.AbsoluteUri);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources
+ /// </summary>
+ /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
+ protected virtual void Dispose(bool disposing) {
+ if (disposing) {
+ if (this.nonVerifyingRelyingParty != null) {
+ this.nonVerifyingRelyingParty.Dispose();
+ this.nonVerifyingRelyingParty = null;
+ }
+
+ // Tear off the instance member as a local variable for thread safety.
+ IDisposable disposableChannel = this.channel as IDisposable;
+ if (disposableChannel != null) {
+ disposableChannel.Dispose();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Invokes a method on a parent frame or window and closes the calling popup window if applicable.
+ /// </summary>
+ /// <param name="methodCall">The method to call on the parent window, including
+ /// parameters. (i.e. "callback('arg1', 2)"). No escaping is done by this method.</param>
+ /// <returns>The entire HTTP response to send to the popup window or iframe to perform the invocation.</returns>
+ private static OutgoingWebResponse InvokeParentPageScript(string methodCall) {
+ Requires.NotNullOrEmpty(methodCall, "methodCall");
+
+ Logger.OpenId.DebugFormat("Sending Javascript callback: {0}", methodCall);
+ StringBuilder builder = new StringBuilder();
+ builder.AppendLine("<html><body><script type='text/javascript' language='javascript'><!--");
+ builder.AppendLine("//<![CDATA[");
+ builder.Append(@" var inPopup = !window.frameElement;
+ var objSrc = inPopup ? window.opener : window.frameElement;
+");
+
+ // Something about calling objSrc.{0} can somehow cause FireFox to forget about the inPopup variable,
+ // so we have to actually put the test for it ABOVE the call to objSrc.{0} so that it already
+ // whether to call window.self.close() after the call.
+ string htmlFormat = @" if (inPopup) {{
+ try {{
+ objSrc.{0};
+ }} catch (ex) {{
+ alert(ex);
+ }} finally {{
+ window.self.close();
+ }}
+ }} else {{
+ objSrc.{0};
+ }}";
+ builder.AppendFormat(CultureInfo.InvariantCulture, htmlFormat, methodCall);
+ builder.AppendLine("//]]>--></script>");
+ builder.AppendLine("</body></html>");
+
+ var response = new OutgoingWebResponse();
+ response.Body = builder.ToString();
+ response.Headers.Add(HttpResponseHeader.ContentType, new ContentType("text/html").ToString());
+ return response;
+ }
+
+ /// <summary>
+ /// Called by derived classes when behaviors are added or removed.
+ /// </summary>
+ /// <param name="sender">The collection being modified.</param>
+ /// <param name="e">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
+ private void OnBehaviorsChanged(object sender, NotifyCollectionChangedEventArgs e) {
+ foreach (IRelyingPartyBehavior profile in e.NewItems) {
+ profile.ApplySecuritySettings(this.SecuritySettings);
+ Reporting.RecordFeatureUse(profile);
+ }
+ }
+
+#if CONTRACTS_FULL
+ /// <summary>
+ /// Verifies conditions that should be true for any valid state of this object.
+ /// </summary>
+ [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")]
+ [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")]
+ [ContractInvariantMethod]
+ private void ObjectInvariant() {
+ Contract.Invariant(this.SecuritySettings != null);
+ Contract.Invariant(this.Channel != null);
+ Contract.Invariant(this.EndpointOrder != null);
+ }
+#endif
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/PositiveAnonymousResponse.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/PositiveAnonymousResponse.cs
new file mode 100644
index 0000000..de82bed
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/PositiveAnonymousResponse.cs
@@ -0,0 +1,347 @@
+//-----------------------------------------------------------------------
+// <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>
+ /// Information about the OP endpoint that issued this assertion.
+ /// </summary>
+ private readonly IProviderEndpoint provider;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PositiveAnonymousResponse"/> class.
+ /// </summary>
+ /// <param name="response">The response message.</param>
+ protected internal PositiveAnonymousResponse(IndirectSignedResponse response) {
+ Requires.NotNull(response, "response");
+
+ this.response = response;
+ if (response.ProviderEndpoint != null && response.Version != null) {
+ this.provider = new ProviderEndpointDescription(response.ProviderEndpoint, response.Version);
+ }
+
+ // Derived types of this are responsible to log an appropriate message for themselves.
+ if (Logger.OpenId.IsInfoEnabled && this.GetType() == typeof(PositiveAnonymousResponse)) {
+ Logger.OpenId.Info("Received anonymous (identity-less) positive assertion.");
+ }
+
+ if (response.ProviderEndpoint != null) {
+ Reporting.RecordEventOccurrence(this, response.ProviderEndpoint.AbsoluteUri);
+ }
+ }
+
+ #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 information about the OpenId Provider, as advertised by the
+ /// OpenID discovery documents found at the <see cref="ClaimedIdentifier"/>
+ /// location.
+ /// </summary>
+ /// <value>
+ /// The Provider endpoint that issued the positive assertion;
+ /// or <c>null</c> if information about the Provider is unavailable.
+ /// </value>
+ public IProviderEndpoint Provider {
+ get { return this.provider; }
+ }
+
+ /// <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 a value indicating whether trusted callback arguments are available.
+ /// </summary>
+ /// <remarks>
+ /// We use this internally to avoid logging a warning during a standard snapshot creation.
+ /// </remarks>
+ internal bool TrustedCallbackArgumentsAvailable {
+ get { return this.response.ReturnToParametersSignatureValidated; }
+ }
+
+ /// <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.GetUntrustedCallbackArgument(key);
+ } else {
+ Logger.OpenId.WarnFormat(OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IRelyingPartyAssociationStore).Name, typeof(OpenIdRelyingParty).Name);
+ return null;
+ }
+ }
+
+ /// <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 even if the RP is in stateless mode,
+ /// or the callback parameters are otherwise unverifiable as untampered with.
+ /// Therefore, use this method only when the callback argument is not to be
+ /// used to make a security-sensitive decision.
+ /// </remarks>
+ public string GetUntrustedCallbackArgument(string key) {
+ return this.response.GetReturnToArgument(key);
+ }
+
+ /// <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) {
+ return this.GetUntrustedCallbackArguments();
+ } else {
+ Logger.OpenId.WarnFormat(OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IRelyingPartyAssociationStore).Name, typeof(OpenIdRelyingParty).Name);
+ return EmptyDictionary<string, string>.Instance;
+ }
+ }
+
+ /// <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> GetUntrustedCallbackArguments() {
+ 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;
+ }
+
+ /// <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&lt;T&gt;"/> 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) {
+ 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&lt;T&gt;"/> 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) {
+ return this.response.Extensions.OfType<IOpenIdMessageExtension>().Where(ext => extensionType.IsInstanceOfType(ext)).FirstOrDefault();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/PositiveAuthenticationResponse.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/PositiveAuthenticationResponse.cs
new file mode 100644
index 0000000..1123912
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId.RelyingParty/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) {
+ 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
+
+ /// <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));
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/PositiveAuthenticationResponseSnapshot.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/PositiveAuthenticationResponseSnapshot.cs
new file mode 100644
index 0000000..141b4f7
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/PositiveAuthenticationResponseSnapshot.cs
@@ -0,0 +1,304 @@
+//-----------------------------------------------------------------------
+// <copyright file="PositiveAuthenticationResponseSnapshot.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.Text;
+ using System.Web;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// A serializable snapshot of a verified authentication message.
+ /// </summary>
+ [Serializable]
+ internal class PositiveAuthenticationResponseSnapshot : IAuthenticationResponse {
+ /// <summary>
+ /// The callback arguments that came with the authentication response.
+ /// </summary>
+ private IDictionary<string, string> callbackArguments;
+
+ /// <summary>
+ /// The untrusted callback arguments that came with the authentication response.
+ /// </summary>
+ private IDictionary<string, string> untrustedCallbackArguments;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PositiveAuthenticationResponseSnapshot"/> class.
+ /// </summary>
+ /// <param name="copyFrom">The authentication response to copy from.</param>
+ internal PositiveAuthenticationResponseSnapshot(IAuthenticationResponse copyFrom) {
+ Requires.NotNull(copyFrom, "copyFrom");
+
+ this.ClaimedIdentifier = copyFrom.ClaimedIdentifier;
+ this.FriendlyIdentifierForDisplay = copyFrom.FriendlyIdentifierForDisplay;
+ this.Status = copyFrom.Status;
+ this.Provider = copyFrom.Provider;
+ this.untrustedCallbackArguments = copyFrom.GetUntrustedCallbackArguments();
+
+ // Do this special check to avoid logging a warning for trying to clone a dictionary.
+ var anonResponse = copyFrom as PositiveAnonymousResponse;
+ if (anonResponse == null || anonResponse.TrustedCallbackArgumentsAvailable) {
+ this.callbackArguments = copyFrom.GetCallbackArguments();
+ } else {
+ this.callbackArguments = EmptyDictionary<string, string>.Instance;
+ }
+ }
+
+ #region IAuthenticationResponse Members
+
+ /// <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 Identifier ClaimedIdentifier { get; private set; }
+
+ /// <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 string FriendlyIdentifierForDisplay { get; private set; }
+
+ /// <summary>
+ /// Gets the detailed success or failure status of the authentication attempt.
+ /// </summary>
+ /// <value></value>
+ public AuthenticationStatus Status { get; private set; }
+
+ /// <summary>
+ /// Gets information about the OpenId Provider, as advertised by the
+ /// OpenID discovery documents found at the <see cref="ClaimedIdentifier"/>
+ /// location.
+ /// </summary>
+ /// <value>
+ /// The Provider endpoint that issued the positive assertion;
+ /// or <c>null</c> if information about the Provider is unavailable.
+ /// </value>
+ public IProviderEndpoint Provider { get; private set; }
+
+ /// <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 { throw new NotSupportedException(OpenIdStrings.NotSupportedByAuthenticationSnapshot); }
+ }
+
+ /// <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&lt;T&gt;"/> 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 {
+ throw new NotSupportedException(OpenIdStrings.NotSupportedByAuthenticationSnapshot);
+ }
+
+ /// <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) {
+ throw new NotSupportedException(OpenIdStrings.NotSupportedByAuthenticationSnapshot);
+ }
+
+ /// <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&lt;T&gt;"/> 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 {
+ throw new NotSupportedException(OpenIdStrings.NotSupportedByAuthenticationSnapshot);
+ }
+
+ /// <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) {
+ throw new NotSupportedException(OpenIdStrings.NotSupportedByAuthenticationSnapshot);
+ }
+
+ /// <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>
+ /// <para>This MAY return any argument on the querystring that came with the authentication response,
+ /// which may include parameters not explicitly added using
+ /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.</para>
+ /// <para>Note that these values are NOT protected against tampering in transit.</para>
+ /// </remarks>
+ public IDictionary<string, string> GetCallbackArguments() {
+ // Return a copy so that the caller cannot change the contents.
+ return new Dictionary<string, string>(this.callbackArguments);
+ }
+
+ /// <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 even if the RP is in stateless mode,
+ /// or the callback parameters are otherwise unverifiable as untampered with.
+ /// Therefore, use this method only when the callback argument is not to be
+ /// used to make a security-sensitive decision.
+ /// </remarks>
+ public IDictionary<string, string> GetUntrustedCallbackArguments() {
+ // Return a copy so that the caller cannot change the contents.
+ return new Dictionary<string, string>(this.untrustedCallbackArguments);
+ }
+
+ /// <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>
+ /// <para>This may return any argument on the querystring that came with the authentication response,
+ /// which may include parameters not explicitly added using
+ /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.</para>
+ /// <para>Note that these values are NOT protected against tampering in transit.</para>
+ /// </remarks>
+ public string GetCallbackArgument(string key) {
+ string value;
+ this.callbackArguments.TryGetValue(key, out value);
+ return value;
+ }
+
+ /// <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 even if the RP is in stateless mode,
+ /// or the callback parameters are otherwise unverifiable as untampered with.
+ /// Therefore, use this method only when the callback argument is not to be
+ /// used to make a security-sensitive decision.
+ /// </remarks>
+ public string GetUntrustedCallbackArgument(string key) {
+ string value;
+ this.untrustedCallbackArguments.TryGetValue(key, out value);
+ return value;
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/SimpleXrdsProviderEndpoint.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/SimpleXrdsProviderEndpoint.cs
new file mode 100644
index 0000000..678f69a
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/SimpleXrdsProviderEndpoint.cs
@@ -0,0 +1,78 @@
+//-----------------------------------------------------------------------
+// <copyright file="SimpleXrdsProviderEndpoint.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.RelyingParty {
+ using System;
+ using System.Collections.ObjectModel;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.Messages;
+
+ /// <summary>
+ /// A very simple IXrdsProviderEndpoint implementation for verifying that all positive
+ /// assertions (particularly unsolicited ones) are received from OP endpoints that
+ /// are deemed permissible by the host RP.
+ /// </summary>
+ internal class SimpleXrdsProviderEndpoint : IProviderEndpoint {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SimpleXrdsProviderEndpoint"/> class.
+ /// </summary>
+ /// <param name="positiveAssertion">The positive assertion.</param>
+ internal SimpleXrdsProviderEndpoint(PositiveAssertionResponse positiveAssertion) {
+ this.Uri = positiveAssertion.ProviderEndpoint;
+ this.Version = positiveAssertion.Version;
+ }
+
+ #region IProviderEndpoint Members
+
+ /// <summary>
+ /// Gets the detected version of OpenID implemented by the Provider.
+ /// </summary>
+ public Version Version { get; private set; }
+
+ /// <summary>
+ /// Gets the URL that the OpenID Provider receives authentication requests at.
+ /// </summary>
+ public Uri Uri { get; private set; }
+
+ /// <summary>
+ /// Checks whether the OpenId Identifier claims support for a given extension.
+ /// </summary>
+ /// <typeparam name="T">The extension whose support is being queried.</typeparam>
+ /// <returns>
+ /// True if support for the extension is advertised. False otherwise.
+ /// </returns>
+ /// <remarks>
+ /// Note that a true or false return value is no guarantee of a Provider's
+ /// support for or lack of support for an extension. The return value is
+ /// determined by how the authenticating user filled out his/her XRDS document only.
+ /// The only way to be sure of support for a given extension is to include
+ /// the extension in the request and see if a response comes back for that extension.
+ /// </remarks>
+ bool IProviderEndpoint.IsExtensionSupported<T>() {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Checks whether the OpenId Identifier claims support for a given extension.
+ /// </summary>
+ /// <param name="extensionType">The extension whose support is being queried.</param>
+ /// <returns>
+ /// True if support for the extension is advertised. False otherwise.
+ /// </returns>
+ /// <remarks>
+ /// Note that a true or false return value is no guarantee of a Provider's
+ /// support for or lack of support for an extension. The return value is
+ /// determined by how the authenticating user filled out his/her XRDS document only.
+ /// The only way to be sure of support for a given extension is to include
+ /// the extension in the request and see if a response comes back for that extension.
+ /// </remarks>
+ bool IProviderEndpoint.IsExtensionSupported(Type extensionType) {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/StandardRelyingPartyApplicationStore.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/StandardRelyingPartyApplicationStore.cs
new file mode 100644
index 0000000..a14b55d
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/StandardRelyingPartyApplicationStore.cs
@@ -0,0 +1,110 @@
+//-----------------------------------------------------------------------
+// <copyright file="StandardRelyingPartyApplicationStore.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.RelyingParty {
+ using System;
+ using System.Collections.Generic;
+ using DotNetOpenAuth.Configuration;
+ using DotNetOpenAuth.Messaging.Bindings;
+ using DotNetOpenAuth.OpenId.ChannelElements;
+
+ /// <summary>
+ /// An in-memory store for Relying Parties, suitable for single server, single process
+ /// ASP.NET web sites.
+ /// </summary>
+ public class StandardRelyingPartyApplicationStore : IOpenIdApplicationStore {
+ /// <summary>
+ /// The nonce store to use.
+ /// </summary>
+ private readonly INonceStore nonceStore;
+
+ /// <summary>
+ /// The association store to use.
+ /// </summary>
+ private readonly ICryptoKeyStore keyStore;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StandardRelyingPartyApplicationStore"/> class.
+ /// </summary>
+ public StandardRelyingPartyApplicationStore() {
+ this.nonceStore = new NonceMemoryStore(OpenIdElement.Configuration.MaxAuthenticationTime);
+ this.keyStore = new MemoryCryptoKeyStore();
+ }
+
+ #region ICryptoKeyStore Members
+
+ /// <summary>
+ /// Gets the key in a given bucket and handle.
+ /// </summary>
+ /// <param name="bucket">The bucket name. Case sensitive.</param>
+ /// <param name="handle">The key handle. Case sensitive.</param>
+ /// <returns>
+ /// The cryptographic key, or <c>null</c> if no matching key was found.
+ /// </returns>
+ public CryptoKey GetKey(string bucket, string handle) {
+ return this.keyStore.GetKey(bucket, handle);
+ }
+
+ /// <summary>
+ /// Gets a sequence of existing keys within a given bucket.
+ /// </summary>
+ /// <param name="bucket">The bucket name. Case sensitive.</param>
+ /// <returns>
+ /// A sequence of handles and keys, ordered by descending <see cref="CryptoKey.ExpiresUtc"/>.
+ /// </returns>
+ public IEnumerable<KeyValuePair<string, CryptoKey>> GetKeys(string bucket) {
+ return this.keyStore.GetKeys(bucket);
+ }
+
+ /// <summary>
+ /// Stores a cryptographic key.
+ /// </summary>
+ /// <param name="bucket">The name of the bucket to store the key in. Case sensitive.</param>
+ /// <param name="handle">The handle to the key, unique within the bucket. Case sensitive.</param>
+ /// <param name="key">The key to store.</param>
+ /// <exception cref="CryptoKeyCollisionException">Thrown in the event of a conflict with an existing key in the same bucket and with the same handle.</exception>
+ public void StoreKey(string bucket, string handle, CryptoKey key) {
+ this.keyStore.StoreKey(bucket, handle, key);
+ }
+
+ /// <summary>
+ /// Removes the key.
+ /// </summary>
+ /// <param name="bucket">The bucket name. Case sensitive.</param>
+ /// <param name="handle">The key handle. Case sensitive.</param>
+ public void RemoveKey(string bucket, string handle) {
+ this.keyStore.RemoveKey(bucket, handle);
+ }
+
+ #endregion
+
+ #region INonceStore Members
+
+ /// <summary>
+ /// Stores a given nonce and timestamp.
+ /// </summary>
+ /// <param name="context">The context, or namespace, within which the <paramref name="nonce"/> must be unique.</param>
+ /// <param name="nonce">A series of random characters.</param>
+ /// <param name="timestampUtc">The timestamp that together with the nonce string make it unique.
+ /// The timestamp may also be used by the data store to clear out old nonces.</param>
+ /// <returns>
+ /// True if the nonce+timestamp (combination) was not previously in the database.
+ /// False if the nonce was stored previously with the same timestamp.
+ /// </returns>
+ /// <remarks>
+ /// The nonce must be stored for no less than the maximum time window a message may
+ /// be processed within before being discarded as an expired message.
+ /// If the binding element is applicable to your channel, this expiration window
+ /// is retrieved or set using the
+ /// <see cref="StandardExpirationBindingElement.MaximumMessageAge"/> property.
+ /// </remarks>
+ public bool StoreNonce(string context, string nonce, DateTime timestampUtc) {
+ return this.nonceStore.StoreNonce(context, nonce, timestampUtc);
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/WellKnownProviders.cs b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/WellKnownProviders.cs
new file mode 100644
index 0000000..ad1a11a
--- /dev/null
+++ b/src/DotNetOpenAuth.OpenId.RelyingParty/OpenId/RelyingParty/WellKnownProviders.cs
@@ -0,0 +1,52 @@
+//-----------------------------------------------------------------------
+// <copyright file="WellKnownProviders.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.RelyingParty {
+ using System.Diagnostics.CodeAnalysis;
+
+ /// <summary>
+ /// Common OpenID Provider Identifiers.
+ /// </summary>
+ public sealed class WellKnownProviders {
+ /// <summary>
+ /// The Yahoo OP Identifier.
+ /// </summary>
+ [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "Immutable type")]
+ public static readonly Identifier Yahoo = "https://me.yahoo.com/";
+
+ /// <summary>
+ /// The Google OP Identifier.
+ /// </summary>
+ [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "Immutable type")]
+ public static readonly Identifier Google = "https://www.google.com/accounts/o8/id";
+
+ /// <summary>
+ /// The MyOpenID OP Identifier.
+ /// </summary>
+ [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "Immutable type")]
+ public static readonly Identifier MyOpenId = "https://www.myopenid.com/";
+
+ /// <summary>
+ /// The Verisign OP Identifier.
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Verisign", Justification = "The spelling is correct.")]
+ [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "Immutable type")]
+ public static readonly Identifier Verisign = "https://pip.verisignlabs.com/";
+
+ /// <summary>
+ /// The MyVidoop OP Identifier.
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Vidoop", Justification = "The spelling is correct.")]
+ [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "Immutable type")]
+ public static readonly Identifier MyVidoop = "https://myvidoop.com/";
+
+ /// <summary>
+ /// Prevents a default instance of the <see cref="WellKnownProviders"/> class from being created.
+ /// </summary>
+ private WellKnownProviders() {
+ }
+ }
+}