OpenID error direct response messages are now sent with HTTP status codes of 400.
[dotnetoauth.git] / src / DotNetOpenAuth / OpenId / ChannelElements / OpenIdChannel.cs
blobd38012004efd9f7301d6990c08f77b7b3cd81bdb
1 //-----------------------------------------------------------------------
2 // <copyright file="OpenIdChannel.cs" company="Andrew Arnott">
3 // Copyright (c) Andrew Arnott. All rights reserved.
4 // </copyright>
5 //-----------------------------------------------------------------------
7 namespace DotNetOpenAuth.OpenId.ChannelElements {
8 using System;
9 using System.Collections.Generic;
10 using System.Globalization;
11 using System.IO;
12 using System.Linq;
13 using System.Net;
14 using System.Text;
15 using DotNetOpenAuth.Messaging;
16 using DotNetOpenAuth.Messaging.Bindings;
17 using DotNetOpenAuth.OpenId.Extensions;
18 using DotNetOpenAuth.OpenId.Messages;
19 using DotNetOpenAuth.OpenId.Provider;
20 using DotNetOpenAuth.OpenId.RelyingParty;
22 /// <summary>
23 /// A channel that knows how to send and receive OpenID messages.
24 /// </summary>
25 internal class OpenIdChannel : Channel {
26 /// <summary>
27 /// The HTTP Content-Type to use in Key-Value Form responses.
28 /// </summary>
29 /// <remarks>
30 /// OpenID 2.0 section 5.1.2 says this SHOULD be text/plain. But this value
31 /// does not prevent free hosters like GoDaddy from tacking on their ads
32 /// to the end of the direct response, corrupting the data. So we deviate
33 /// from the spec a bit here to improve the story for free Providers.
34 /// </remarks>
35 private const string KeyValueFormContentType = "application/x-openid-kvf";
37 /// <summary>
38 /// The encoder that understands how to read and write Key-Value Form.
39 /// </summary>
40 private KeyValueFormEncoding keyValueForm = new KeyValueFormEncoding();
42 /// <summary>
43 /// Initializes a new instance of the <see cref="OpenIdChannel"/> class
44 /// for use by a Relying Party.
45 /// </summary>
46 /// <param name="associationStore">The association store to use.</param>
47 /// <param name="nonceStore">The nonce store to use.</param>
48 /// <param name="secretStore">The secret store to use.</param>
49 /// <param name="securitySettings">The security settings to apply.</param>
50 internal OpenIdChannel(IAssociationStore<Uri> associationStore, INonceStore nonceStore, IPrivateSecretStore secretStore, RelyingPartySecuritySettings securitySettings)
51 : this(associationStore, nonceStore, secretStore, new OpenIdMessageFactory(), securitySettings) {
54 /// <summary>
55 /// Initializes a new instance of the <see cref="OpenIdChannel"/> class
56 /// for use by a Provider.
57 /// </summary>
58 /// <param name="associationStore">The association store to use.</param>
59 /// <param name="nonceStore">The nonce store to use.</param>
60 /// <param name="securitySettings">The security settings.</param>
61 internal OpenIdChannel(IAssociationStore<AssociationRelyingPartyType> associationStore, INonceStore nonceStore, ProviderSecuritySettings securitySettings)
62 : this(associationStore, nonceStore, new OpenIdMessageFactory(), securitySettings) {
65 /// <summary>
66 /// Initializes a new instance of the <see cref="OpenIdChannel"/> class
67 /// for use by a Relying Party.
68 /// </summary>
69 /// <param name="associationStore">The association store to use.</param>
70 /// <param name="nonceStore">The nonce store to use.</param>
71 /// <param name="secretStore">The secret store to use.</param>
72 /// <param name="messageTypeProvider">An object that knows how to distinguish the various OpenID message types for deserialization purposes.</param>
73 /// <param name="securitySettings">The security settings to apply.</param>
74 private OpenIdChannel(IAssociationStore<Uri> associationStore, INonceStore nonceStore, IPrivateSecretStore secretStore, IMessageFactory messageTypeProvider, RelyingPartySecuritySettings securitySettings) :
75 this(messageTypeProvider, InitializeBindingElements(new SigningBindingElement(associationStore), nonceStore, secretStore, securitySettings)) {
78 /// <summary>
79 /// Initializes a new instance of the <see cref="OpenIdChannel"/> class
80 /// for use by a Provider.
81 /// </summary>
82 /// <param name="associationStore">The association store to use.</param>
83 /// <param name="nonceStore">The nonce store to use.</param>
84 /// <param name="messageTypeProvider">An object that knows how to distinguish the various OpenID message types for deserialization purposes.</param>
85 /// <param name="securitySettings">The security settings.</param>
86 private OpenIdChannel(IAssociationStore<AssociationRelyingPartyType> associationStore, INonceStore nonceStore, IMessageFactory messageTypeProvider, ProviderSecuritySettings securitySettings) :
87 this(messageTypeProvider, InitializeBindingElements(new SigningBindingElement(associationStore, securitySettings), nonceStore, null, securitySettings)) {
90 /// <summary>
91 /// Initializes a new instance of the <see cref="OpenIdChannel"/> class.
92 /// </summary>
93 /// <param name="messageTypeProvider">A class prepared to analyze incoming messages and indicate what concrete
94 /// message types can deserialize from it.</param>
95 /// <param name="bindingElements">The binding elements to use in sending and receiving messages.</param>
96 private OpenIdChannel(IMessageFactory messageTypeProvider, IChannelBindingElement[] bindingElements)
97 : base(messageTypeProvider, bindingElements) {
98 // Customize the binding element order, since we play some tricks for higher
99 // security and backward compatibility with older OpenID versions.
100 var outgoingBindingElements = new List<IChannelBindingElement>(bindingElements);
101 var incomingBindingElements = new List<IChannelBindingElement>(bindingElements);
102 incomingBindingElements.Reverse();
104 // Customize the order of the incoming elements by moving the return_to elements in front.
105 var backwardCompatibility = incomingBindingElements.OfType<BackwardCompatibilityBindingElement>().SingleOrDefault();
106 var returnToSign = incomingBindingElements.OfType<ReturnToSignatureBindingElement>().SingleOrDefault();
107 if (backwardCompatibility != null && returnToSign != null) {
108 incomingBindingElements.MoveTo(0, returnToSign);
109 incomingBindingElements.MoveTo(1, backwardCompatibility);
112 CustomizeBindingElementOrder(outgoingBindingElements, incomingBindingElements);
114 // Change out the standard web request handler to reflect the standard
115 // OpenID pattern that outgoing web requests are to unknown and untrusted
116 // servers on the Internet.
117 this.WebRequestHandler = new UntrustedWebRequestHandler();
120 public override IDirectWebRequestHandler WebRequestHandler {
121 get {
122 // Unwrap the handler we were originally assigned.
123 var wrappedHandler = (Accept400ErrorDirectWebRequestHandlerWrapper) base.WebRequestHandler;
124 return wrappedHandler.WrappedHandler;
126 set {
127 if (value == null) {
128 base.WebRequestHandler = null;
131 // Wrap the handler with one that can injest HTTP 400 errors.
132 IDirectWebRequestHandler wrappedHandler;
133 IDirectSslWebRequestHandler sslHandler = value as IDirectSslWebRequestHandler;
134 if (sslHandler != null) {
135 wrappedHandler = new Accept400ErrorDirectSslWebRequestHandlerWrapper(sslHandler);
136 } else {
137 wrappedHandler = new Accept400ErrorDirectWebRequestHandlerWrapper(value);
140 base.WebRequestHandler = wrappedHandler;
144 /// <summary>
145 /// Gets the extension factory that can be used to register OpenID extensions.
146 /// </summary>
147 internal OpenIdExtensionFactory Extensions { get; private set; }
149 /// <summary>
150 /// Verifies the integrity and applicability of an incoming message.
151 /// </summary>
152 /// <param name="message">The message just received.</param>
153 /// <exception cref="ProtocolException">
154 /// Thrown when the message is somehow invalid, except for check_authentication messages.
155 /// This can be due to tampering, replay attack or expiration, among other things.
156 /// </exception>
157 protected override void VerifyMessageAfterReceiving(IProtocolMessage message) {
158 var checkAuthRequest = message as CheckAuthenticationRequest;
159 if (checkAuthRequest != null) {
160 IndirectSignedResponse originalResponse = new IndirectSignedResponse(checkAuthRequest);
161 try {
162 base.VerifyMessageAfterReceiving(originalResponse);
163 checkAuthRequest.IsValid = true;
164 } catch (ProtocolException) {
165 checkAuthRequest.IsValid = false;
167 } else {
168 base.VerifyMessageAfterReceiving(message);
171 // Convert an OpenID indirect error message, which we never expect
172 // between two good OpenID implementations, into an exception.
173 // We don't process DirectErrorResponse because associate negotiations
174 // commonly get a derivative of that message type and handle it.
175 var errorMessage = message as IndirectErrorResponse;
176 if (errorMessage != null) {
177 string exceptionMessage = string.Format(
178 CultureInfo.CurrentCulture,
179 OpenIdStrings.IndirectErrorFormattedMessage,
180 errorMessage.ErrorMessage,
181 errorMessage.Contact,
182 errorMessage.Reference);
183 throw new ProtocolException(exceptionMessage, message);
187 /// <summary>
188 /// Prepares an HTTP request that carries a given message.
189 /// </summary>
190 /// <param name="request">The message to send.</param>
191 /// <returns>
192 /// The <see cref="HttpWebRequest"/> prepared to send the request.
193 /// </returns>
194 protected override HttpWebRequest CreateHttpRequest(IDirectedProtocolMessage request) {
195 return this.InitializeRequestAsPost(request);
198 /// <summary>
199 /// Gets the protocol message that may be in the given HTTP response.
200 /// </summary>
201 /// <param name="response">The response that is anticipated to contain an protocol message.</param>
202 /// <returns>
203 /// The deserialized message parts, if found. Null otherwise.
204 /// </returns>
205 /// <exception cref="ProtocolException">Thrown when the response is not valid.</exception>
206 protected override IDictionary<string, string> ReadFromResponseInternal(DirectWebResponse response) {
207 if (response == null) {
208 throw new ArgumentNullException("response");
211 try {
212 return this.keyValueForm.GetDictionary(response.ResponseStream);
213 } catch (FormatException ex) {
214 throw ErrorUtilities.Wrap(ex, ex.Message);
218 /// <summary>
219 /// Queues a message for sending in the response stream where the fields
220 /// are sent in the response stream in querystring style.
221 /// </summary>
222 /// <param name="response">The message to send as a response.</param>
223 /// <returns>
224 /// The pending user agent redirect based message to be sent as an HttpResponse.
225 /// </returns>
226 /// <remarks>
227 /// This method implements spec V1.0 section 5.3.
228 /// </remarks>
229 protected override UserAgentResponse SendDirectMessageResponse(IProtocolMessage response) {
230 ErrorUtilities.VerifyArgumentNotNull(response, "response");
232 var serializer = MessageSerializer.Get(response.GetType());
233 var fields = serializer.Serialize(response);
234 byte[] keyValueEncoding = KeyValueFormEncoding.GetBytes(fields);
236 UserAgentResponse preparedResponse = new UserAgentResponse();
237 preparedResponse.Headers.Add(HttpResponseHeader.ContentType, KeyValueFormContentType);
238 preparedResponse.OriginalMessage = response;
239 preparedResponse.ResponseStream = new MemoryStream(keyValueEncoding);
241 IHttpDirectResponse httpMessage = response as IHttpDirectResponse;
242 if (httpMessage != null) {
243 preparedResponse.Status = httpMessage.HttpStatusCode;
246 return preparedResponse;
249 /// <summary>
250 /// Initializes the binding elements.
251 /// </summary>
252 /// <param name="signingElement">The signing element, previously constructed.</param>
253 /// <param name="nonceStore">The nonce store to use.</param>
254 /// <param name="secretStore">The secret store to use.</param>
255 /// <param name="securitySettings">The security settings to apply. Must be an instance of either <see cref="RelyingPartySecuritySettings"/> or <see cref="ProviderSecuritySettings"/>.</param>
256 /// <returns>
257 /// An array of binding elements which may be used to construct the channel.
258 /// </returns>
259 private static IChannelBindingElement[] InitializeBindingElements(SigningBindingElement signingElement, INonceStore nonceStore, IPrivateSecretStore secretStore, SecuritySettings securitySettings) {
260 ErrorUtilities.VerifyArgumentNotNull(signingElement, "signingElement");
261 ErrorUtilities.VerifyArgumentNotNull(securitySettings, "securitySettings");
263 RelyingPartySecuritySettings rpSecuritySettings = securitySettings as RelyingPartySecuritySettings;
264 ProviderSecuritySettings opSecuritySettings = securitySettings as ProviderSecuritySettings;
265 ErrorUtilities.VerifyInternal(rpSecuritySettings != null || opSecuritySettings != null, "Expected an RP or OP security settings instance.");
266 bool isRelyingPartyRole = rpSecuritySettings != null;
268 List<IChannelBindingElement> elements = new List<IChannelBindingElement>(7);
269 if (isRelyingPartyRole) {
270 elements.Add(new ExtensionsBindingElement(new OpenIdExtensionFactory(), rpSecuritySettings));
271 elements.Add(new BackwardCompatibilityBindingElement());
273 if (secretStore != null) {
274 secretStore.InitializeSecretIfUnset();
276 if (nonceStore != null) {
277 // There is no point in having a ReturnToNonceBindingElement without
278 // a ReturnToSignatureBindingElement because the nonce could be
279 // artificially changed without it.
280 elements.Add(new ReturnToNonceBindingElement(nonceStore));
283 // It is important that the return_to signing element comes last
284 // so that the nonce is included in the signature.
285 elements.Add(new ReturnToSignatureBindingElement(secretStore));
287 } else {
288 elements.Add(new ExtensionsBindingElement(new OpenIdExtensionFactory(), opSecuritySettings));
290 // Providers must always have a nonce store.
291 ErrorUtilities.VerifyArgumentNotNull(nonceStore, "nonceStore");
294 if (nonceStore != null) {
295 elements.Add(new StandardReplayProtectionBindingElement(nonceStore, true));
298 elements.Add(new StandardExpirationBindingElement());
299 elements.Add(signingElement);
301 return elements.ToArray();