1
//-----------------------------------------------------------------------
2 // <copyright file="OpenIdChannel.cs" company="Andrew Arnott">
3 // Copyright (c) Andrew Arnott. All rights reserved.
5 //-----------------------------------------------------------------------
7 namespace DotNetOpenAuth
.OpenId
.ChannelElements
{
9 using System
.Collections
.Generic
;
10 using System
.Globalization
;
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
;
23 /// A channel that knows how to send and receive OpenID messages.
25 internal class OpenIdChannel
: Channel
{
27 /// The HTTP Content-Type to use in Key-Value Form responses.
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.
35 private const string KeyValueFormContentType
= "application/x-openid-kvf";
38 /// The encoder that understands how to read and write Key-Value Form.
40 private KeyValueFormEncoding keyValueForm
= new KeyValueFormEncoding();
43 /// Initializes a new instance of the <see cref="OpenIdChannel"/> class
44 /// for use by a Relying Party.
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
) {
55 /// Initializes a new instance of the <see cref="OpenIdChannel"/> class
56 /// for use by a Provider.
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
) {
66 /// Initializes a new instance of the <see cref="OpenIdChannel"/> class
67 /// for use by a Relying Party.
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
)) {
79 /// Initializes a new instance of the <see cref="OpenIdChannel"/> class
80 /// for use by a Provider.
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
)) {
91 /// Initializes a new instance of the <see cref="OpenIdChannel"/> class.
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
{
122 // Unwrap the handler we were originally assigned.
123 var wrappedHandler
= (Accept400ErrorDirectWebRequestHandlerWrapper
) base.WebRequestHandler
;
124 return wrappedHandler
.WrappedHandler
;
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
);
137 wrappedHandler
= new Accept400ErrorDirectWebRequestHandlerWrapper(value);
140 base.WebRequestHandler
= wrappedHandler
;
145 /// Gets the extension factory that can be used to register OpenID extensions.
147 internal OpenIdExtensionFactory Extensions { get; private set; }
150 /// Verifies the integrity and applicability of an incoming message.
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.
157 protected override void VerifyMessageAfterReceiving(IProtocolMessage message
) {
158 var checkAuthRequest
= message
as CheckAuthenticationRequest
;
159 if (checkAuthRequest
!= null) {
160 IndirectSignedResponse originalResponse
= new IndirectSignedResponse(checkAuthRequest
);
162 base.VerifyMessageAfterReceiving(originalResponse
);
163 checkAuthRequest
.IsValid
= true;
164 } catch (ProtocolException
) {
165 checkAuthRequest
.IsValid
= false;
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
);
188 /// Prepares an HTTP request that carries a given message.
190 /// <param name="request">The message to send.</param>
192 /// The <see cref="HttpWebRequest"/> prepared to send the request.
194 protected override HttpWebRequest
CreateHttpRequest(IDirectedProtocolMessage request
) {
195 return this.InitializeRequestAsPost(request
);
199 /// Gets the protocol message that may be in the given HTTP response.
201 /// <param name="response">The response that is anticipated to contain an protocol message.</param>
203 /// The deserialized message parts, if found. Null otherwise.
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");
212 return this.keyValueForm
.GetDictionary(response
.ResponseStream
);
213 } catch (FormatException ex
) {
214 throw ErrorUtilities
.Wrap(ex
, ex
.Message
);
219 /// Queues a message for sending in the response stream where the fields
220 /// are sent in the response stream in querystring style.
222 /// <param name="response">The message to send as a response.</param>
224 /// The pending user agent redirect based message to be sent as an HttpResponse.
227 /// This method implements spec V1.0 section 5.3.
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
;
250 /// Initializes the binding elements.
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>
257 /// An array of binding elements which may be used to construct the channel.
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
));
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();