summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/DotNetOpenAuth.Test/OpenId/Messages/CheckIdRequestTests.cs4
-rw-r--r--src/DotNetOpenAuth.Test/OpenId/Messages/IndirectErrorResponseTests.cs3
-rw-r--r--src/DotNetOpenAuth/DotNetOpenAuth.csproj6
-rw-r--r--src/DotNetOpenAuth/Messaging/MessagePartAttribute.cs32
-rw-r--r--src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs1
-rw-r--r--src/DotNetOpenAuth/OpenId/ChannelElements/ITamperResistantOpenIdMessage.cs12
-rw-r--r--src/DotNetOpenAuth/OpenId/ChannelElements/OriginalStringUriEncoder.cs47
-rw-r--r--src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs2
-rw-r--r--src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationRequest.cs32
-rw-r--r--src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationResponse.cs49
-rw-r--r--src/DotNetOpenAuth/OpenId/Messages/CheckIdRequest.cs10
-rw-r--r--src/DotNetOpenAuth/OpenId/Messages/IndirectErrorResponse.cs9
-rw-r--r--src/DotNetOpenAuth/OpenId/Messages/IndirectResponseBase.cs76
-rw-r--r--src/DotNetOpenAuth/OpenId/Messages/NegativeAssertionResponse.cs70
-rw-r--r--src/DotNetOpenAuth/OpenId/Messages/PositiveAssertionResponse.cs216
-rw-r--r--src/DotNetOpenAuth/OpenId/Messages/RequestBase.cs15
-rw-r--r--src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs9
-rw-r--r--src/DotNetOpenAuth/OpenId/OpenIdStrings.resx3
18 files changed, 585 insertions, 11 deletions
diff --git a/src/DotNetOpenAuth.Test/OpenId/Messages/CheckIdRequestTests.cs b/src/DotNetOpenAuth.Test/OpenId/Messages/CheckIdRequestTests.cs
index dd7b3d9..479816b 100644
--- a/src/DotNetOpenAuth.Test/OpenId/Messages/CheckIdRequestTests.cs
+++ b/src/DotNetOpenAuth.Test/OpenId/Messages/CheckIdRequestTests.cs
@@ -83,6 +83,10 @@ namespace DotNetOpenAuth.Test.OpenId.Messages {
/// Verifies that the validation check throws if the return_to and the realm
/// values are not compatible.
/// </summary>
+ /// <remarks>
+ /// This test does not test all the realm-return_to matching rules as that is done in the Realm tests.
+ /// This test merely checks that the compatibility match occurs at all.
+ /// </remarks>
[TestMethod, ExpectedException(typeof(ProtocolException))]
public void RealmReturnToMismatchV2() {
this.setupv2.Realm = "http://somehost/";
diff --git a/src/DotNetOpenAuth.Test/OpenId/Messages/IndirectErrorResponseTests.cs b/src/DotNetOpenAuth.Test/OpenId/Messages/IndirectErrorResponseTests.cs
index 7794970..be72cb7 100644
--- a/src/DotNetOpenAuth.Test/OpenId/Messages/IndirectErrorResponseTests.cs
+++ b/src/DotNetOpenAuth.Test/OpenId/Messages/IndirectErrorResponseTests.cs
@@ -18,7 +18,8 @@ namespace DotNetOpenAuth.Test.OpenId.Messages {
[TestInitialize]
public void Setup() {
- this.response = new IndirectErrorResponse(Protocol.V20.Version, this.recipient);
+ CheckIdRequest request = new CheckIdRequest(Protocol.V20.Version, this.recipient, true);
+ this.response = new IndirectErrorResponse(request);
}
[TestMethod]
diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj
index 1ae0007..f4316d7 100644
--- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj
+++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj
@@ -160,13 +160,19 @@
<Compile Include="OpenId\AssociationMemoryStore.cs" />
<Compile Include="OpenId\Associations.cs" />
<Compile Include="OpenId\ChannelElements\ITamperResistantOpenIdMessage.cs" />
+ <Compile Include="OpenId\ChannelElements\OriginalStringUriEncoder.cs" />
<Compile Include="OpenId\ChannelElements\SigningBindingElement.cs" />
<Compile Include="OpenId\ChannelElements\KeyValueFormEncoding.cs" />
<Compile Include="OpenId\ChannelElements\OpenIdChannel.cs" />
<Compile Include="OpenId\ChannelElements\OpenIdMessageFactory.cs" />
<Compile Include="OpenId\Configuration.cs" />
<Compile Include="OpenId\Identifier.cs" />
+ <Compile Include="OpenId\Messages\CheckAuthenticationRequest.cs" />
+ <Compile Include="OpenId\Messages\CheckAuthenticationResponse.cs" />
<Compile Include="OpenId\Messages\CheckIdRequest.cs" />
+ <Compile Include="OpenId\Messages\IndirectResponseBase.cs" />
+ <Compile Include="OpenId\Messages\NegativeAssertionResponse.cs" />
+ <Compile Include="OpenId\Messages\PositiveAssertionResponse.cs" />
<Compile Include="OpenId\NoDiscoveryIdentifier.cs" />
<Compile Include="OpenId\Realm.cs" />
<Compile Include="OpenId\RelyingPartyDescription.cs" />
diff --git a/src/DotNetOpenAuth/Messaging/MessagePartAttribute.cs b/src/DotNetOpenAuth/Messaging/MessagePartAttribute.cs
index 51a888e..f1c39f5 100644
--- a/src/DotNetOpenAuth/Messaging/MessagePartAttribute.cs
+++ b/src/DotNetOpenAuth/Messaging/MessagePartAttribute.cs
@@ -25,6 +25,7 @@ namespace DotNetOpenAuth.Messaging {
public MessagePartAttribute() {
this.AllowEmpty = true;
this.MinVersionValue = new Version(0, 0);
+ this.MaxVersionValue = new Version(int.MaxValue, 0);
}
/// <summary>
@@ -34,7 +35,8 @@ namespace DotNetOpenAuth.Messaging {
/// A special name to give the value of this member in the serialized message.
/// When null or empty, the name of the member will be used in the serialized message.
/// </param>
- public MessagePartAttribute(string name) : this() {
+ public MessagePartAttribute(string name)
+ : this() {
this.Name = name;
}
@@ -63,7 +65,8 @@ namespace DotNetOpenAuth.Messaging {
public bool AllowEmpty { get; set; }
/// <summary>
- /// Gets or sets a custom encoder to use to translate the applied member to and from a string.
+ /// Gets or sets an IMessagePartEncoder custom encoder to use
+ /// to translate the applied member to and from a string.
/// </summary>
public Type Encoder { get; set; }
@@ -78,10 +81,35 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
+ /// Gets or sets the maximum version of the protocol this attribute applies to.
+ /// </summary>
+ /// <value>Defaults to int.MaxValue for the major version number.</value>
+ /// <remarks>
+ /// Specifying <see cref="MinVersion"/> on another attribute on the same member
+ /// automatically turns this attribute off. This property should only be set when
+ /// a property is totally dropped from a newer version of the protocol.
+ /// </remarks>
+ public string MaxVersion {
+ get { return this.MaxVersionValue.ToString(); }
+ set { this.MaxVersionValue = new Version(value); }
+ }
+
+ /// <summary>
/// Gets or sets the minimum version of the protocol this attribute applies to
/// and overrides any attributes with lower values for this property.
/// </summary>
/// <value>Defaults to 0.0.</value>
internal Version MinVersionValue { get; set; }
+
+ /// <summary>
+ /// Gets or sets the maximum version of the protocol this attribute applies to.
+ /// </summary>
+ /// <value>Defaults to int.MaxValue for the major version number.</value>
+ /// <remarks>
+ /// Specifying <see cref="MinVersion"/> on another attribute on the same member
+ /// automatically turns this attribute off. This property should only be set when
+ /// a property is totally dropped from a newer version of the protocol.
+ /// </remarks>
+ internal Version MaxVersionValue { get; set; }
}
}
diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs
index 94812d8..a363373 100644
--- a/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs
+++ b/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs
@@ -100,6 +100,7 @@ namespace DotNetOpenAuth.Messaging.Reflection {
(from a in member.GetCustomAttributes(typeof(MessagePartAttribute), true).OfType<MessagePartAttribute>()
orderby a.MinVersionValue descending
where a.MinVersionValue <= this.messageTypeAndVersion.Version
+ where a.MaxVersionValue >= this.messageTypeAndVersion.Version
select a).FirstOrDefault();
if (partAttribute != null) {
MessagePart part = new MessagePart(member, partAttribute);
diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/ITamperResistantOpenIdMessage.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/ITamperResistantOpenIdMessage.cs
index 3c0ecad..533e818 100644
--- a/src/DotNetOpenAuth/OpenId/ChannelElements/ITamperResistantOpenIdMessage.cs
+++ b/src/DotNetOpenAuth/OpenId/ChannelElements/ITamperResistantOpenIdMessage.cs
@@ -10,17 +10,25 @@ namespace DotNetOpenAuth.OpenId.ChannelElements {
using System.Linq;
using System.Text;
using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
/// <summary>
/// An interface that OAuth messages implement to support signing.
/// </summary>
- internal interface ITamperResistantOpenIdMessage : ITamperResistantProtocolMessage {
+ internal interface ITamperResistantOpenIdMessage : ITamperResistantProtocolMessage, IReplayProtectedProtocolMessage {
/// <summary>
/// Gets or sets the association handle used to sign the message.
/// </summary>
+ /// <value>The handle for the association that was used to sign this assertion. </value>
string AssociationHandle { get; set; }
/// <summary>
+ /// Gets or sets the association handle that the Provider wants the Relying Party to not use any more.
+ /// </summary>
+ /// <value>If the Relying Party sent an invalid association handle with the request, it SHOULD be included here.</value>
+ string InvalidateHandle { get; set; }
+
+ /// <summary>
/// Gets or sets the signed parameter order.
/// </summary>
/// <value>Comma-separated list of signed fields.</value>
@@ -31,6 +39,6 @@ namespace DotNetOpenAuth.OpenId.ChannelElements {
/// and if present in the response, "claimed_id" and "identity".
/// Additional keys MAY be signed as part of the message. See Generating Signatures.
/// </remarks>
- string SignedParameterOrder { get; set; }
+ string SignedParameterOrder { get; set; } // TODO: make sure we have a unit test to verify that an incoming message with fewer signed fields than required will be rejected.
}
}
diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/OriginalStringUriEncoder.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/OriginalStringUriEncoder.cs
new file mode 100644
index 0000000..75b01e1
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/ChannelElements/OriginalStringUriEncoder.cs
@@ -0,0 +1,47 @@
+//-----------------------------------------------------------------------
+// <copyright file="OriginalStringUriEncoder.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging.Reflection;
+
+ /// <summary>
+ /// A Uri encoder that serializes using <see cref="Uri.OriginalString"/>
+ /// rather than the standard <see cref="Uri.AbsoluteUri"/>.
+ /// </summary>
+ internal class OriginalStringUriEncoder : IMessagePartEncoder {
+ #region IMessagePartEncoder Members
+
+ /// <summary>
+ /// Encodes the specified value.
+ /// </summary>
+ /// <param name="value">The value. Guaranteed to never be null.</param>
+ /// <returns>
+ /// The <paramref name="value"/> in string form, ready for message transport.
+ /// </returns>
+ public string Encode(object value) {
+ Uri uriValue = (Uri)value;
+ return uriValue != null ? uriValue.OriginalString : null;
+ }
+
+ /// <summary>
+ /// Decodes the specified value.
+ /// </summary>
+ /// <param name="value">The string value carried by the transport. Guaranteed to never be null, although it may be empty.</param>
+ /// <returns>
+ /// The deserialized form of the given string.
+ /// </returns>
+ /// <exception cref="FormatException">Thrown when the string value given cannot be decoded into the required object type.</exception>
+ public object Decode(string value) {
+ return value != null ? new Uri(value) : null;
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs b/src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs
index 621054f..bd68111 100644
--- a/src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs
+++ b/src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs
@@ -24,7 +24,7 @@ namespace DotNetOpenAuth.OpenId.Messages {
/// <param name="version">The OpenID version this message must comply with.</param>
/// <param name="providerEndpoint">The OpenID Provider endpoint.</param>
protected AssociateRequest(Version version, Uri providerEndpoint)
- : base(version, providerEndpoint, "associate", MessageTransport.Direct) {
+ : base(version, providerEndpoint, GetProtocolConstant(version, p => p.Args.Mode.associate), MessageTransport.Direct) {
}
/// <summary>
diff --git a/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationRequest.cs
new file mode 100644
index 0000000..2a62fc1
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationRequest.cs
@@ -0,0 +1,32 @@
+//-----------------------------------------------------------------------
+// <copyright file="CheckAuthenticationRequest.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+
+ /// <summary>
+ /// A message a Relying Party sends to a Provider to confirm the validity
+ /// of a positive assertion that was signed by a Provider-only secret.
+ /// </summary>
+ /// <remarks>
+ /// The significant payload of this message depends entirely upon the
+ /// assertion message, and therefore is all in the
+ /// <see cref="DotNetOpenAuth.Messaging.IProtocolMessage.ExtraData"/> property bag.
+ /// </remarks>
+ internal class CheckAuthenticationRequest : RequestBase {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CheckAuthenticationRequest"/> class.
+ /// </summary>
+ /// <param name="version">The OpenID version this message must comply with.</param>
+ /// <param name="providerEndpoint">The OpenID Provider endpoint.</param>
+ internal CheckAuthenticationRequest(Version version, Uri providerEndpoint)
+ : base(version, providerEndpoint, GetProtocolConstant(version, p => p.Args.Mode.check_authentication), DotNetOpenAuth.Messaging.MessageTransport.Direct) {
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationResponse.cs
new file mode 100644
index 0000000..d66d0a9
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationResponse.cs
@@ -0,0 +1,49 @@
+//-----------------------------------------------------------------------
+// <copyright file="CheckAuthenticationResponse.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// The message sent from the Provider to the Relying Party to confirm/deny
+ /// the validity of an assertion that was signed by a private Provider secret.
+ /// </summary>
+ internal class CheckAuthenticationResponse : DirectResponseBase {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CheckAuthenticationResponse"/> class.
+ /// </summary>
+ /// <param name="request">The request that this message is responding to.</param>
+ internal CheckAuthenticationResponse(CheckAuthenticationRequest request)
+ : base(request) {
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the signature of the verification request is valid.
+ /// </summary>
+ [MessagePart("is_valid", IsRequired = true)]
+ internal bool IsValid { get; set; }
+
+ /// <summary>
+ /// Gets or sets the handle the relying party should invalidate if <see cref="IsValid"/> is true.
+ /// </summary>
+ /// <value>The "invalidate_handle" value sent in the verification request, if the OP confirms it is invalid.</value>
+ /// <remarks>
+ /// <para>If present in a verification response with "is_valid" set to "true",
+ /// the Relying Party SHOULD remove the corresponding association from
+ /// its store and SHOULD NOT send further authentication requests with
+ /// this handle.</para>
+ /// <para>This two-step process for invalidating associations is necessary
+ /// to prevent an attacker from invalidating an association at will by
+ /// adding "invalidate_handle" parameters to an authentication response.</para>
+ /// </remarks>
+ [MessagePart("invalidate_handle", IsRequired = false, AllowEmpty = false)]
+ internal string InvalidateHandle { get; set; }
+ }
+}
diff --git a/src/DotNetOpenAuth/OpenId/Messages/CheckIdRequest.cs b/src/DotNetOpenAuth/OpenId/Messages/CheckIdRequest.cs
index 46a32f6..bdc3f48 100644
--- a/src/DotNetOpenAuth/OpenId/Messages/CheckIdRequest.cs
+++ b/src/DotNetOpenAuth/OpenId/Messages/CheckIdRequest.cs
@@ -34,6 +34,15 @@ namespace DotNetOpenAuth.OpenId.Messages {
}
/// <summary>
+ /// Gets a value indicating whether the Provider is allowed to interact with the user
+ /// as part of authentication.
+ /// </summary>
+ /// <value><c>true</c> if using OpenID immediate mode; otherwise, <c>false</c>.</value>
+ internal bool Immediate {
+ get { return String.Equals(this.Mode, Protocol.Args.Mode.checkid_immediate, StringComparison.Ordinal); }
+ }
+
+ /// <summary>
/// Gets or sets the Claimed Identifier.
/// </summary>
/// <remarks>
@@ -123,6 +132,7 @@ namespace DotNetOpenAuth.OpenId.Messages {
base.EnsureValidMessage();
if (this.ProtocolVersion.Major >= 2) {
+ // Ensure that claimed_id and identity are either both present or both absent.
ErrorUtilities.VerifyProtocol((this.ClaimedIdentifier == null) == (this.LocalIdentifier == null), OpenIdStrings.ClaimedIdAndLocalIdMustBothPresentOrAbsent);
}
diff --git a/src/DotNetOpenAuth/OpenId/Messages/IndirectErrorResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/IndirectErrorResponse.cs
index 4b038b5..ffc73f8 100644
--- a/src/DotNetOpenAuth/OpenId/Messages/IndirectErrorResponse.cs
+++ b/src/DotNetOpenAuth/OpenId/Messages/IndirectErrorResponse.cs
@@ -17,14 +17,13 @@ namespace DotNetOpenAuth.OpenId.Messages {
/// <remarks>
/// This class satisfies OpenID 2.0 section 5.2.3.
/// </remarks>
- internal class IndirectErrorResponse : RequestBase {
+ internal class IndirectErrorResponse : IndirectResponseBase {
/// <summary>
/// Initializes a new instance of the <see cref="IndirectErrorResponse"/> class.
/// </summary>
- /// <param name="version">The OpenID version this message must comply with.</param>
- /// <param name="relyingPartyReturnTo">The value of the Relying Party's openid.return_to argument.</param>
- internal IndirectErrorResponse(Version version, Uri relyingPartyReturnTo)
- : base(version, relyingPartyReturnTo, "error", MessageTransport.Indirect) {
+ /// <param name="request">The request that resulted in this error on the Provider.</param>
+ internal IndirectErrorResponse(CheckIdRequest request)
+ : base(request, "error") {
}
/// <summary>
diff --git a/src/DotNetOpenAuth/OpenId/Messages/IndirectResponseBase.cs b/src/DotNetOpenAuth/OpenId/Messages/IndirectResponseBase.cs
new file mode 100644
index 0000000..bcda594
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/Messages/IndirectResponseBase.cs
@@ -0,0 +1,76 @@
+//-----------------------------------------------------------------------
+// <copyright file="IndirectResponseBase.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// A common base class from which indirect response messages should derive.
+ /// </summary>
+ internal class IndirectResponseBase : RequestBase {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="IndirectResponseBase"/> class.
+ /// </summary>
+ /// <param name="request">The request that caused this response message to be constructed.</param>
+ /// <param name="mode">The value of the openid.mode parameter.</param>
+ protected IndirectResponseBase(CheckIdRequest request, string mode)
+ : base(GetVersion(request), GetReturnTo(request), mode, MessageTransport.Indirect) {
+ ErrorUtilities.VerifyArgumentNotNull(request, "request");
+
+ this.OriginatingRequest = request;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="IndirectResponseBase"/> class
+ /// for unsolicited assertion scenarios.
+ /// </summary>
+ /// <param name="version">The OpenID version supported at the Relying Party.</param>
+ /// <param name="relyingPartyReturnTo">
+ /// The URI at which the Relying Party receives OpenID indirect messages.
+ /// </param>
+ /// <param name="mode">The value to use for the openid.mode parameter.</param>
+ protected IndirectResponseBase(Version version, Uri relyingPartyReturnTo, string mode)
+ : base(version, relyingPartyReturnTo, mode, MessageTransport.Indirect) {
+ }
+
+ /// <summary>
+ /// Gets the originating request message, if applicable.
+ /// </summary>
+ protected CheckIdRequest OriginatingRequest { get; private set; }
+
+ /// <summary>
+ /// Gets the <see cref="IProtocolMessage.ProtocolVersion"/> property of a message.
+ /// </summary>
+ /// <param name="message">The message to fetch the protocol version from.</param>
+ /// <returns>The value of the <see cref="IProtocolMessage.ProtocolVersion"/> property.</returns>
+ /// <remarks>
+ /// This method can be used by a constructor to throw an <see cref="ArgumentNullException"/>
+ /// instead of a <see cref="NullReferenceException"/>.
+ /// </remarks>
+ protected static Version GetVersion(IProtocolMessage message) {
+ ErrorUtilities.VerifyArgumentNotNull(message, "message");
+ return message.ProtocolVersion;
+ }
+
+ /// <summary>
+ /// Gets the <see cref="CheckIdRequest.ReturnTo"/> property of a message.
+ /// </summary>
+ /// <param name="message">The message to fetch the ReturnTo from.</param>
+ /// <returns>The value of the <see cref="CheckIdRequest.ReturnTo"/> property.</returns>
+ /// <remarks>
+ /// This method can be used by a constructor to throw an <see cref="ArgumentNullException"/>
+ /// instead of a <see cref="NullReferenceException"/>.
+ /// </remarks>
+ private static Uri GetReturnTo(CheckIdRequest message) {
+ ErrorUtilities.VerifyArgumentNotNull(message, "message");
+ return message.ReturnTo;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/OpenId/Messages/NegativeAssertionResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/NegativeAssertionResponse.cs
new file mode 100644
index 0000000..b0543e9
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/Messages/NegativeAssertionResponse.cs
@@ -0,0 +1,70 @@
+//-----------------------------------------------------------------------
+// <copyright file="NegativeAssertionResponse.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// The message OpenID Providers send back to Relying Parties to refuse
+ /// to assert the identity of a user.
+ /// </summary>
+ internal class NegativeAssertionResponse : IndirectResponseBase {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="NegativeAssertionResponse"/> class.
+ /// </summary>
+ /// <param name="request">The request that the relying party sent.</param>
+ internal NegativeAssertionResponse(CheckIdRequest request)
+ : base(request, GetMode(request)) {
+ }
+
+ /// <summary>
+ /// Gets or sets the URL the relying party can use to upgrade their authentication
+ /// request from an immediate to a setup message.
+ /// </summary>
+ /// <value>URL to redirect User-Agent to so the End User can do whatever's necessary to fulfill the assertion.</value>
+ /// <remarks>
+ /// This part is only included
+ /// </remarks>
+ [MessagePart("openid.user_setup_url", AllowEmpty = false, IsRequired = false, MaxVersion = "1.1")]
+ internal Uri UserSetupUrl { get; set; }
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ /// <remarks>
+ /// <para>Some messages have required fields, or combinations of fields that must relate to each other
+ /// in specialized ways. After deserializing a message, this method checks the state of the
+ /// message to see if it conforms to the protocol.</para>
+ /// <para>Note that this property should <i>not</i> check signatures or perform any state checks
+ /// outside this scope of this particular message.</para>
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception>
+ public override void EnsureValidMessage() {
+ base.EnsureValidMessage();
+
+ if (OriginatingRequest.Immediate && Protocol.Version.Major < 2) {
+ ErrorUtilities.VerifyProtocol(this.UserSetupUrl != null, OpenIdStrings.UserSetupUrlRequiredInImmediateNegativeResponse);
+ }
+ }
+
+ /// <summary>
+ /// Gets the value for the openid.mode that is appropriate for this response.
+ /// </summary>
+ /// <param name="request">The request that we're responding to.</param>
+ /// <returns>The value of the openid.mode parameter to use.</returns>
+ private static string GetMode(CheckIdRequest request) {
+ ErrorUtilities.VerifyArgumentNotNull(request, "request");
+
+ Protocol protocol = Protocol.Lookup(request.ProtocolVersion);
+ return request.Immediate ? protocol.Args.Mode.setup_needed : protocol.Args.Mode.cancel;
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/OpenId/Messages/PositiveAssertionResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/PositiveAssertionResponse.cs
new file mode 100644
index 0000000..4f45cee
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/Messages/PositiveAssertionResponse.cs
@@ -0,0 +1,216 @@
+//-----------------------------------------------------------------------
+// <copyright file="PositiveAssertionResponse.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Globalization;
+ using System.Linq;
+ using System.Net.Security;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
+ using DotNetOpenAuth.OpenId.ChannelElements;
+
+ /// <summary>
+ /// An identity assertion from a Provider to a Relying Party, stating that the
+ /// user operating the user agent is in fact some specific user known to the Provider.
+ /// </summary>
+ [DebuggerDisplay("OpenID {ProtocolVersion} {Mode} {LocalIdentifier}")]
+ internal class PositiveAssertionResponse : IndirectResponseBase, ITamperResistantOpenIdMessage {
+ /// <summary>
+ /// The allowed date/time formats for the response_nonce parameter.
+ /// </summary>
+ /// <remarks>
+ /// This array of formats is not yet a complete list.
+ /// </remarks>
+ private static readonly string[] PermissibleDateTimeFormats = { "yyyy-MM-ddTHH:mm:ssZ" };
+
+ /// <summary>
+ /// Backing field for the <see cref="IExpiringProtocolMessage.UtcCreationDate"/> property.
+ /// </summary>
+ private DateTime creationDateUtc;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PositiveAssertionResponse"/> class.
+ /// </summary>
+ /// <param name="request">
+ /// The authentication request that caused this assertion to be generated.
+ /// </param>
+ internal PositiveAssertionResponse(CheckIdRequest request)
+ : base(request, Protocol.Lookup(GetVersion(request)).Args.Mode.id_res) {
+ ErrorUtilities.VerifyArgumentNotNull(request, "request");
+
+ this.ReturnTo = request.ReturnTo;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PositiveAssertionResponse"/> class
+ /// for unsolicited assertions.
+ /// </summary>
+ /// <param name="version">The OpenID version to use.</param>
+ /// <param name="relyingPartyReturnTo">The return_to URL of the Relying Party.
+ /// This value will commonly be from <see cref="CheckIdRequest.ReturnTo"/>,
+ /// but for unsolicited assertions may come from the Provider performing RP discovery
+ /// to find the appropriate return_to URL to use.</param>
+ internal PositiveAssertionResponse(Version version, Uri relyingPartyReturnTo)
+ : base(version, relyingPartyReturnTo, Protocol.Lookup(version).Args.Mode.id_res) {
+ }
+
+ /// <summary>
+ /// Gets or sets the message signature.
+ /// </summary>
+ /// <value>Base 64 encoded signature calculated as specified in Section 6 (Generating Signatures).</value>
+ [MessagePart("openid.sig", IsRequired = true, AllowEmpty = false)]
+ string ITamperResistantProtocolMessage.Signature { get; set; }
+
+ /// <summary>
+ /// Gets or sets the signed parameter order.
+ /// </summary>
+ /// <value>Comma-separated list of signed fields.</value>
+ /// <example>"op_endpoint,identity,claimed_id,return_to,assoc_handle,response_nonce"</example>
+ /// <remarks>
+ /// This entry consists of the fields without the "openid." prefix that the signature covers.
+ /// This list MUST contain at least "op_endpoint", "return_to" "response_nonce" and "assoc_handle",
+ /// and if present in the response, "claimed_id" and "identity".
+ /// Additional keys MAY be signed as part of the message. See Generating Signatures.
+ /// </remarks>
+ [MessagePart("openid.signed", IsRequired = true, AllowEmpty = false)]
+ string ITamperResistantOpenIdMessage.SignedParameterOrder { get; set; }
+
+ /// <summary>
+ /// Gets or sets the association handle used to sign the message.
+ /// </summary>
+ /// <value>The handle for the association that was used to sign this assertion. </value>
+ [MessagePart("openid.assoc_handle", IsRequired = true, AllowEmpty = false, RequiredProtection = ProtectionLevel.Sign)]
+ string ITamperResistantOpenIdMessage.AssociationHandle { get; set; }
+
+ /// <summary>
+ /// Gets or sets the nonce that will protect the message from replay attacks.
+ /// </summary>
+ string IReplayProtectedProtocolMessage.Nonce { get; set; }
+
+ /// <summary>
+ /// Gets or sets the UTC date/time the message was originally sent onto the network.
+ /// </summary>
+ /// <remarks>
+ /// The property setter should ensure a UTC date/time,
+ /// and throw an exception if this is not possible.
+ /// </remarks>
+ /// <exception cref="ArgumentException">
+ /// Thrown when a DateTime that cannot be converted to UTC is set.
+ /// </exception>
+ DateTime IExpiringProtocolMessage.UtcCreationDate {
+ get { return this.creationDateUtc; }
+ set { this.creationDateUtc = value.ToUniversalTime(); }
+ }
+
+ /// <summary>
+ /// Gets or sets the association handle that the Provider wants the Relying Party to not use any more.
+ /// </summary>
+ /// <value>If the Relying Party sent an invalid association handle with the request, it SHOULD be included here.</value>
+ [MessagePart("openid.invalidate_handle", IsRequired = false, AllowEmpty = false)]
+ string ITamperResistantOpenIdMessage.InvalidateHandle { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Provider Endpoint URI.
+ /// </summary>
+ [MessagePart("openid.op_endpoint", IsRequired = true, AllowEmpty = false, RequiredProtection = ProtectionLevel.Sign, MinVersion = "2.0")]
+ internal Uri ProviderEndpoint { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Claimed Identifier.
+ /// </summary>
+ /// <remarks>
+ /// <para>"openid.claimed_id" and "openid.identity" SHALL be either both present or both absent.
+ /// If neither value is present, the assertion is not about an identifier,
+ /// and will contain other information in its payload, using extensions (Extensions). </para>
+ /// </remarks>
+ [MessagePart("openid.claimed_id", IsRequired = false, AllowEmpty = false, RequiredProtection = ProtectionLevel.Sign, MinVersion = "2.0")]
+ internal Identifier ClaimedIdentifier { get; set; }
+
+ /// <summary>
+ /// Gets or sets the OP Local Identifier.
+ /// </summary>
+ /// <value>The OP-Local Identifier. </value>
+ /// <remarks>
+ /// <para>OpenID Providers MAY assist the end user in selecting the Claimed
+ /// and OP-Local Identifiers about which the assertion is made.
+ /// The openid.identity field MAY be omitted if an extension is in use that
+ /// makes the response meaningful without it (see openid.claimed_id above). </para>
+ /// </remarks>
+ [MessagePart("openid.identity", IsRequired = false, AllowEmpty = false, RequiredProtection = ProtectionLevel.Sign)]
+ internal Identifier LocalIdentifier { get; set; }
+
+ /// <summary>
+ /// Gets or sets the return_to parameter as the relying party provided
+ /// it in <see cref="CheckIdRequest.ReturnTo"/>.
+ /// </summary>
+ /// <value>Verbatim copy of the return_to URL parameter sent in the
+ /// request, before the Provider modified it. </value>
+ [MessagePart("openid.return_to", IsRequired = true, AllowEmpty = false, RequiredProtection = ProtectionLevel.Sign, Encoder = typeof(OriginalStringUriEncoder))]
+ internal Uri ReturnTo { get; set; }
+
+ /// <summary>
+ /// Gets or sets the nonce that will protect the message from replay attacks.
+ /// </summary>
+ /// <value>
+ /// <para>A string 255 characters or less in length, that MUST be unique to
+ /// this particular successful authentication response. The nonce MUST start
+ /// with the current time on the server, and MAY contain additional ASCII
+ /// characters in the range 33-126 inclusive (printable non-whitespace characters),
+ /// as necessary to make each response unique. The date and time MUST be
+ /// formatted as specified in section 5.6 of [RFC3339]
+ /// (Klyne, G. and C. Newman, “Date and Time on the Internet: Timestamps,” .),
+ /// with the following restrictions:</para>
+ /// <list type="bullet">
+ /// <item>All times must be in the UTC timezone, indicated with a "Z".</item>
+ /// <item>No fractional seconds are allowed</item>
+ /// </list>
+ /// </value>
+ /// <example>2005-05-15T17:11:51ZUNIQUE</example>
+ [MessagePart("openid.response_nonce", IsRequired = true, AllowEmpty = false, RequiredProtection = ProtectionLevel.Sign, MinVersion = "2.0")]
+ private string ResponseNonce {
+ get {
+ string uniqueFragment = ((IReplayProtectedProtocolMessage)this).Nonce;
+ return this.creationDateUtc.ToString(PermissibleDateTimeFormats[0], CultureInfo.InvariantCulture) + uniqueFragment;
+ }
+
+ set {
+ if (value == null) {
+ ((IReplayProtectedProtocolMessage)this).Nonce = null;
+ } else {
+ int indexOfZ = value.IndexOf("Z", StringComparison.Ordinal);
+ ErrorUtilities.VerifyProtocol(indexOfZ >= 0, MessagingStrings.UnexpectedMessagePartValue, Protocol.openid.response_nonce, value);
+ this.creationDateUtc = DateTime.Parse(value.Substring(0, indexOfZ + 1), CultureInfo.InvariantCulture);
+ ((IReplayProtectedProtocolMessage)this).Nonce = value.Substring(indexOfZ + 1);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ /// <remarks>
+ /// <para>Some messages have required fields, or combinations of fields that must relate to each other
+ /// in specialized ways. After deserializing a message, this method checks the state of the
+ /// message to see if it conforms to the protocol.</para>
+ /// <para>Note that this property should <i>not</i> check signatures or perform any state checks
+ /// outside this scope of this particular message.</para>
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception>
+ public override void EnsureValidMessage() {
+ base.EnsureValidMessage();
+
+ if (this.ProtocolVersion.Major >= 2) {
+ // Ensure that claimed_id and identity are either both present or both absent.
+ ErrorUtilities.VerifyProtocol((this.ClaimedIdentifier == null) == (this.LocalIdentifier == null), OpenIdStrings.ClaimedIdAndLocalIdMustBothPresentOrAbsent);
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/OpenId/Messages/RequestBase.cs b/src/DotNetOpenAuth/OpenId/Messages/RequestBase.cs
index bc00ac0..28e53b7 100644
--- a/src/DotNetOpenAuth/OpenId/Messages/RequestBase.cs
+++ b/src/DotNetOpenAuth/OpenId/Messages/RequestBase.cs
@@ -169,5 +169,20 @@ namespace DotNetOpenAuth.OpenId.Messages {
internal void SetAsIncoming() {
this.incoming = true;
}
+
+ /// <summary>
+ /// Gets some string from a given version of the OpenID protocol.
+ /// </summary>
+ /// <param name="protocolVersion">The protocol version to use for lookup.</param>
+ /// <param name="mode">A function that can retrieve the desired protocol constant.</param>
+ /// <returns>The value of the constant.</returns>
+ /// <remarks>
+ /// This method can be used by a constructor to throw an <see cref="ArgumentNullException"/>
+ /// instead of a <see cref="NullReferenceException"/>.
+ /// </remarks>
+ protected static string GetProtocolConstant(Version protocolVersion, Func<Protocol, string> mode) {
+ ErrorUtilities.VerifyArgumentNotNull(protocolVersion, "protocolVersion");
+ return mode(Protocol.Lookup(protocolVersion));
+ }
}
}
diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs
index d51ec37..a3d6255 100644
--- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs
+++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs
@@ -232,6 +232,15 @@ namespace DotNetOpenAuth.OpenId {
}
/// <summary>
+ /// Looks up a localized string similar to The openid.user_setup_url parameter is required when sending negative assertion messages in response to immediate mode requests..
+ /// </summary>
+ internal static string UserSetupUrlRequiredInImmediateNegativeResponse {
+ get {
+ return ResourceManager.GetString("UserSetupUrlRequiredInImmediateNegativeResponse", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to XRI resolution failed..
/// </summary>
internal static string XriResolutionFailed {
diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx
index fd3d799..4ba06e2 100644
--- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx
+++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx
@@ -174,6 +174,9 @@
<data name="ReturnToNotUnderRealm" xml:space="preserve">
<value>return_to '{0}' not under realm '{1}'.</value>
</data>
+ <data name="UserSetupUrlRequiredInImmediateNegativeResponse" xml:space="preserve">
+ <value>The openid.user_setup_url parameter is required when sending negative assertion messages in response to immediate mode requests.</value>
+ </data>
<data name="XriResolutionFailed" xml:space="preserve">
<value>XRI resolution failed.</value>
</data>