Individual association types can now have configured lifetimes.
[dotnetoauth.git] / src / DotNetOpenAuth / OpenId / RelyingParty / OpenIdRelyingParty.cs
blob351090f9b7aedcea0cf7498e59a1c6e553c65940
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.AssociationStore = associationStore;
79 this.SecuritySettings = RelyingPartySection.Configuration.SecuritySettings.CreateSecuritySettings();
81 // Without a nonce store, we must rely on the Provider to protect against
82 // replay attacks. But only 2.0+ Providers can be expected to provide
83 // replay protection.
84 if (nonceStore == null) {
85 this.SecuritySettings.MinimumRequiredOpenIdVersion = ProtocolVersion.V20;
88 this.Channel = new OpenIdChannel(this.AssociationStore, nonceStore, secretStore);
91 /// <summary>
92 /// Gets an XRDS sorting routine that uses the XRDS Service/@Priority
93 /// attribute to determine order.
94 /// </summary>
95 /// <remarks>
96 /// Endpoints lacking any priority value are sorted to the end of the list.
97 /// </remarks>
98 [EditorBrowsable(EditorBrowsableState.Advanced)]
99 public static Comparison<IXrdsProviderEndpoint> DefaultEndpointOrder {
100 get {
101 // Sort first by service type (OpenID 2.0, 1.1, 1.0),
102 // then by Service/@priority, then by Service/Uri/@priority
103 return (se1, se2) => {
104 int result = GetEndpointPrecedenceOrderByServiceType(se1).CompareTo(GetEndpointPrecedenceOrderByServiceType(se2));
105 if (result != 0) {
106 return result;
108 if (se1.ServicePriority.HasValue && se2.ServicePriority.HasValue) {
109 result = se1.ServicePriority.Value.CompareTo(se2.ServicePriority.Value);
110 if (result != 0) {
111 return result;
113 if (se1.UriPriority.HasValue && se2.UriPriority.HasValue) {
114 return se1.UriPriority.Value.CompareTo(se2.UriPriority.Value);
115 } else if (se1.UriPriority.HasValue) {
116 return -1;
117 } else if (se2.UriPriority.HasValue) {
118 return 1;
119 } else {
120 return 0;
122 } else {
123 if (se1.ServicePriority.HasValue) {
124 return -1;
125 } else if (se2.ServicePriority.HasValue) {
126 return 1;
127 } else {
128 // neither service defines a priority, so base ordering by uri priority.
129 if (se1.UriPriority.HasValue && se2.UriPriority.HasValue) {
130 return se1.UriPriority.Value.CompareTo(se2.UriPriority.Value);
131 } else if (se1.UriPriority.HasValue) {
132 return -1;
133 } else if (se2.UriPriority.HasValue) {
134 return 1;
135 } else {
136 return 0;
144 /// <summary>
145 /// Gets the standard state storage mechanism that uses ASP.NET's
146 /// HttpApplication state dictionary to store associations and nonces.
147 /// </summary>
148 [EditorBrowsable(EditorBrowsableState.Advanced)]
149 public static IRelyingPartyApplicationStore HttpApplicationStore {
150 get {
151 HttpContext context = HttpContext.Current;
152 ErrorUtilities.VerifyOperation(context != null, OpenIdStrings.StoreRequiredWhenNoHttpContextAvailable, typeof(IRelyingPartyApplicationStore).Name);
153 var store = (IRelyingPartyApplicationStore)context.Application[ApplicationStoreKey];
154 if (store == null) {
155 context.Application.Lock();
156 try {
157 if ((store = (IRelyingPartyApplicationStore)context.Application[ApplicationStoreKey]) == null) {
158 context.Application[ApplicationStoreKey] = store = new StandardRelyingPartyApplicationStore();
160 } finally {
161 context.Application.UnLock();
165 return store;
169 /// <summary>
170 /// Gets the channel to use for sending/receiving messages.
171 /// </summary>
172 public Channel Channel { get; internal set; }
174 /// <summary>
175 /// Gets the security settings used by this Relying Party.
176 /// </summary>
177 public RelyingPartySecuritySettings SecuritySettings {
178 get {
179 return this.securitySettings;
182 internal set {
183 if (value == null) {
184 throw new ArgumentNullException("value");
187 this.securitySettings = value;
191 /// <summary>
192 /// Gets or sets the optional Provider Endpoint filter to use.
193 /// </summary>
194 /// <remarks>
195 /// Provides a way to optionally filter the providers that may be used in authenticating a user.
196 /// If provided, the delegate should return true to accept an endpoint, and false to reject it.
197 /// If null, all identity providers will be accepted. This is the default.
198 /// </remarks>
199 [EditorBrowsable(EditorBrowsableState.Advanced)]
200 public EndpointSelector EndpointFilter { get; set; }
202 /// <summary>
203 /// Gets or sets the ordering routine that will determine which XRDS
204 /// Service element to try first
205 /// </summary>
206 /// <value>Default is <see cref="DefaultEndpointOrder"/>.</value>
207 /// <remarks>
208 /// This may never be null. To reset to default behavior this property
209 /// can be set to the value of <see cref="DefaultEndpointOrder"/>.
210 /// </remarks>
211 [EditorBrowsable(EditorBrowsableState.Advanced)]
212 public Comparison<IXrdsProviderEndpoint> EndpointOrder {
213 get {
214 return this.endpointOrder;
217 set {
218 ErrorUtilities.VerifyArgumentNotNull(value, "value");
219 this.endpointOrder = value;
223 /// <summary>
224 /// Gets the association store.
225 /// </summary>
226 internal IAssociationStore<Uri> AssociationStore { get; private set; }
228 /// <summary>
229 /// Gets a value indicating whether this Relying Party can sign its return_to
230 /// parameter in outgoing authentication requests.
231 /// </summary>
232 internal bool CanSignCallbackArguments {
233 get { return this.Channel.BindingElements.OfType<ReturnToSignatureBindingElement>().Any(); }
236 /// <summary>
237 /// Gets the web request handler to use for discovery and the part of
238 /// authentication where direct messages are sent to an untrusted remote party.
239 /// </summary>
240 internal IDirectWebRequestHandler WebRequestHandler {
241 get { return this.Channel.WebRequestHandler; }
244 /// <summary>
245 /// Creates an authentication request to verify that a user controls
246 /// some given Identifier.
247 /// </summary>
248 /// <param name="userSuppliedIdentifier">
249 /// The Identifier supplied by the user. This may be a URL, an XRI or i-name.
250 /// </param>
251 /// <param name="realm">
252 /// The shorest URL that describes this relying party web site's address.
253 /// For example, if your login page is found at https://www.example.com/login.aspx,
254 /// your realm would typically be https://www.example.com/.
255 /// </param>
256 /// <param name="returnToUrl">
257 /// The URL of the login page, or the page prepared to receive authentication
258 /// responses from the OpenID Provider.
259 /// </param>
260 /// <returns>
261 /// An authentication request object that describes the HTTP response to
262 /// send to the user agent to initiate the authentication.
263 /// </returns>
264 /// <exception cref="ProtocolException">Thrown if no OpenID endpoint could be found.</exception>
265 public IAuthenticationRequest CreateRequest(Identifier userSuppliedIdentifier, Realm realm, Uri returnToUrl) {
266 try {
267 return this.CreateRequests(userSuppliedIdentifier, realm, returnToUrl).First();
268 } catch (InvalidOperationException ex) {
269 throw ErrorUtilities.Wrap(ex, OpenIdStrings.OpenIdEndpointNotFound);
273 /// <summary>
274 /// Creates an authentication request to verify that a user controls
275 /// some given Identifier.
276 /// </summary>
277 /// <param name="userSuppliedIdentifier">
278 /// The Identifier supplied by the user. This may be a URL, an XRI or i-name.
279 /// </param>
280 /// <param name="realm">
281 /// The shorest URL that describes this relying party web site's address.
282 /// For example, if your login page is found at https://www.example.com/login.aspx,
283 /// your realm would typically be https://www.example.com/.
284 /// </param>
285 /// <returns>
286 /// An authentication request object that describes the HTTP response to
287 /// send to the user agent to initiate the authentication.
288 /// </returns>
289 /// <remarks>
290 /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para>
291 /// </remarks>
292 /// <exception cref="ProtocolException">Thrown if no OpenID endpoint could be found.</exception>
293 /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception>
294 public IAuthenticationRequest CreateRequest(Identifier userSuppliedIdentifier, Realm realm) {
295 try {
296 return this.CreateRequests(userSuppliedIdentifier, realm).First();
297 } catch (InvalidOperationException ex) {
298 throw ErrorUtilities.Wrap(ex, OpenIdStrings.OpenIdEndpointNotFound);
302 /// <summary>
303 /// Creates an authentication request to verify that a user controls
304 /// some given Identifier.
305 /// </summary>
306 /// <param name="userSuppliedIdentifier">
307 /// The Identifier supplied by the user. This may be a URL, an XRI or i-name.
308 /// </param>
309 /// <returns>
310 /// An authentication request object that describes the HTTP response to
311 /// send to the user agent to initiate the authentication.
312 /// </returns>
313 /// <remarks>
314 /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para>
315 /// </remarks>
316 /// <exception cref="ProtocolException">Thrown if no OpenID endpoint could be found.</exception>
317 /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception>
318 public IAuthenticationRequest CreateRequest(Identifier userSuppliedIdentifier) {
319 try {
320 return this.CreateRequests(userSuppliedIdentifier).First();
321 } catch (InvalidOperationException ex) {
322 throw ErrorUtilities.Wrap(ex, OpenIdStrings.OpenIdEndpointNotFound);
326 /// <summary>
327 /// Gets an authentication response from a Provider.
328 /// </summary>
329 /// <returns>The processed authentication response if there is any; <c>null</c> otherwise.</returns>
330 /// <remarks>
331 /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para>
332 /// </remarks>
333 public IAuthenticationResponse GetResponse() {
334 return this.GetResponse(this.Channel.GetRequestFromContext());
337 /// <summary>
338 /// Gets an authentication response from a Provider.
339 /// </summary>
340 /// <param name="httpRequestInfo">The HTTP request that may be carrying an authentication response from the Provider.</param>
341 /// <returns>The processed authentication response if there is any; <c>null</c> otherwise.</returns>
342 public IAuthenticationResponse GetResponse(HttpRequestInfo httpRequestInfo) {
343 try {
344 var message = this.Channel.ReadFromRequest();
345 PositiveAssertionResponse positiveAssertion;
346 NegativeAssertionResponse negativeAssertion;
347 if ((positiveAssertion = message as PositiveAssertionResponse) != null) {
348 return new PositiveAuthenticationResponse(positiveAssertion, this);
349 } else if ((negativeAssertion = message as NegativeAssertionResponse) != null) {
350 return new NegativeAuthenticationResponse(negativeAssertion);
351 } else if (message != null) {
352 Logger.WarnFormat("Received unexpected message type {0} when expecting an assertion message.", message.GetType().Name);
355 return null;
356 } catch (ProtocolException ex) {
357 return new FailedAuthenticationResponse(ex);
361 /// <summary>
362 /// Determines whether some parameter name belongs to OpenID or this library
363 /// as a protocol or internal parameter name.
364 /// </summary>
365 /// <param name="parameterName">Name of the parameter.</param>
366 /// <returns>
367 /// <c>true</c> if the named parameter is a library- or protocol-specific parameter; otherwise, <c>false</c>.
368 /// </returns>
369 internal static bool IsOpenIdSupportingParameter(string parameterName) {
370 Protocol protocol = Protocol.Default;
371 return parameterName.StartsWith(protocol.openid.Prefix, StringComparison.OrdinalIgnoreCase)
372 || parameterName.StartsWith("dnoi.", StringComparison.Ordinal);
375 /// <summary>
376 /// Generates the authentication requests that can satisfy the requirements of some OpenID Identifier.
377 /// </summary>
378 /// <param name="userSuppliedIdentifier">
379 /// The Identifier supplied by the user. This may be a URL, an XRI or i-name.
380 /// </param>
381 /// <param name="realm">
382 /// The shorest URL that describes this relying party web site's address.
383 /// For example, if your login page is found at https://www.example.com/login.aspx,
384 /// your realm would typically be https://www.example.com/.
385 /// </param>
386 /// <param name="returnToUrl">
387 /// The URL of the login page, or the page prepared to receive authentication
388 /// responses from the OpenID Provider.
389 /// </param>
390 /// <returns>
391 /// An authentication request object that describes the HTTP response to
392 /// send to the user agent to initiate the authentication.
393 /// </returns>
394 /// <remarks>
395 /// <para>Any individual generated request can satisfy the authentication.
396 /// The generated requests are sorted in preferred order.
397 /// Each request is generated as it is enumerated to. Associations are created only as
398 /// <see cref="IAuthenticationRequest.RedirectingResponse"/> is called.</para>
399 /// <para>No exception is thrown if no OpenID endpoints were discovered.
400 /// An empty enumerable is returned instead.</para>
401 /// </remarks>
402 internal IEnumerable<IAuthenticationRequest> CreateRequests(Identifier userSuppliedIdentifier, Realm realm, Uri returnToUrl) {
403 ErrorUtilities.VerifyArgumentNotNull(realm, "realm");
404 ErrorUtilities.VerifyArgumentNotNull(returnToUrl, "returnToUrl");
406 return AuthenticationRequest.Create(userSuppliedIdentifier, this, realm, returnToUrl, true).Cast<IAuthenticationRequest>();
409 /// <summary>
410 /// Generates the authentication requests that can satisfy the requirements of some OpenID Identifier.
411 /// </summary>
412 /// <param name="userSuppliedIdentifier">
413 /// The Identifier supplied by the user. This may be a URL, an XRI or i-name.
414 /// </param>
415 /// <param name="realm">
416 /// The shorest URL that describes this relying party web site's address.
417 /// For example, if your login page is found at https://www.example.com/login.aspx,
418 /// your realm would typically be https://www.example.com/.
419 /// </param>
420 /// <returns>
421 /// An authentication request object that describes the HTTP response to
422 /// send to the user agent to initiate the authentication.
423 /// </returns>
424 /// <remarks>
425 /// <para>Any individual generated request can satisfy the authentication.
426 /// The generated requests are sorted in preferred order.
427 /// Each request is generated as it is enumerated to. Associations are created only as
428 /// <see cref="IAuthenticationRequest.RedirectingResponse"/> is called.</para>
429 /// <para>No exception is thrown if no OpenID endpoints were discovered.
430 /// An empty enumerable is returned instead.</para>
431 /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para>
432 /// </remarks>
433 /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception>
434 internal IEnumerable<IAuthenticationRequest> CreateRequests(Identifier userSuppliedIdentifier, Realm realm) {
435 ErrorUtilities.VerifyHttpContext();
437 // Build the return_to URL
438 UriBuilder returnTo = new UriBuilder(MessagingUtilities.GetRequestUrlFromContext());
440 // Trim off any parameters with an "openid." prefix, and a few known others
441 // to avoid carrying state from a prior login attempt.
442 returnTo.Query = string.Empty;
443 NameValueCollection queryParams = MessagingUtilities.GetQueryFromContextNVC();
444 var returnToParams = new Dictionary<string, string>(queryParams.Count);
445 foreach (string key in queryParams) {
446 if (!IsOpenIdSupportingParameter(key)) {
447 returnToParams.Add(key, queryParams[key]);
450 returnTo.AppendQueryArgs(returnToParams);
452 return this.CreateRequests(userSuppliedIdentifier, realm, returnTo.Uri);
455 /// <summary>
456 /// Generates the authentication requests that can satisfy the requirements of some OpenID Identifier.
457 /// </summary>
458 /// <param name="userSuppliedIdentifier">
459 /// The Identifier supplied by the user. This may be a URL, an XRI or i-name.
460 /// </param>
461 /// <returns>
462 /// An authentication request object that describes the HTTP response to
463 /// send to the user agent to initiate the authentication.
464 /// </returns>
465 /// <remarks>
466 /// <para>Any individual generated request can satisfy the authentication.
467 /// The generated requests are sorted in preferred order.
468 /// Each request is generated as it is enumerated to. Associations are created only as
469 /// <see cref="IAuthenticationRequest.RedirectingResponse"/> is called.</para>
470 /// <para>No exception is thrown if no OpenID endpoints were discovered.
471 /// An empty enumerable is returned instead.</para>
472 /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para>
473 /// </remarks>
474 /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception>
475 internal IEnumerable<IAuthenticationRequest> CreateRequests(Identifier userSuppliedIdentifier) {
476 ErrorUtilities.VerifyHttpContext();
478 // Build the realm URL
479 UriBuilder realmUrl = new UriBuilder(MessagingUtilities.GetRequestUrlFromContext());
480 realmUrl.Path = HttpContext.Current.Request.ApplicationPath;
481 realmUrl.Query = null;
482 realmUrl.Fragment = null;
484 // For RP discovery, the realm url MUST NOT redirect. To prevent this for
485 // virtual directory hosted apps, we need to make sure that the realm path ends
486 // in a slash (since our calculation above guarantees it doesn't end in a specific
487 // page like default.aspx).
488 if (!realmUrl.Path.EndsWith("/", StringComparison.Ordinal)) {
489 realmUrl.Path += "/";
492 return this.CreateRequests(userSuppliedIdentifier, new Realm(realmUrl.Uri));
495 /// <summary>
496 /// Gets an association between this Relying Party and a given Provider
497 /// if it already exists in the association store.
498 /// </summary>
499 /// <param name="provider">The provider to create an association with.</param>
500 /// <returns>The association if one exists and has useful life remaining. Otherwise <c>null</c>.</returns>
501 internal Association GetExistingAssociation(ProviderEndpointDescription provider) {
502 ErrorUtilities.VerifyArgumentNotNull(provider, "provider");
504 Protocol protocol = Protocol.Lookup(provider.ProtocolVersion);
506 // If the RP has no application store for associations, there's no point in creating one.
507 if (this.AssociationStore == null) {
508 return null;
511 // TODO: we need a way to lookup an association that fulfills a given set of security
512 // requirements. We may have a SHA-1 association and a SHA-256 association that need
513 // to be called for specifically. (a bizzare scenario, admittedly, making this low priority).
514 Association association = this.AssociationStore.GetAssociation(provider.Endpoint);
516 // If the returned association does not fulfill security requirements, ignore it.
517 if (association != null && !this.SecuritySettings.IsAssociationInPermittedRange(protocol, association.GetAssociationType(protocol))) {
518 association = null;
521 if (association != null && !association.HasUsefulLifeRemaining) {
522 association = null;
525 return association;
528 /// <summary>
529 /// Gets an existing association with the specified Provider, or attempts to create
530 /// a new association of one does not already exist.
531 /// </summary>
532 /// <param name="provider">The provider to get an association for.</param>
533 /// <returns>The existing or new association; <c>null</c> if none existed and one could not be created.</returns>
534 internal Association GetOrCreateAssociation(ProviderEndpointDescription provider) {
535 return this.GetExistingAssociation(provider) ?? this.CreateNewAssociation(provider);
538 /// <summary>
539 /// Gets the priority rating for a given type of endpoint, allowing a
540 /// priority sorting of endpoints.
541 /// </summary>
542 /// <param name="endpoint">The endpoint to prioritize.</param>
543 /// <returns>An arbitary integer, which may be used for sorting against other returned values from this method.</returns>
544 private static double GetEndpointPrecedenceOrderByServiceType(IXrdsProviderEndpoint endpoint) {
545 // The numbers returned from this method only need to compare against other numbers
546 // from this method, which makes them arbitrary but relational to only others here.
547 if (endpoint.IsTypeUriPresent(Protocol.V20.OPIdentifierServiceTypeURI)) {
548 return 0;
550 if (endpoint.IsTypeUriPresent(Protocol.V20.ClaimedIdentifierServiceTypeURI)) {
551 return 1;
553 if (endpoint.IsTypeUriPresent(Protocol.V11.ClaimedIdentifierServiceTypeURI)) {
554 return 2;
556 if (endpoint.IsTypeUriPresent(Protocol.V10.ClaimedIdentifierServiceTypeURI)) {
557 return 3;
559 return 10;
562 /// <summary>
563 /// Creates a new association with a given Provider.
564 /// </summary>
565 /// <param name="provider">The provider to create an association with.</param>
566 /// <returns>
567 /// The newly created association, or null if no association can be created with
568 /// the given Provider given the current security settings.
569 /// </returns>
570 /// <remarks>
571 /// A new association is created and returned even if one already exists in the
572 /// association store.
573 /// Any new association is automatically added to the <see cref="AssociationStore"/>.
574 /// </remarks>
575 private Association CreateNewAssociation(ProviderEndpointDescription provider) {
576 ErrorUtilities.VerifyArgumentNotNull(provider, "provider");
578 // If there is no association store, there is no point in creating an association.
579 if (this.AssociationStore == null) {
580 return null;
583 var associateRequest = AssociateRequest.Create(this.SecuritySettings, provider);
584 if (associateRequest == null) {
585 // this can happen if security requirements and protocol conflict
586 // to where there are no association types to choose from.
587 return null;
590 var associateResponse = this.Channel.Request(associateRequest);
591 var associateSuccessfulResponse = associateResponse as AssociateSuccessfulResponse;
592 var associateUnsuccessfulResponse = associateResponse as AssociateUnsuccessfulResponse;
593 if (associateSuccessfulResponse != null) {
594 Association association = associateSuccessfulResponse.CreateAssociation(associateRequest, null);
595 this.AssociationStore.StoreAssociation(provider.Endpoint, association);
596 return association;
597 } else if (associateUnsuccessfulResponse != null) {
598 // TODO: code here
599 throw new NotImplementedException();
600 } else {
601 throw new ProtocolException(MessagingStrings.UnexpectedMessageReceivedOfMany);