Shifted some of the maximum message age references.
[dotnetoauth.git] / src / DotNetOpenAuth / OpenId / RelyingParty / OpenIdRelyingParty.cs
blobc0687b5eb5cc2b7be5cefc93d219b3f674b0b703
1 //-----------------------------------------------------------------------
2 // <copyright file="OpenIdRelyingParty.cs" company="Andrew Arnott">
3 // Copyright (c) Andrew Arnott. All rights reserved.
4 // </copyright>
5 //-----------------------------------------------------------------------
7 namespace DotNetOpenAuth.OpenId.RelyingParty {
8 using System;
9 using System.Collections.Generic;
10 using System.Collections.Specialized;
11 using System.ComponentModel;
12 using System.Linq;
13 using System.Web;
14 using DotNetOpenAuth.Configuration;
15 using DotNetOpenAuth.Messaging;
16 using DotNetOpenAuth.Messaging.Bindings;
17 using DotNetOpenAuth.OpenId.ChannelElements;
18 using DotNetOpenAuth.OpenId.Messages;
20 /// <summary>
21 /// A delegate that decides whether a given OpenID Provider endpoint may be
22 /// considered for authenticating a user.
23 /// </summary>
24 /// <param name="endpoint">The endpoint for consideration.</param>
25 /// <returns>
26 /// <c>True</c> if the endpoint should be considered.
27 /// <c>False</c> to remove it from the pool of acceptable providers.
28 /// </returns>
29 public delegate bool EndpointSelector(IXrdsProviderEndpoint endpoint);
31 /// <summary>
32 /// Provides the programmatic facilities to act as an OpenId consumer.
33 /// </summary>
34 public sealed class OpenIdRelyingParty {
35 /// <summary>
36 /// The name of the key to use in the HttpApplication cache to store the
37 /// instance of <see cref="StandardRelyingPartyApplicationStore"/> to use.
38 /// </summary>
39 private const string ApplicationStoreKey = "DotNetOpenAuth.OpenId.RelyingParty.OpenIdRelyingParty.ApplicationStore";
41 /// <summary>
42 /// Backing field for the <see cref="SecuritySettings"/> property.
43 /// </summary>
44 private RelyingPartySecuritySettings securitySettings;
46 /// <summary>
47 /// Backing store for the <see cref="EndpointOrder"/> property.
48 /// </summary>
49 private Comparison<IXrdsProviderEndpoint> endpointOrder = DefaultEndpointOrder;
51 /// <summary>
52 /// Initializes a new instance of the <see cref="OpenIdRelyingParty"/> class.
53 /// </summary>
54 public OpenIdRelyingParty()
55 : this(DotNetOpenAuth.Configuration.RelyingPartySection.Configuration.ApplicationStore.CreateInstance(HttpApplicationStore)) {
58 /// <summary>
59 /// Initializes a new instance of the <see cref="OpenIdRelyingParty"/> class.
60 /// </summary>
61 /// <param name="applicationStore">The application store. If null, the relying party will always operate in "dumb mode".</param>
62 public OpenIdRelyingParty(IRelyingPartyApplicationStore applicationStore)
63 : this(applicationStore, applicationStore, applicationStore) {
66 /// <summary>
67 /// Initializes a new instance of the <see cref="OpenIdRelyingParty"/> class.
68 /// </summary>
69 /// <param name="associationStore">The association store. If null, the relying party will always operate in "dumb mode".</param>
70 /// <param name="nonceStore">The nonce store to use. If null, the relying party will always operate in "dumb mode".</param>
71 /// <param name="secretStore">The secret store to use. If null, the relying party will always operate in "dumb mode".</param>
72 private OpenIdRelyingParty(IAssociationStore<Uri> associationStore, INonceStore nonceStore, IPrivateSecretStore secretStore) {
73 // If we are a smart-mode RP (supporting associations), then we MUST also be
74 // capable of storing nonces to prevent replay attacks.
75 // If we're a dumb-mode RP, then 2.0 OPs are responsible for preventing replays.
76 ErrorUtilities.VerifyArgument(associationStore == null || nonceStore != null, OpenIdStrings.AssociationStoreRequiresNonceStore);
78 this.Channel = new OpenIdChannel(associationStore, nonceStore, secretStore);
79 this.AssociationStore = associationStore;
80 this.SecuritySettings = RelyingPartySection.Configuration.SecuritySettings.CreateSecuritySettings();
82 // Without a nonce store, we must rely on the Provider to protect against
83 // replay attacks. But only 2.0+ Providers can be expected to provide
84 // replay protection.
85 if (nonceStore == null) {
86 this.SecuritySettings.MinimumRequiredOpenIdVersion = ProtocolVersion.V20;
90 /// <summary>
91 /// Gets an XRDS sorting routine that uses the XRDS Service/@Priority
92 /// attribute to determine order.
93 /// </summary>
94 /// <remarks>
95 /// Endpoints lacking any priority value are sorted to the end of the list.
96 /// </remarks>
97 [EditorBrowsable(EditorBrowsableState.Advanced)]
98 public static Comparison<IXrdsProviderEndpoint> DefaultEndpointOrder {
99 get {
100 // Sort first by service type (OpenID 2.0, 1.1, 1.0),
101 // then by Service/@priority, then by Service/Uri/@priority
102 return (se1, se2) => {
103 int result = GetEndpointPrecedenceOrderByServiceType(se1).CompareTo(GetEndpointPrecedenceOrderByServiceType(se2));
104 if (result != 0) {
105 return result;
107 if (se1.ServicePriority.HasValue && se2.ServicePriority.HasValue) {
108 result = se1.ServicePriority.Value.CompareTo(se2.ServicePriority.Value);
109 if (result != 0) {
110 return result;
112 if (se1.UriPriority.HasValue && se2.UriPriority.HasValue) {
113 return se1.UriPriority.Value.CompareTo(se2.UriPriority.Value);
114 } else if (se1.UriPriority.HasValue) {
115 return -1;
116 } else if (se2.UriPriority.HasValue) {
117 return 1;
118 } else {
119 return 0;
121 } else {
122 if (se1.ServicePriority.HasValue) {
123 return -1;
124 } else if (se2.ServicePriority.HasValue) {
125 return 1;
126 } else {
127 // neither service defines a priority, so base ordering by uri priority.
128 if (se1.UriPriority.HasValue && se2.UriPriority.HasValue) {
129 return se1.UriPriority.Value.CompareTo(se2.UriPriority.Value);
130 } else if (se1.UriPriority.HasValue) {
131 return -1;
132 } else if (se2.UriPriority.HasValue) {
133 return 1;
134 } else {
135 return 0;
143 /// <summary>
144 /// Gets the standard state storage mechanism that uses ASP.NET's
145 /// HttpApplication state dictionary to store associations and nonces.
146 /// </summary>
147 [EditorBrowsable(EditorBrowsableState.Advanced)]
148 public static IRelyingPartyApplicationStore HttpApplicationStore {
149 get {
150 HttpContext context = HttpContext.Current;
151 ErrorUtilities.VerifyOperation(context != null, OpenIdStrings.StoreRequiredWhenNoHttpContextAvailable, typeof(IRelyingPartyApplicationStore).Name);
152 var store = (IRelyingPartyApplicationStore)context.Application[ApplicationStoreKey];
153 if (store == null) {
154 context.Application.Lock();
155 try {
156 if ((store = (IRelyingPartyApplicationStore)context.Application[ApplicationStoreKey]) == null) {
157 context.Application[ApplicationStoreKey] = store = new StandardRelyingPartyApplicationStore();
159 } finally {
160 context.Application.UnLock();
164 return store;
168 /// <summary>
169 /// Gets the channel to use for sending/receiving messages.
170 /// </summary>
171 public Channel Channel { get; internal set; }
173 /// <summary>
174 /// Gets the security settings used by this Relying Party.
175 /// </summary>
176 public RelyingPartySecuritySettings SecuritySettings {
177 get {
178 return this.securitySettings;
181 internal set {
182 if (value == null) {
183 throw new ArgumentNullException("value");
186 this.securitySettings = value;
190 /// <summary>
191 /// Gets or sets the optional Provider Endpoint filter to use.
192 /// </summary>
193 /// <remarks>
194 /// Provides a way to optionally filter the providers that may be used in authenticating a user.
195 /// If provided, the delegate should return true to accept an endpoint, and false to reject it.
196 /// If null, all identity providers will be accepted. This is the default.
197 /// </remarks>
198 [EditorBrowsable(EditorBrowsableState.Advanced)]
199 public EndpointSelector EndpointFilter { get; set; }
201 /// <summary>
202 /// Gets or sets the ordering routine that will determine which XRDS
203 /// Service element to try first
204 /// </summary>
205 /// <value>Default is <see cref="DefaultEndpointOrder"/>.</value>
206 /// <remarks>
207 /// This may never be null. To reset to default behavior this property
208 /// can be set to the value of <see cref="DefaultEndpointOrder"/>.
209 /// </remarks>
210 [EditorBrowsable(EditorBrowsableState.Advanced)]
211 public Comparison<IXrdsProviderEndpoint> EndpointOrder {
212 get {
213 return this.endpointOrder;
216 set {
217 ErrorUtilities.VerifyArgumentNotNull(value, "value");
218 this.endpointOrder = value;
222 /// <summary>
223 /// Gets the association store.
224 /// </summary>
225 internal IAssociationStore<Uri> AssociationStore { get; private set; }
227 /// <summary>
228 /// Gets a value indicating whether this Relying Party can sign its return_to
229 /// parameter in outgoing authentication requests.
230 /// </summary>
231 internal bool CanSignCallbackArguments {
232 get { return this.Channel.BindingElements.OfType<ReturnToSignatureBindingElement>().Any(); }
235 /// <summary>
236 /// Gets the web request handler to use for discovery and the part of
237 /// authentication where direct messages are sent to an untrusted remote party.
238 /// </summary>
239 internal IDirectWebRequestHandler WebRequestHandler {
240 get { return this.Channel.WebRequestHandler; }
243 /// <summary>
244 /// Creates an authentication request to verify that a user controls
245 /// some given Identifier.
246 /// </summary>
247 /// <param name="userSuppliedIdentifier">
248 /// The Identifier supplied by the user. This may be a URL, an XRI or i-name.
249 /// </param>
250 /// <param name="realm">
251 /// The shorest URL that describes this relying party web site's address.
252 /// For example, if your login page is found at https://www.example.com/login.aspx,
253 /// your realm would typically be https://www.example.com/.
254 /// </param>
255 /// <param name="returnToUrl">
256 /// The URL of the login page, or the page prepared to receive authentication
257 /// responses from the OpenID Provider.
258 /// </param>
259 /// <returns>
260 /// An authentication request object that describes the HTTP response to
261 /// send to the user agent to initiate the authentication.
262 /// </returns>
263 /// <exception cref="ProtocolException">Thrown if no OpenID endpoint could be found.</exception>
264 public IAuthenticationRequest CreateRequest(Identifier userSuppliedIdentifier, Realm realm, Uri returnToUrl) {
265 try {
266 return this.CreateRequests(userSuppliedIdentifier, realm, returnToUrl).First();
267 } catch (InvalidOperationException ex) {
268 throw ErrorUtilities.Wrap(ex, OpenIdStrings.OpenIdEndpointNotFound);
272 /// <summary>
273 /// Creates an authentication request to verify that a user controls
274 /// some given Identifier.
275 /// </summary>
276 /// <param name="userSuppliedIdentifier">
277 /// The Identifier supplied by the user. This may be a URL, an XRI or i-name.
278 /// </param>
279 /// <param name="realm">
280 /// The shorest URL that describes this relying party web site's address.
281 /// For example, if your login page is found at https://www.example.com/login.aspx,
282 /// your realm would typically be https://www.example.com/.
283 /// </param>
284 /// <returns>
285 /// An authentication request object that describes the HTTP response to
286 /// send to the user agent to initiate the authentication.
287 /// </returns>
288 /// <remarks>
289 /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para>
290 /// </remarks>
291 /// <exception cref="ProtocolException">Thrown if no OpenID endpoint could be found.</exception>
292 /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception>
293 public IAuthenticationRequest CreateRequest(Identifier userSuppliedIdentifier, Realm realm) {
294 try {
295 return this.CreateRequests(userSuppliedIdentifier, realm).First();
296 } catch (InvalidOperationException ex) {
297 throw ErrorUtilities.Wrap(ex, OpenIdStrings.OpenIdEndpointNotFound);
301 /// <summary>
302 /// Creates an authentication request to verify that a user controls
303 /// some given Identifier.
304 /// </summary>
305 /// <param name="userSuppliedIdentifier">
306 /// The Identifier supplied by the user. This may be a URL, an XRI or i-name.
307 /// </param>
308 /// <returns>
309 /// An authentication request object that describes the HTTP response to
310 /// send to the user agent to initiate the authentication.
311 /// </returns>
312 /// <remarks>
313 /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para>
314 /// </remarks>
315 /// <exception cref="ProtocolException">Thrown if no OpenID endpoint could be found.</exception>
316 /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception>
317 public IAuthenticationRequest CreateRequest(Identifier userSuppliedIdentifier) {
318 try {
319 return this.CreateRequests(userSuppliedIdentifier).First();
320 } catch (InvalidOperationException ex) {
321 throw ErrorUtilities.Wrap(ex, OpenIdStrings.OpenIdEndpointNotFound);
325 /// <summary>
326 /// Gets an authentication response from a Provider.
327 /// </summary>
328 /// <returns>The processed authentication response if there is any; <c>null</c> otherwise.</returns>
329 /// <remarks>
330 /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para>
331 /// </remarks>
332 public IAuthenticationResponse GetResponse() {
333 return this.GetResponse(this.Channel.GetRequestFromContext());
336 /// <summary>
337 /// Gets an authentication response from a Provider.
338 /// </summary>
339 /// <param name="httpRequestInfo">The HTTP request that may be carrying an authentication response from the Provider.</param>
340 /// <returns>The processed authentication response if there is any; <c>null</c> otherwise.</returns>
341 public IAuthenticationResponse GetResponse(HttpRequestInfo httpRequestInfo) {
342 try {
343 var message = this.Channel.ReadFromRequest();
344 PositiveAssertionResponse positiveAssertion;
345 NegativeAssertionResponse negativeAssertion;
346 if ((positiveAssertion = message as PositiveAssertionResponse) != null) {
347 return new PositiveAuthenticationResponse(positiveAssertion, this);
348 } else if ((negativeAssertion = message as NegativeAssertionResponse) != null) {
349 return new NegativeAuthenticationResponse(negativeAssertion);
350 } else if (message != null) {
351 Logger.WarnFormat("Received unexpected message type {0} when expecting an assertion message.", message.GetType().Name);
354 return null;
355 } catch (ProtocolException ex) {
356 return new FailedAuthenticationResponse(ex);
360 /// <summary>
361 /// Determines whether some parameter name belongs to OpenID or this library
362 /// as a protocol or internal parameter name.
363 /// </summary>
364 /// <param name="parameterName">Name of the parameter.</param>
365 /// <returns>
366 /// <c>true</c> if the named parameter is a library- or protocol-specific parameter; otherwise, <c>false</c>.
367 /// </returns>
368 internal static bool IsOpenIdSupportingParameter(string parameterName) {
369 Protocol protocol = Protocol.Default;
370 return parameterName.StartsWith(protocol.openid.Prefix, StringComparison.OrdinalIgnoreCase)
371 || parameterName.StartsWith("dnoi.", StringComparison.Ordinal);
374 /// <summary>
375 /// Generates the authentication requests that can satisfy the requirements of some OpenID Identifier.
376 /// </summary>
377 /// <param name="userSuppliedIdentifier">
378 /// The Identifier supplied by the user. This may be a URL, an XRI or i-name.
379 /// </param>
380 /// <param name="realm">
381 /// The shorest URL that describes this relying party web site's address.
382 /// For example, if your login page is found at https://www.example.com/login.aspx,
383 /// your realm would typically be https://www.example.com/.
384 /// </param>
385 /// <param name="returnToUrl">
386 /// The URL of the login page, or the page prepared to receive authentication
387 /// responses from the OpenID Provider.
388 /// </param>
389 /// <returns>
390 /// An authentication request object that describes the HTTP response to
391 /// send to the user agent to initiate the authentication.
392 /// </returns>
393 /// <remarks>
394 /// <para>Any individual generated request can satisfy the authentication.
395 /// The generated requests are sorted in preferred order.
396 /// Each request is generated as it is enumerated to. Associations are created only as
397 /// <see cref="IAuthenticationRequest.RedirectingResponse"/> is called.</para>
398 /// <para>No exception is thrown if no OpenID endpoints were discovered.
399 /// An empty enumerable is returned instead.</para>
400 /// </remarks>
401 internal IEnumerable<IAuthenticationRequest> CreateRequests(Identifier userSuppliedIdentifier, Realm realm, Uri returnToUrl) {
402 ErrorUtilities.VerifyArgumentNotNull(realm, "realm");
403 ErrorUtilities.VerifyArgumentNotNull(returnToUrl, "returnToUrl");
405 return AuthenticationRequest.Create(userSuppliedIdentifier, this, realm, returnToUrl, true).Cast<IAuthenticationRequest>();
408 /// <summary>
409 /// Generates the authentication requests that can satisfy the requirements of some OpenID Identifier.
410 /// </summary>
411 /// <param name="userSuppliedIdentifier">
412 /// The Identifier supplied by the user. This may be a URL, an XRI or i-name.
413 /// </param>
414 /// <param name="realm">
415 /// The shorest URL that describes this relying party web site's address.
416 /// For example, if your login page is found at https://www.example.com/login.aspx,
417 /// your realm would typically be https://www.example.com/.
418 /// </param>
419 /// <returns>
420 /// An authentication request object that describes the HTTP response to
421 /// send to the user agent to initiate the authentication.
422 /// </returns>
423 /// <remarks>
424 /// <para>Any individual generated request can satisfy the authentication.
425 /// The generated requests are sorted in preferred order.
426 /// Each request is generated as it is enumerated to. Associations are created only as
427 /// <see cref="IAuthenticationRequest.RedirectingResponse"/> is called.</para>
428 /// <para>No exception is thrown if no OpenID endpoints were discovered.
429 /// An empty enumerable is returned instead.</para>
430 /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para>
431 /// </remarks>
432 /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception>
433 internal IEnumerable<IAuthenticationRequest> CreateRequests(Identifier userSuppliedIdentifier, Realm realm) {
434 ErrorUtilities.VerifyHttpContext();
436 // Build the return_to URL
437 UriBuilder returnTo = new UriBuilder(MessagingUtilities.GetRequestUrlFromContext());
439 // Trim off any parameters with an "openid." prefix, and a few known others
440 // to avoid carrying state from a prior login attempt.
441 returnTo.Query = string.Empty;
442 NameValueCollection queryParams = MessagingUtilities.GetQueryFromContextNVC();
443 var returnToParams = new Dictionary<string, string>(queryParams.Count);
444 foreach (string key in queryParams) {
445 if (!IsOpenIdSupportingParameter(key)) {
446 returnToParams.Add(key, queryParams[key]);
449 returnTo.AppendQueryArgs(returnToParams);
451 return this.CreateRequests(userSuppliedIdentifier, realm, returnTo.Uri);
454 /// <summary>
455 /// Generates the authentication requests that can satisfy the requirements of some OpenID Identifier.
456 /// </summary>
457 /// <param name="userSuppliedIdentifier">
458 /// The Identifier supplied by the user. This may be a URL, an XRI or i-name.
459 /// </param>
460 /// <returns>
461 /// An authentication request object that describes the HTTP response to
462 /// send to the user agent to initiate the authentication.
463 /// </returns>
464 /// <remarks>
465 /// <para>Any individual generated request can satisfy the authentication.
466 /// The generated requests are sorted in preferred order.
467 /// Each request is generated as it is enumerated to. Associations are created only as
468 /// <see cref="IAuthenticationRequest.RedirectingResponse"/> is called.</para>
469 /// <para>No exception is thrown if no OpenID endpoints were discovered.
470 /// An empty enumerable is returned instead.</para>
471 /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para>
472 /// </remarks>
473 /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception>
474 internal IEnumerable<IAuthenticationRequest> CreateRequests(Identifier userSuppliedIdentifier) {
475 ErrorUtilities.VerifyHttpContext();
477 // Build the realm URL
478 UriBuilder realmUrl = new UriBuilder(MessagingUtilities.GetRequestUrlFromContext());
479 realmUrl.Path = HttpContext.Current.Request.ApplicationPath;
480 realmUrl.Query = null;
481 realmUrl.Fragment = null;
483 // For RP discovery, the realm url MUST NOT redirect. To prevent this for
484 // virtual directory hosted apps, we need to make sure that the realm path ends
485 // in a slash (since our calculation above guarantees it doesn't end in a specific
486 // page like default.aspx).
487 if (!realmUrl.Path.EndsWith("/", StringComparison.Ordinal)) {
488 realmUrl.Path += "/";
491 return this.CreateRequests(userSuppliedIdentifier, new Realm(realmUrl.Uri));
494 /// <summary>
495 /// Gets an association between this Relying Party and a given Provider
496 /// if it already exists in the association store.
497 /// </summary>
498 /// <param name="provider">The provider to create an association with.</param>
499 /// <returns>The association if one exists and has useful life remaining. Otherwise <c>null</c>.</returns>
500 internal Association GetExistingAssociation(ProviderEndpointDescription provider) {
501 ErrorUtilities.VerifyArgumentNotNull(provider, "provider");
503 Protocol protocol = Protocol.Lookup(provider.ProtocolVersion);
505 // If the RP has no application store for associations, there's no point in creating one.
506 if (this.AssociationStore == null) {
507 return null;
510 // TODO: we need a way to lookup an association that fulfills a given set of security
511 // requirements. We may have a SHA-1 association and a SHA-256 association that need
512 // to be called for specifically. (a bizzare scenario, admittedly, making this low priority).
513 Association association = this.AssociationStore.GetAssociation(provider.Endpoint);
515 // If the returned association does not fulfill security requirements, ignore it.
516 if (association != null && !this.SecuritySettings.IsAssociationInPermittedRange(protocol, association.GetAssociationType(protocol))) {
517 association = null;
520 if (association != null && !association.HasUsefulLifeRemaining) {
521 association = null;
524 return association;
527 /// <summary>
528 /// Gets an existing association with the specified Provider, or attempts to create
529 /// a new association of one does not already exist.
530 /// </summary>
531 /// <param name="provider">The provider to get an association for.</param>
532 /// <returns>The existing or new association; <c>null</c> if none existed and one could not be created.</returns>
533 internal Association GetOrCreateAssociation(ProviderEndpointDescription provider) {
534 return this.GetExistingAssociation(provider) ?? this.CreateNewAssociation(provider);
537 /// <summary>
538 /// Gets the priority rating for a given type of endpoint, allowing a
539 /// priority sorting of endpoints.
540 /// </summary>
541 /// <param name="endpoint">The endpoint to prioritize.</param>
542 /// <returns>An arbitary integer, which may be used for sorting against other returned values from this method.</returns>
543 private static double GetEndpointPrecedenceOrderByServiceType(IXrdsProviderEndpoint endpoint) {
544 // The numbers returned from this method only need to compare against other numbers
545 // from this method, which makes them arbitrary but relational to only others here.
546 if (endpoint.IsTypeUriPresent(Protocol.V20.OPIdentifierServiceTypeURI)) {
547 return 0;
549 if (endpoint.IsTypeUriPresent(Protocol.V20.ClaimedIdentifierServiceTypeURI)) {
550 return 1;
552 if (endpoint.IsTypeUriPresent(Protocol.V11.ClaimedIdentifierServiceTypeURI)) {
553 return 2;
555 if (endpoint.IsTypeUriPresent(Protocol.V10.ClaimedIdentifierServiceTypeURI)) {
556 return 3;
558 return 10;
561 /// <summary>
562 /// Creates a new association with a given Provider.
563 /// </summary>
564 /// <param name="provider">The provider to create an association with.</param>
565 /// <returns>
566 /// The newly created association, or null if no association can be created with
567 /// the given Provider given the current security settings.
568 /// </returns>
569 /// <remarks>
570 /// A new association is created and returned even if one already exists in the
571 /// association store.
572 /// Any new association is automatically added to the <see cref="AssociationStore"/>.
573 /// </remarks>
574 private Association CreateNewAssociation(ProviderEndpointDescription provider) {
575 ErrorUtilities.VerifyArgumentNotNull(provider, "provider");
577 // If there is no association store, there is no point in creating an association.
578 if (this.AssociationStore == null) {
579 return null;
582 var associateRequest = AssociateRequest.Create(this.SecuritySettings, provider);
583 if (associateRequest == null) {
584 // this can happen if security requirements and protocol conflict
585 // to where there are no association types to choose from.
586 return null;
589 var associateResponse = this.Channel.Request(associateRequest);
590 var associateSuccessfulResponse = associateResponse as AssociateSuccessfulResponse;
591 var associateUnsuccessfulResponse = associateResponse as AssociateUnsuccessfulResponse;
592 if (associateSuccessfulResponse != null) {
593 Association association = associateSuccessfulResponse.CreateAssociation(associateRequest);
594 this.AssociationStore.StoreAssociation(provider.Endpoint, association);
595 return association;
596 } else if (associateUnsuccessfulResponse != null) {
597 // TODO: code here
598 throw new NotImplementedException();
599 } else {
600 throw new ProtocolException(MessagingStrings.UnexpectedMessageReceivedOfMany);