1
//-----------------------------------------------------------------------
2 // <copyright file="AssociationManager.cs" company="Andrew Arnott">
3 // Copyright (c) Andrew Arnott. All rights reserved.
5 //-----------------------------------------------------------------------
7 namespace DotNetOpenAuth
.OpenId
.RelyingParty
{
9 using System
.Collections
.Generic
;
12 using DotNetOpenAuth
.Messaging
;
13 using DotNetOpenAuth
.OpenId
.ChannelElements
;
14 using DotNetOpenAuth
.OpenId
.Messages
;
17 /// Manages the establishment, storage and retrieval of associations at the relying party.
19 internal class AssociationManager
{
21 /// The storage to use for saving and retrieving associations. May be null.
23 private readonly IAssociationStore
<Uri
> associationStore
;
26 /// Backing field for the <see cref="Channel"/> property.
28 private Channel channel
;
31 /// Backing field for the <see cref="SecuritySettings"/> property.
33 private RelyingPartySecuritySettings securitySettings
;
36 /// Initializes a new instance of the <see cref="AssociationManager"/> class.
38 /// <param name="channel">The channel the relying party is using.</param>
39 /// <param name="associationStore">The association store. May be null for dumb mode relying parties.</param>
40 /// <param name="securitySettings">The security settings.</param>
41 internal AssociationManager(Channel channel
, IAssociationStore
<Uri
> associationStore
, RelyingPartySecuritySettings securitySettings
) {
42 ErrorUtilities
.VerifyArgumentNotNull(channel
, "channel");
43 ErrorUtilities
.VerifyArgumentNotNull(securitySettings
, "securitySettings");
45 this.channel
= channel
;
46 this.associationStore
= associationStore
;
47 this.securitySettings
= securitySettings
;
51 /// Gets or sets the channel to use for establishing associations.
53 /// <value>The channel.</value>
54 internal Channel Channel
{
60 ErrorUtilities
.VerifyArgumentNotNull(value, "value");
66 /// Gets or sets the security settings to apply in choosing association types to support.
68 internal RelyingPartySecuritySettings SecuritySettings
{
70 return this.securitySettings
;
74 ErrorUtilities
.VerifyArgumentNotNull(value, "value");
75 this.securitySettings
= value;
80 /// Gets a value indicating whether this instance has an association store.
83 /// <c>true</c> if the relying party can act in 'smart' mode;
84 /// <c>false</c> if the relying party must always act in 'dumb' mode.
86 internal bool HasAssociationStore
{
87 get { return this.associationStore != null; }
91 /// Gets an association between this Relying Party and a given Provider
92 /// if it already exists in the association store.
94 /// <param name="provider">The provider to create an association with.</param>
95 /// <returns>The association if one exists and has useful life remaining. Otherwise <c>null</c>.</returns>
96 internal Association
GetExistingAssociation(ProviderEndpointDescription provider
) {
97 ErrorUtilities
.VerifyArgumentNotNull(provider
, "provider");
99 Protocol protocol
= Protocol
.Lookup(provider
.ProtocolVersion
);
101 // If the RP has no application store for associations, there's no point in creating one.
102 if (this.associationStore
== null) {
106 // TODO: we need a way to lookup an association that fulfills a given set of security
107 // requirements. We may have a SHA-1 association and a SHA-256 association that need
108 // to be called for specifically. (a bizzare scenario, admittedly, making this low priority).
109 Association association
= this.associationStore
.GetAssociation(provider
.Endpoint
);
111 // If the returned association does not fulfill security requirements, ignore it.
112 if (association
!= null && !this.securitySettings
.IsAssociationInPermittedRange(protocol
, association
.GetAssociationType(protocol
))) {
116 if (association
!= null && !association
.HasUsefulLifeRemaining
) {
124 /// Gets an existing association with the specified Provider, or attempts to create
125 /// a new association of one does not already exist.
127 /// <param name="provider">The provider to get an association for.</param>
128 /// <returns>The existing or new association; <c>null</c> if none existed and one could not be created.</returns>
129 internal Association
GetOrCreateAssociation(ProviderEndpointDescription provider
) {
130 return this.GetExistingAssociation(provider
) ?? this.CreateNewAssociation(provider
);
134 /// Creates a new association with a given Provider.
136 /// <param name="provider">The provider to create an association with.</param>
138 /// The newly created association, or null if no association can be created with
139 /// the given Provider given the current security settings.
142 /// A new association is created and returned even if one already exists in the
143 /// association store.
144 /// Any new association is automatically added to the <see cref="associationStore"/>.
146 private Association
CreateNewAssociation(ProviderEndpointDescription provider
) {
147 ErrorUtilities
.VerifyArgumentNotNull(provider
, "provider");
149 // If there is no association store, there is no point in creating an association.
150 if (this.associationStore
== null) {
154 var associateRequest
= AssociateRequest
.Create(this.securitySettings
, provider
);
156 const int RenegotiateRetries
= 1;
157 return this.CreateNewAssociation(provider
, associateRequest
, RenegotiateRetries
);
161 /// Creates a new association with a given Provider.
163 /// <param name="provider">The provider to create an association with.</param>
164 /// <param name="associateRequest">The associate request. May be <c>null</c>, which will always result in a <c>null</c> return value..</param>
165 /// <param name="retriesRemaining">The number of times to try the associate request again if the Provider suggests it.</param>
167 /// The newly created association, or null if no association can be created with
168 /// the given Provider given the current security settings.
170 private Association
CreateNewAssociation(ProviderEndpointDescription provider
, AssociateRequest associateRequest
, int retriesRemaining
) {
171 ErrorUtilities
.VerifyArgumentNotNull(provider
, "provider");
173 if (associateRequest
== null || retriesRemaining
< 0) {
174 // this can happen if security requirements and protocol conflict
175 // to where there are no association types to choose from.
180 var associateResponse
= this.channel
.Request(associateRequest
);
181 var associateSuccessfulResponse
= associateResponse
as AssociateSuccessfulResponse
;
182 var associateUnsuccessfulResponse
= associateResponse
as AssociateUnsuccessfulResponse
;
183 if (associateSuccessfulResponse
!= null) {
184 Association association
= associateSuccessfulResponse
.CreateAssociation(associateRequest
, null);
185 this.associationStore
.StoreAssociation(provider
.Endpoint
, association
);
187 } else if (associateUnsuccessfulResponse
!= null) {
188 if (string.IsNullOrEmpty(associateUnsuccessfulResponse
.AssociationType
)) {
189 Logger
.Debug("Provider rejected an association request and gave no suggestion as to an alternative association type. Giving up.");
193 if (!this.securitySettings
.IsAssociationInPermittedRange(Protocol
.Lookup(provider
.ProtocolVersion
), associateUnsuccessfulResponse
.AssociationType
)) {
194 Logger
.DebugFormat("Provider rejected an association request and suggested '{0}' as an association to try, which this Relying Party does not support. Giving up.");
198 if (retriesRemaining
<= 0) {
199 Logger
.Debug("Unable to agree on an association type with the Provider in the allowed number of retries. Giving up.");
203 // Make sure the Provider isn't suggesting an incompatible pair of association/session types.
204 Protocol protocol
= Protocol
.Lookup(provider
.ProtocolVersion
);
205 ErrorUtilities
.VerifyProtocol(
206 HmacShaAssociation
.IsDHSessionCompatible(protocol
, associateUnsuccessfulResponse
.AssociationType
, associateUnsuccessfulResponse
.SessionType
),
207 OpenIdStrings
.IncompatibleAssociationAndSessionTypes
,
208 associateUnsuccessfulResponse
.AssociationType
,
209 associateUnsuccessfulResponse
.SessionType
);
211 associateRequest
= AssociateRequest
.Create(this.securitySettings
, provider
, associateUnsuccessfulResponse
.AssociationType
, associateUnsuccessfulResponse
.SessionType
);
212 return this.CreateNewAssociation(provider
, associateRequest
, retriesRemaining
- 1);
214 throw new ProtocolException(MessagingStrings
.UnexpectedMessageReceivedOfMany
);
216 } catch (ProtocolException ex
) {
217 // Since having associations with OPs is not totally critical, we'll log and eat
218 // the exception so that auth may continue in dumb mode.
219 Logger
.ErrorFormat("An error occurred while trying to create an association with {0}. {1}", provider
.Endpoint
, ex
);