diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2011-05-07 17:25:41 -0700 |
---|---|---|
committer | Andrew Arnott <andrewarnott@gmail.com> | 2011-05-07 17:25:41 -0700 |
commit | ebcf20f3fe65b7839fdc834c861c5b11978e14ae (patch) | |
tree | 4445c8297f2ebef8ed3d4dd198aa0a2f1fa59fe0 /src | |
parent | 3063b24dba973bce7f86880a00056a60bddd1f5c (diff) | |
download | DotNetOpenAuth-ebcf20f3fe65b7839fdc834c861c5b11978e14ae.zip DotNetOpenAuth-ebcf20f3fe65b7839fdc834c861c5b11978e14ae.tar.gz DotNetOpenAuth-ebcf20f3fe65b7839fdc834c861c5b11978e14ae.tar.bz2 |
OpenID Provider association stores replaced with self-describing association handles.
Diffstat (limited to 'src')
35 files changed, 459 insertions, 444 deletions
diff --git a/src/DotNetOpenAuth.Test/OpenId/AssociationHandshakeTests.cs b/src/DotNetOpenAuth.Test/OpenId/AssociationHandshakeTests.cs index 390a5f1..ee62084 100644 --- a/src/DotNetOpenAuth.Test/OpenId/AssociationHandshakeTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/AssociationHandshakeTests.cs @@ -353,8 +353,8 @@ namespace DotNetOpenAuth.Test.OpenId { if (expectSuccess) { Assert.IsNotNull(rpAssociation); Assert.AreSame(rpAssociation, coordinator.RelyingParty.AssociationManager.AssociationStoreTestHook.GetAssociation(opDescription.Uri, rpAssociation.Handle)); - opAssociation = coordinator.Provider.AssociationStore.GetAssociation(AssociationRelyingPartyType.Smart, rpAssociation.Handle); - Assert.IsNotNull(opAssociation, "The Provider should have stored the association."); + opAssociation = coordinator.Provider.AssociationStore.Decode(new TestSignedDirectedMessage(), AssociationRelyingPartyType.Smart, rpAssociation.Handle); + Assert.IsNotNull(opAssociation, "The Provider could not decode the association handle."); Assert.AreEqual(opAssociation.Handle, rpAssociation.Handle); Assert.AreEqual(expectedAssociationType, rpAssociation.GetAssociationType(protocol)); @@ -372,7 +372,6 @@ namespace DotNetOpenAuth.Test.OpenId { } } else { Assert.IsNull(coordinator.RelyingParty.AssociationManager.AssociationStoreTestHook.GetAssociation(opDescription.Uri, new RelyingPartySecuritySettings())); - Assert.IsNull(coordinator.Provider.AssociationStore.GetAssociation(AssociationRelyingPartyType.Smart, new ProviderSecuritySettings())); } } } diff --git a/src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs b/src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs index 27db93e..9e5c754 100644 --- a/src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs @@ -138,8 +138,9 @@ namespace DotNetOpenAuth.Test.OpenId { private void ParameterizedAuthenticationTest(Protocol protocol, bool statelessRP, bool sharedAssociation, bool positive, bool immediate, bool tamper) { Contract.Requires<ArgumentException>(!statelessRP || !sharedAssociation, "The RP cannot be stateless while sharing an association with the OP."); Contract.Requires<ArgumentException>(positive || !tamper, "Cannot tamper with a negative response."); - ProviderSecuritySettings securitySettings = new ProviderSecuritySettings(); - Association association = sharedAssociation ? HmacShaAssociation.Create(protocol, protocol.Args.SignatureAlgorithm.Best, AssociationRelyingPartyType.Smart, securitySettings) : null; + var securitySettings = new ProviderSecuritySettings(); + var associationStore = new ProviderAssociationStore(); + Association association = sharedAssociation ? HmacShaAssociation.Create(protocol, protocol.Args.SignatureAlgorithm.Best, AssociationRelyingPartyType.Smart, associationStore, securitySettings) : null; var coordinator = new OpenIdCoordinator( rp => { var request = new CheckIdRequest(protocol.Version, OPUri, immediate ? AuthenticationRequestMode.Immediate : AuthenticationRequestMode.Setup); @@ -196,10 +197,7 @@ namespace DotNetOpenAuth.Test.OpenId { } }, op => { - if (association != null) { - op.AssociationStore.StoreAssociation(AssociationRelyingPartyType.Smart, association); - } - + op.AssociationStore.Secret = associationStore.Secret; var request = op.Channel.ReadFromRequest<CheckIdRequest>(); Assert.IsNotNull(request); IProtocolMessage response; diff --git a/src/DotNetOpenAuth.Test/OpenId/ChannelElements/OpenIdChannelTests.cs b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/OpenIdChannelTests.cs index eaaef34..a73c355 100644 --- a/src/DotNetOpenAuth.Test/OpenId/ChannelElements/OpenIdChannelTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/OpenIdChannelTests.cs @@ -29,7 +29,7 @@ namespace DotNetOpenAuth.Test.OpenId.ChannelElements { [SetUp] public void Setup() { this.webHandler = new Mocks.TestWebRequestHandler(); - this.channel = new OpenIdChannel(new AssociationMemoryStore<Uri>(), new NonceMemoryStore(maximumMessageAge), new RelyingPartySecuritySettings()); + this.channel = new OpenIdChannel(new AssociationMemoryStore(), new NonceMemoryStore(maximumMessageAge), new RelyingPartySecuritySettings()); this.channel.WebRequestHandler = this.webHandler; } diff --git a/src/DotNetOpenAuth.Test/OpenId/ChannelElements/SigningBindingElementTests.cs b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/SigningBindingElementTests.cs index 6160680..dbbe7f0 100644 --- a/src/DotNetOpenAuth.Test/OpenId/ChannelElements/SigningBindingElementTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/SigningBindingElementTests.cs @@ -23,10 +23,10 @@ namespace DotNetOpenAuth.Test.OpenId.ChannelElements { public void SignaturesMatchKnownGood() { Protocol protocol = Protocol.V20; var settings = new ProviderSecuritySettings(); - var store = new AssociationMemoryStore<AssociationRelyingPartyType>(); + var store = new ProviderAssociationStore(); byte[] associationSecret = Convert.FromBase64String("rsSwv1zPWfjPRQU80hciu8FPDC+GONAMJQ/AvSo1a2M="); - Association association = HmacShaAssociation.Create("mock", associationSecret, TimeSpan.FromDays(1)); - store.StoreAssociation(AssociationRelyingPartyType.Smart, association); + string handle = store.Encode(new AssociationDataBag { Secret = associationSecret, ExpiresUtc = DateTime.UtcNow.AddDays(1), AssociationType = AssociationRelyingPartyType.Smart }); + Association association = HmacShaAssociation.Create(handle, associationSecret, TimeSpan.FromDays(1)); SigningBindingElement signer = new SigningBindingElement(store, settings); signer.Channel = new TestChannel(this.MessageDescriptions); @@ -45,7 +45,7 @@ namespace DotNetOpenAuth.Test.OpenId.ChannelElements { [TestCase] public void SignedResponsesIncludeExtraDataInSignature() { Protocol protocol = Protocol.Default; - SigningBindingElement sbe = new SigningBindingElement(new AssociationMemoryStore<AssociationRelyingPartyType>(), new ProviderSecuritySettings()); + SigningBindingElement sbe = new SigningBindingElement(new ProviderAssociationStore(), new ProviderSecuritySettings()); sbe.Channel = new TestChannel(this.MessageDescriptions); IndirectSignedResponse response = new IndirectSignedResponse(protocol.Version, RPUri); response.ReturnTo = RPUri; diff --git a/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionTestUtilities.cs b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionTestUtilities.cs index 334fc93..fc1c98b 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionTestUtilities.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionTestUtilities.cs @@ -33,8 +33,9 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions { Protocol protocol, IEnumerable<IOpenIdMessageExtension> requests, IEnumerable<IOpenIdMessageExtension> responses) { - ProviderSecuritySettings securitySettings = new ProviderSecuritySettings(); - Association association = HmacShaAssociation.Create(protocol, protocol.Args.SignatureAlgorithm.Best, AssociationRelyingPartyType.Smart, securitySettings); + var securitySettings = new ProviderSecuritySettings(); + var associationStore = new ProviderAssociationStore(); + Association association = HmacShaAssociation.Create(protocol, protocol.Args.SignatureAlgorithm.Best, AssociationRelyingPartyType.Smart, associationStore, securitySettings); var coordinator = new OpenIdCoordinator( rp => { RegisterExtension(rp.Channel, Mocks.MockOpenIdExtension.Factory); @@ -57,7 +58,7 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions { }, op => { RegisterExtension(op.Channel, Mocks.MockOpenIdExtension.Factory); - op.AssociationStore.StoreAssociation(AssociationRelyingPartyType.Smart, association); + op.AssociationStore.Secret = associationStore.Secret; var request = op.Channel.ReadFromRequest<CheckIdRequest>(); var response = new PositiveAssertionResponse(request); var receivedRequests = request.Extensions.Cast<IOpenIdMessageExtension>(); diff --git a/src/DotNetOpenAuth.Test/OpenId/Provider/PerformanceTests.cs b/src/DotNetOpenAuth.Test/OpenId/Provider/PerformanceTests.cs index 4530982..365c5c5 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Provider/PerformanceTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Provider/PerformanceTests.cs @@ -78,8 +78,8 @@ namespace DotNetOpenAuth.Test.OpenId.Provider { protocol, assocType, AssociationRelyingPartyType.Smart, + this.provider.AssociationStore, this.provider.SecuritySettings); - this.provider.AssociationStore.StoreAssociation(AssociationRelyingPartyType.Smart, assoc); var checkidRequest = this.CreateCheckIdRequest(true); MeasurePerformance( () => { diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj index bf16f3e..1dfe4ed 100644 --- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj +++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj @@ -492,7 +492,10 @@ http://opensource.org/licenses/ms-pl.html <Compile Include="OAuth\ChannelElements\OAuthServiceProviderMessageFactory.cs" /> <Compile Include="Messaging\ProtocolException.cs" /> <Compile Include="OpenId\Association.cs" /> - <Compile Include="OpenId\AssociationMemoryStore.cs" /> + <Compile Include="OpenId\Provider\AssociationDataBag.cs" /> + <Compile Include="OpenId\RelyingParty\AssociationMemoryStore.cs" /> + <Compile Include="OpenId\Provider\ProviderAssociationStore.cs" /> + <Compile Include="OpenId\RelyingParty\IAssociationStore.cs" /> <Compile Include="OpenId\Associations.cs" /> <Compile Include="OpenId\Behaviors\AXFetchAsSregTransform.cs" /> <Compile Include="OpenId\Behaviors\BehaviorStrings.Designer.cs"> @@ -573,6 +576,7 @@ http://opensource.org/licenses/ms-pl.html <Compile Include="OpenId\Messages\SignedResponseRequest.cs" /> <Compile Include="OpenId\NoDiscoveryIdentifier.cs" /> <Compile Include="OpenId\OpenIdUtilities.cs" /> + <Compile Include="OpenId\Provider\AssociationRelyingPartyType.cs" /> <Compile Include="OpenId\Provider\PrivatePersonalIdentifierProviderBase.cs" /> <Compile Include="OpenId\Provider\AnonymousRequest.cs" /> <Compile Include="OpenId\Provider\AnonymousRequestEventArgs.cs" /> @@ -607,7 +611,6 @@ http://opensource.org/licenses/ms-pl.html <Compile Include="OpenId\DiffieHellman\mono\PrimeGeneratorBase.cs" /> <Compile Include="OpenId\DiffieHellman\mono\SequentialSearchPrimeGeneratorBase.cs" /> <Compile Include="OpenId\HmacShaAssociation.cs" /> - <Compile Include="OpenId\IAssociationStore.cs" /> <Compile Include="OpenId\Messages\AssociateUnencryptedRequest.cs" /> <Compile Include="OpenId\Provider\OpenIdProvider.cs" /> <Compile Include="OpenId\Messages\AssociateDiffieHellmanRequest.cs" /> @@ -865,16 +868,9 @@ http://opensource.org/licenses/ms-pl.html <ILMergeTargetPlatformDirectory Condition=" '$(ClrVersion)' == '4' ">"$(MSBuildProgramFiles32)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0"</ILMergeTargetPlatformDirectory> </PropertyGroup> <MakeDir Directories="$(ILMergeOutputAssemblyDirectory)" /> - <ILMerge ExcludeFile="$(ProjectRoot)ILMergeInternalizeExceptions.txt" - InputAssemblies="@(ILMergeInputAssemblies)" - OutputFile="$(ILMergeOutputAssembly)" - KeyFile="$(PublicKeyFile)" - DelaySign="true" - ToolPath="$(ProjectRoot)tools\ILMerge" - TargetPlatformVersion="$(ClrVersion).0" - TargetPlatformDirectory="$(ILMergeTargetPlatformDirectory)" /> + <ILMerge ExcludeFile="$(ProjectRoot)ILMergeInternalizeExceptions.txt" InputAssemblies="@(ILMergeInputAssemblies)" OutputFile="$(ILMergeOutputAssembly)" KeyFile="$(PublicKeyFile)" DelaySign="true" ToolPath="$(ProjectRoot)tools\ILMerge" TargetPlatformVersion="$(ClrVersion).0" TargetPlatformDirectory="$(ILMergeTargetPlatformDirectory)" /> </Target> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.targets" /> <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))\EnlistmentInfo.targets" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))' != '' " /> -</Project> +</Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/OpenId/Association.cs b/src/DotNetOpenAuth/OpenId/Association.cs index 3c7e89f..5c2a09e 100644 --- a/src/DotNetOpenAuth/OpenId/Association.cs +++ b/src/DotNetOpenAuth/OpenId/Association.cs @@ -51,7 +51,7 @@ namespace DotNetOpenAuth.OpenId { /// <summary> /// Gets a unique handle by which this <see cref="Association"/> may be stored or retrieved. /// </summary> - public string Handle { get; private set; } + public string Handle { get; internal set; } /// <summary> /// Gets the UTC time when this <see cref="Association"/> will expire. @@ -85,6 +85,7 @@ namespace DotNetOpenAuth.OpenId { /// <summary> /// Gets or sets the UTC time that this <see cref="Association"/> was first created. /// </summary> + [MessagePart] internal DateTime Issued { get; set; } /// <summary> @@ -102,6 +103,7 @@ namespace DotNetOpenAuth.OpenId { /// Gets the shared secret key between the consumer and provider. /// </summary> [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "It is a buffer.")] + [MessagePart("key")] protected internal byte[] SecretKey { get; private set; } /// <summary> @@ -117,6 +119,7 @@ namespace DotNetOpenAuth.OpenId { /// <summary> /// Gets the lifetime the OpenID provider permits this <see cref="Association"/>. /// </summary> + [MessagePart("ttl")] protected TimeSpan TotalLifeLength { get; private set; } /// <summary> diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs index bc613ed..049ea3a 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs +++ b/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs @@ -49,7 +49,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// <param name="associationStore">The association store to use.</param> /// <param name="nonceStore">The nonce store to use.</param> /// <param name="securitySettings">The security settings to apply.</param> - internal OpenIdChannel(IAssociationStore<Uri> associationStore, INonceStore nonceStore, RelyingPartySecuritySettings securitySettings) + internal OpenIdChannel(IAssociationStore associationStore, INonceStore nonceStore, RelyingPartySecuritySettings securitySettings) : this(associationStore, nonceStore, new OpenIdMessageFactory(), securitySettings, false) { Contract.Requires<ArgumentNullException>(securitySettings != null); } @@ -58,11 +58,11 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// Initializes a new instance of the <see cref="OpenIdChannel"/> class /// for use by a Provider. /// </summary> - /// <param name="associationStore">The association store to use.</param> /// <param name="nonceStore">The nonce store to use.</param> /// <param name="securitySettings">The security settings.</param> - internal OpenIdChannel(IAssociationStore<AssociationRelyingPartyType> associationStore, INonceStore nonceStore, ProviderSecuritySettings securitySettings) + internal OpenIdChannel(ProviderAssociationStore associationStore, INonceStore nonceStore, ProviderSecuritySettings securitySettings) : this(associationStore, nonceStore, new OpenIdMessageFactory(), securitySettings) { + Contract.Requires<ArgumentNullException>(associationStore != null, "associationStore"); Contract.Requires<ArgumentNullException>(securitySettings != null); } @@ -75,7 +75,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// <param name="messageTypeProvider">An object that knows how to distinguish the various OpenID message types for deserialization purposes.</param> /// <param name="securitySettings">The security settings to apply.</param> /// <param name="nonVerifying">A value indicating whether the channel is set up with no functional security binding elements.</param> - private OpenIdChannel(IAssociationStore<Uri> associationStore, INonceStore nonceStore, IMessageFactory messageTypeProvider, RelyingPartySecuritySettings securitySettings, bool nonVerifying) : + private OpenIdChannel(IAssociationStore associationStore, INonceStore nonceStore, IMessageFactory messageTypeProvider, RelyingPartySecuritySettings securitySettings, bool nonVerifying) : this(messageTypeProvider, InitializeBindingElements(associationStore, nonceStore, securitySettings, nonVerifying)) { Contract.Requires<ArgumentNullException>(messageTypeProvider != null); Contract.Requires<ArgumentNullException>(securitySettings != null); @@ -90,8 +90,9 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// <param name="nonceStore">The nonce store to use.</param> /// <param name="messageTypeProvider">An object that knows how to distinguish the various OpenID message types for deserialization purposes.</param> /// <param name="securitySettings">The security settings.</param> - private OpenIdChannel(IAssociationStore<AssociationRelyingPartyType> associationStore, INonceStore nonceStore, IMessageFactory messageTypeProvider, ProviderSecuritySettings securitySettings) : - this(messageTypeProvider, InitializeBindingElements(associationStore, nonceStore, securitySettings, false)) { + private OpenIdChannel(ProviderAssociationStore associationStore, INonceStore nonceStore, IMessageFactory messageTypeProvider, ProviderSecuritySettings securitySettings) : + this(messageTypeProvider, InitializeBindingElements(associationStore, nonceStore, securitySettings)) { + Contract.Requires<ArgumentNullException>(associationStore != null, "associationStore"); Contract.Requires<ArgumentNullException>(messageTypeProvider != null); Contract.Requires<ArgumentNullException>(securitySettings != null); } @@ -302,7 +303,6 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// <summary> /// Initializes the binding elements. /// </summary> - /// <typeparam name="T">The distinguishing factor used by the association store.</typeparam> /// <param name="associationStore">The association store.</param> /// <param name="nonceStore">The nonce store to use.</param> /// <param name="securitySettings">The security settings to apply. Must be an instance of either <see cref="RelyingPartySecuritySettings"/> or <see cref="ProviderSecuritySettings"/>.</param> @@ -310,57 +310,36 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// <returns> /// An array of binding elements which may be used to construct the channel. /// </returns> - [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Needed for code contracts.")] - private static IChannelBindingElement[] InitializeBindingElements<T>(IAssociationStore<T> associationStore, INonceStore nonceStore, SecuritySettings securitySettings, bool nonVerifying) { + private static IChannelBindingElement[] InitializeBindingElements(IAssociationStore associationStore, INonceStore nonceStore, RelyingPartySecuritySettings securitySettings, bool nonVerifying) { Contract.Requires<ArgumentNullException>(securitySettings != null); - Contract.Requires<ArgumentException>(!nonVerifying || securitySettings is RelyingPartySecuritySettings); - - var rpSecuritySettings = securitySettings as RelyingPartySecuritySettings; - var opSecuritySettings = securitySettings as ProviderSecuritySettings; - ErrorUtilities.VerifyInternal(rpSecuritySettings != null || opSecuritySettings != null, "Expected an RP or OP security settings instance."); - ErrorUtilities.VerifyInternal(!nonVerifying || rpSecuritySettings != null, "Non-verifying channels can only be constructed for relying parties."); - bool isRelyingPartyRole = rpSecuritySettings != null; - - var rpAssociationStore = associationStore as IAssociationStore<Uri>; - var opAssociationStore = associationStore as IAssociationStore<AssociationRelyingPartyType>; - ErrorUtilities.VerifyInternal(isRelyingPartyRole || opAssociationStore != null, "Providers MUST have an association store."); SigningBindingElement signingElement; - if (isRelyingPartyRole) { - signingElement = nonVerifying ? null : new SigningBindingElement(rpAssociationStore); - } else { - signingElement = new SigningBindingElement(opAssociationStore, opSecuritySettings); - } + signingElement = nonVerifying ? null : new SigningBindingElement(associationStore); var extensionFactory = OpenIdExtensionFactoryAggregator.LoadFromConfiguration(); List<IChannelBindingElement> elements = new List<IChannelBindingElement>(8); elements.Add(new ExtensionsBindingElement(extensionFactory, securitySettings)); - if (isRelyingPartyRole) { - elements.Add(new RelyingPartySecurityOptions(rpSecuritySettings)); - elements.Add(new BackwardCompatibilityBindingElement()); - ReturnToNonceBindingElement requestNonceElement = null; - - if (associationStore != null) { - if (nonceStore != null) { - // There is no point in having a ReturnToNonceBindingElement without - // a ReturnToSignatureBindingElement because the nonce could be - // artificially changed without it. - requestNonceElement = new ReturnToNonceBindingElement(nonceStore, rpSecuritySettings); - elements.Add(requestNonceElement); - } + elements.Add(new RelyingPartySecurityOptions(securitySettings)); + elements.Add(new BackwardCompatibilityBindingElement()); + ReturnToNonceBindingElement requestNonceElement = null; - // It is important that the return_to signing element comes last - // so that the nonce is included in the signature. - elements.Add(new ReturnToSignatureBindingElement(rpAssociationStore, rpSecuritySettings)); + if (associationStore != null) { + if (nonceStore != null) { + // There is no point in having a ReturnToNonceBindingElement without + // a ReturnToSignatureBindingElement because the nonce could be + // artificially changed without it. + requestNonceElement = new ReturnToNonceBindingElement(nonceStore, securitySettings); + elements.Add(requestNonceElement); } - ErrorUtilities.VerifyOperation(!rpSecuritySettings.RejectUnsolicitedAssertions || requestNonceElement != null, OpenIdStrings.UnsolicitedAssertionRejectionRequiresNonceStore); - } else { - // Providers must always have a nonce store. - ErrorUtilities.VerifyArgumentNotNull(nonceStore, "nonceStore"); + // It is important that the return_to signing element comes last + // so that the nonce is included in the signature. + elements.Add(new ReturnToSignatureBindingElement(associationStore, securitySettings)); } + ErrorUtilities.VerifyOperation(!securitySettings.RejectUnsolicitedAssertions || requestNonceElement != null, OpenIdStrings.UnsolicitedAssertionRejectionRequiresNonceStore); + if (nonVerifying) { elements.Add(new SkipSecurityBindingElement()); } else { @@ -374,5 +353,34 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { return elements.ToArray(); } + + /// <summary> + /// Initializes the binding elements. + /// </summary> + /// <param name="associationStore">The association store.</param> + /// <param name="nonceStore">The nonce store to use.</param> + /// <param name="securitySettings">The security settings to apply. Must be an instance of either <see cref="RelyingPartySecuritySettings"/> or <see cref="ProviderSecuritySettings"/>.</param> + /// <param name="nonVerifying">A value indicating whether the channel is set up with no functional security binding elements.</param> + /// <returns> + /// An array of binding elements which may be used to construct the channel. + /// </returns> + private static IChannelBindingElement[] InitializeBindingElements(ProviderAssociationStore associationStore, INonceStore nonceStore, ProviderSecuritySettings securitySettings) { + Contract.Requires<ArgumentNullException>(associationStore != null, "associationStore"); + Contract.Requires<ArgumentNullException>(securitySettings != null); + Contract.Requires<ArgumentNullException>(nonceStore != null, "nonceStore"); + + SigningBindingElement signingElement; + signingElement = new SigningBindingElement(associationStore, securitySettings); + + var extensionFactory = OpenIdExtensionFactoryAggregator.LoadFromConfiguration(); + + List<IChannelBindingElement> elements = new List<IChannelBindingElement>(8); + elements.Add(new ExtensionsBindingElement(extensionFactory, securitySettings)); + elements.Add(new StandardReplayProtectionBindingElement(nonceStore, true)); + elements.Add(new StandardExpirationBindingElement()); + elements.Add(signingElement); + + return elements.ToArray(); + } } } diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs index 438c496..aba5dbd 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs +++ b/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs @@ -52,7 +52,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// </summary> /// <param name="secretStore">The secret store from which to retrieve the secret used for signing.</param> /// <param name="securitySettings">The security settings.</param> - internal ReturnToSignatureBindingElement(IAssociationStore<Uri> secretStore, RelyingPartySecuritySettings securitySettings) { + internal ReturnToSignatureBindingElement(IAssociationStore secretStore, RelyingPartySecuritySettings securitySettings) { Contract.Requires<ArgumentNullException>(secretStore != null); this.secretManager = new PrivateSecretManager(securitySettings, secretStore); diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/SigningBindingElement.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/SigningBindingElement.cs index 30310ac..be64d82 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/SigningBindingElement.cs +++ b/src/DotNetOpenAuth/OpenId/ChannelElements/SigningBindingElement.cs @@ -28,12 +28,12 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// <summary> /// The association store used by Relying Parties to look up the secrets needed for signing. /// </summary> - private readonly IAssociationStore<Uri> rpAssociations; + private readonly IAssociationStore rpAssociations; /// <summary> /// The association store used by Providers to look up the secrets needed for signing. /// </summary> - private readonly IAssociationStore<AssociationRelyingPartyType> opAssociations; + private readonly ProviderAssociationStore opAssociations; /// <summary> /// The security settings at the Provider. @@ -45,7 +45,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// Initializes a new instance of the SigningBindingElement class for use by a Relying Party. /// </summary> /// <param name="associationStore">The association store used to look up the secrets needed for signing. May be null for dumb Relying Parties.</param> - internal SigningBindingElement(IAssociationStore<Uri> associationStore) { + internal SigningBindingElement(IAssociationStore associationStore) { this.rpAssociations = associationStore; } @@ -54,8 +54,8 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// </summary> /// <param name="associationStore">The association store used to look up the secrets needed for signing.</param> /// <param name="securitySettings">The security settings.</param> - internal SigningBindingElement(IAssociationStore<AssociationRelyingPartyType> associationStore, ProviderSecuritySettings securitySettings) { - Contract.Requires<ArgumentNullException>(associationStore != null); + internal SigningBindingElement(ProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { + Contract.Requires<ArgumentNullException>(associationStore != null, "associationStore"); Contract.Requires<ArgumentNullException>(securitySettings != null); this.opAssociations = associationStore; @@ -83,7 +83,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// Gets a value indicating whether this binding element is on a Provider channel. /// </summary> private bool IsOnProvider { - get { return this.opAssociations != null; } + get { return this.opSecuritySettings != null; } } #region IChannelBindingElement Methods @@ -232,14 +232,14 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { // get included in the list of checked parameters. Protocol protocol = Protocol.Lookup(signedMessage.Version); var partsRequiringProtection = from part in this.Channel.MessageDescriptions.Get(signedMessage).Mapping.Values - where part.RequiredProtection != ProtectionLevel.None - where part.IsRequired || part.IsNondefaultValueSet(signedMessage) - select part.Name; + where part.RequiredProtection != ProtectionLevel.None + where part.IsRequired || part.IsNondefaultValueSet(signedMessage) + select part.Name; ErrorUtilities.VerifyInternal(partsRequiringProtection.All(name => name.StartsWith(protocol.openid.Prefix, StringComparison.Ordinal)), "Signing only works when the parameters start with the 'openid.' prefix."); string[] signedParts = signedMessage.SignedParameterOrder.Split(','); var unsignedParts = from partName in partsRequiringProtection - where !signedParts.Contains(partName.Substring(protocol.openid.Prefix.Length)) - select partName; + where !signedParts.Contains(partName.Substring(protocol.openid.Prefix.Length)) + select partName; ErrorUtilities.VerifyProtocol(!unsignedParts.Any(), OpenIdStrings.SignatureDoesNotIncludeMandatoryParts, string.Join(", ", unsignedParts.ToArray())); } @@ -292,9 +292,9 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { MessageDescription description = this.Channel.MessageDescriptions.Get(signedMessage); var signedParts = from part in description.Mapping.Values - where (part.RequiredProtection & System.Net.Security.ProtectionLevel.Sign) != 0 - && part.GetValue(signedMessage) != null - select part.Name; + where (part.RequiredProtection & System.Net.Security.ProtectionLevel.Sign) != 0 + && part.GetValue(signedMessage) != null + select part.Name; string prefix = Protocol.V20.openid.Prefix; ErrorUtilities.VerifyInternal(signedParts.All(name => name.StartsWith(prefix, StringComparison.Ordinal)), "All signed message parts must start with 'openid.'."); @@ -379,7 +379,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { // or verifying a dumb one. bool signing = string.IsNullOrEmpty(signedMessage.Signature); AssociationRelyingPartyType type = signing ? AssociationRelyingPartyType.Smart : AssociationRelyingPartyType.Dumb; - association = this.opAssociations.GetAssociation(type, signedMessage.AssociationHandle); + association = this.opAssociations.Decode(signedMessage, type, signedMessage.AssociationHandle); if (association == null) { // There was no valid association with the requested handle. // Let's tell the RP to forget about that association. @@ -403,12 +403,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { // If no assoc_handle was given or it was invalid, the only thing // left to do is sign a message using a 'dumb' mode association. Protocol protocol = Protocol.Default; - Association association = this.opAssociations.GetAssociation(AssociationRelyingPartyType.Dumb, this.opSecuritySettings); - if (association == null) { - association = HmacShaAssociation.Create(protocol, protocol.Args.SignatureAlgorithm.HMAC_SHA256, AssociationRelyingPartyType.Dumb, this.opSecuritySettings); - this.opAssociations.StoreAssociation(AssociationRelyingPartyType.Dumb, association); - } - + Association association = HmacShaAssociation.Create(protocol, protocol.Args.SignatureAlgorithm.HMAC_SHA256, AssociationRelyingPartyType.Dumb, this.opAssociations, this.opSecuritySettings); return association; } } diff --git a/src/DotNetOpenAuth/OpenId/HmacShaAssociation.cs b/src/DotNetOpenAuth/OpenId/HmacShaAssociation.cs index edc08ee..0108566 100644 --- a/src/DotNetOpenAuth/OpenId/HmacShaAssociation.cs +++ b/src/DotNetOpenAuth/OpenId/HmacShaAssociation.cs @@ -102,7 +102,6 @@ namespace DotNetOpenAuth.OpenId { Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(associationType)); Contract.Requires<ArgumentNullException>(secret != null); Contract.Ensures(Contract.Result<HmacShaAssociation>() != null); - HmacSha match = hmacShaAssociationTypes.FirstOrDefault(sha => String.Equals(sha.GetAssociationType(protocol), associationType, StringComparison.Ordinal)); ErrorUtilities.VerifyProtocol(match != null, OpenIdStrings.NoAssociationTypeFoundByName, associationType); return new HmacShaAssociation(match, handle, secret, totalLifeLength); @@ -139,7 +138,7 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> - /// Creates a new association of a given type. + /// Creates a new association of a given type at an OpenID Provider. /// </summary> /// <param name="protocol">The protocol.</param> /// <param name="associationType">Type of the association (i.e. HMAC-SHA1 or HMAC-SHA256)</param> @@ -150,24 +149,15 @@ namespace DotNetOpenAuth.OpenId { /// <remarks> /// The new association is NOT automatically put into an association store. This must be done by the caller. /// </remarks> - internal static HmacShaAssociation Create(Protocol protocol, string associationType, AssociationRelyingPartyType associationUse, ProviderSecuritySettings securitySettings) { + internal static HmacShaAssociation Create(Protocol protocol, string associationType, AssociationRelyingPartyType associationUse, ProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { Contract.Requires<ArgumentNullException>(protocol != null); Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(associationType)); + Contract.Requires<ArgumentNullException>(associationStore != null, "associationStore"); Contract.Requires<ArgumentNullException>(securitySettings != null); Contract.Ensures(Contract.Result<HmacShaAssociation>() != null); int secretLength = GetSecretLength(protocol, associationType); - // Generate the handle. It must be unique, and preferably unpredictable, - // so we use a time element and a random data element to generate it. - string uniq = MessagingUtilities.GetCryptoRandomDataAsBase64(4); - string handle = string.Format( - CultureInfo.InvariantCulture, - "{{{0}}}{{{1}}}{{{2}}}", - DateTime.UtcNow.Ticks, - uniq, - secretLength); - // Generate the secret that will be used for signing byte[] secret = MessagingUtilities.GetCryptoRandomData(secretLength); @@ -180,10 +170,13 @@ namespace DotNetOpenAuth.OpenId { lifetime = DumbSecretLifetime; } + string handle = associationStore.Encode(new AssociationDataBag { ExpiresUtc = DateTime.UtcNow + lifetime, Secret = secret, AssociationType = associationUse }); + Contract.Assert(protocol != null); // All the way up to the method call, the condition holds, yet we get a Requires failure next Contract.Assert(secret != null); Contract.Assert(!String.IsNullOrEmpty(associationType)); - return Create(protocol, associationType, handle, secret, lifetime); + var result = Create(protocol, associationType, handle, secret, lifetime); + return result; } /// <summary> diff --git a/src/DotNetOpenAuth/OpenId/IAssociationStore.cs b/src/DotNetOpenAuth/OpenId/IAssociationStore.cs deleted file mode 100644 index fb4487c..0000000 --- a/src/DotNetOpenAuth/OpenId/IAssociationStore.cs +++ /dev/null @@ -1,189 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IAssociationStore.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId { - using System; - using System.Diagnostics.Contracts; - - /// <summary> - /// An enumeration that can specify how a given <see cref="Association"/> is used. - /// </summary> - public enum AssociationRelyingPartyType { - /// <summary> - /// The <see cref="Association"/> manages a shared secret between - /// Provider and Relying Party sites that allows the RP to verify - /// the signature on a message from an OP. - /// </summary> - Smart, - - /// <summary> - /// The <see cref="Association"/> manages a secret known alone by - /// a Provider that allows the Provider to verify its own signatures - /// for "dumb" (stateless) relying parties. - /// </summary> - Dumb - } - - /// <summary> - /// Stores <see cref="Association"/>s for lookup by their handle, keeping - /// associations separated by a given distinguishing factor (like which server the - /// association is with). - /// </summary> - /// <typeparam name="TKey"> - /// <see cref="System.Uri"/> for consumers (to distinguish associations across servers) or - /// <see cref="AssociationRelyingPartyType"/> for providers (to distinguish dumb and smart client associations). - /// </typeparam> - /// <remarks> - /// Expired associations should be periodically cleared out of an association store. - /// This should be done frequently enough to avoid a memory leak, but sparingly enough - /// to not be a performance drain. Because this balance can vary by host, it is the - /// responsibility of the host to initiate this cleaning. - /// </remarks> - ////[ContractClass(typeof(IAssociationStoreContract<>))] - public interface IAssociationStore<TKey> { - /// <summary> - /// Saves an <see cref="Association"/> for later recall. - /// </summary> - /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for providers).</param> - /// <param name="association">The association to store.</param> - /// <remarks> - /// TODO: what should implementations do on association handle conflict? - /// </remarks> - void StoreAssociation(TKey distinguishingFactor, Association association); - - /// <summary> - /// Gets the best association (the one with the longest remaining life) for a given key. - /// </summary> - /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - /// <param name="securityRequirements">The security requirements that the returned association must meet.</param> - /// <returns> - /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key. - /// </returns> - /// <remarks> - /// In the event that multiple associations exist for the given - /// <paramref name="distinguishingFactor"/>, it is important for the - /// implementation for this method to use the <paramref name="securityRequirements"/> - /// to pick the best (highest grade or longest living as the host's policy may dictate) - /// association that fits the security requirements. - /// Associations that are returned that do not meet the security requirements will be - /// ignored and a new association created. - /// </remarks> - Association GetAssociation(TKey distinguishingFactor, SecuritySettings securityRequirements); - - /// <summary> - /// Gets the association for a given key and handle. - /// </summary> - /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - /// <param name="handle">The handle of the specific association that must be recalled.</param> - /// <returns>The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key and handle.</returns> - Association GetAssociation(TKey distinguishingFactor, string handle); - - /// <summary>Removes a specified handle that may exist in the store.</summary> - /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - /// <param name="handle">The handle of the specific association that must be deleted.</param> - /// <returns>True if the association existed in this store previous to this call.</returns> - /// <remarks> - /// No exception should be thrown if the association does not exist in the store - /// before this call. - /// </remarks> - bool RemoveAssociation(TKey distinguishingFactor, string handle); - } - - // For some odd reason, having this next class causes our test project to fail to build with this error: - // Error 42 Method 'StoreAssociation' in type 'DotNetOpenAuth.OpenId.IAssociationStoreContract_Accessor`1' from assembly 'DotNetOpenAuth_Accessor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation. DotNetOpenAuth.Test - /////// <summary> - /////// Code Contract for the <see cref="IAssociationStore<TKey>"/> class. - /////// </summary> - /////// <typeparam name="TKey">The type of the key.</typeparam> - ////[ContractClassFor(typeof(IAssociationStore<>))] - ////internal abstract class IAssociationStoreContract<TKey> : IAssociationStore<TKey> { - //// #region IAssociationStore<TKey> Members - - //// /// <summary> - //// /// Saves an <see cref="Association"/> for later recall. - //// /// </summary> - //// /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for providers).</param> - //// /// <param name="association">The association to store.</param> - //// /// <remarks> - //// /// TODO: what should implementations do on association handle conflict? - //// /// </remarks> - //// void IAssociationStore<TKey>.StoreAssociation(TKey distinguishingFactor, Association association) { - //// Contract.Requires<ArgumentNullException>(distinguishingFactor != null); - //// Contract.Requires<ArgumentNullException>(association != null); - //// throw new NotImplementedException(); - //// } - - //// /// <summary> - //// /// Gets the best association (the one with the longest remaining life) for a given key. - //// /// </summary> - //// /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - //// /// <param name="securityRequirements">The security requirements that the returned association must meet.</param> - //// /// <returns> - //// /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key. - //// /// </returns> - //// /// <remarks> - //// /// In the event that multiple associations exist for the given - //// /// <paramref name="distinguishingFactor"/>, it is important for the - //// /// implementation for this method to use the <paramref name="securityRequirements"/> - //// /// to pick the best (highest grade or longest living as the host's policy may dictate) - //// /// association that fits the security requirements. - //// /// Associations that are returned that do not meet the security requirements will be - //// /// ignored and a new association created. - //// /// </remarks> - //// Association IAssociationStore<TKey>.GetAssociation(TKey distinguishingFactor, SecuritySettings securityRequirements) { - //// Contract.Requires<ArgumentNullException>(distinguishingFactor != null); - //// Contract.Requires<ArgumentNullException>(securityRequirements != null); - //// throw new NotImplementedException(); - //// } - - //// /// <summary> - //// /// Gets the association for a given key and handle. - //// /// </summary> - //// /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - //// /// <param name="handle">The handle of the specific association that must be recalled.</param> - //// /// <returns> - //// /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key and handle. - //// /// </returns> - //// Association IAssociationStore<TKey>.GetAssociation(TKey distinguishingFactor, string handle) { - //// Contract.Requires<ArgumentNullException>(distinguishingFactor != null); - //// Contract.Requires(!String.IsNullOrEmpty(handle)); - //// throw new NotImplementedException(); - //// } - - //// /// <summary> - //// /// Removes a specified handle that may exist in the store. - //// /// </summary> - //// /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - //// /// <param name="handle">The handle of the specific association that must be deleted.</param> - //// /// <returns> - //// /// True if the association existed in this store previous to this call. - //// /// </returns> - //// /// <remarks> - //// /// No exception should be thrown if the association does not exist in the store - //// /// before this call. - //// /// </remarks> - //// bool IAssociationStore<TKey>.RemoveAssociation(TKey distinguishingFactor, string handle) { - //// Contract.Requires<ArgumentNullException>(distinguishingFactor != null); - //// Contract.Requires(!String.IsNullOrEmpty(handle)); - //// throw new NotImplementedException(); - //// } - - //// /// <summary> - //// /// Clears all expired associations from the store. - //// /// </summary> - //// /// <remarks> - //// /// If another algorithm is in place to periodically clear out expired associations, - //// /// this method call may be ignored. - //// /// This should be done frequently enough to avoid a memory leak, but sparingly enough - //// /// to not be a performance drain. - //// /// </remarks> - //// void IAssociationStore<TKey>.ClearExpiredAssociations() { - //// throw new NotImplementedException(); - //// } - - //// #endregion - ////} -} diff --git a/src/DotNetOpenAuth/OpenId/Messages/AssociateDiffieHellmanResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/AssociateDiffieHellmanResponse.cs index a4fdf49..86e2d8c 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/AssociateDiffieHellmanResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/AssociateDiffieHellmanResponse.cs @@ -72,14 +72,14 @@ namespace DotNetOpenAuth.OpenId.Messages { /// The response message is updated to include the details of the created association by this method, /// but the resulting association is <i>not</i> added to the association store and must be done by the caller. /// </remarks> - protected override Association CreateAssociationAtProvider(AssociateRequest request, ProviderSecuritySettings securitySettings) { + protected override Association CreateAssociationAtProvider(AssociateRequest request, ProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { var diffieHellmanRequest = request as AssociateDiffieHellmanRequest; ErrorUtilities.VerifyInternal(diffieHellmanRequest != null, "Expected a DH request type."); this.SessionType = this.SessionType ?? request.SessionType; // Go ahead and create the association first, complete with its secret that we're about to share. - Association association = HmacShaAssociation.Create(this.Protocol, this.AssociationType, AssociationRelyingPartyType.Smart, securitySettings); + Association association = HmacShaAssociation.Create(this.Protocol, this.AssociationType, AssociationRelyingPartyType.Smart, associationStore, securitySettings); // We now need to securely communicate the secret to the relying party using Diffie-Hellman. // We do this by performing a DH algorithm on the secret and setting a couple of properties diff --git a/src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs b/src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs index 3fd9424..937c7ff 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs @@ -129,7 +129,6 @@ namespace DotNetOpenAuth.OpenId.Messages { /// <summary> /// Creates a Provider's response to an incoming association request. /// </summary> - /// <param name="associationStore">The association store where a new association (if created) will be stored. Must not be null.</param> /// <param name="securitySettings">The security settings on the Provider.</param> /// <returns> /// The appropriate association response that is ready to be sent back to the Relying Party. @@ -140,8 +139,8 @@ namespace DotNetOpenAuth.OpenId.Messages { /// <para>Successful association response messages will derive from <see cref="AssociateSuccessfulResponse"/>. /// Failed association response messages will derive from <see cref="AssociateUnsuccessfulResponse"/>.</para> /// </remarks> - internal IProtocolMessage CreateResponse(IAssociationStore<AssociationRelyingPartyType> associationStore, ProviderSecuritySettings securitySettings) { - Contract.Requires<ArgumentNullException>(associationStore != null); + internal IProtocolMessage CreateResponse(ProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { + Contract.Requires<ArgumentNullException>(associationStore != null, "associationStore"); Contract.Requires<ArgumentNullException>(securitySettings != null); IProtocolMessage response; @@ -152,8 +151,7 @@ namespace DotNetOpenAuth.OpenId.Messages { // Create and store the association if this is a successful response. var successResponse = response as AssociateSuccessfulResponse; if (successResponse != null) { - Association association = successResponse.CreateAssociation(this, securitySettings); - associationStore.StoreAssociation(AssociationRelyingPartyType.Smart, association); + successResponse.CreateAssociation(this, associationStore, securitySettings); } } else { response = this.CreateUnsuccessfulResponse(securitySettings); diff --git a/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponse.cs index 137cd60..e3d280f 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponse.cs @@ -102,12 +102,11 @@ namespace DotNetOpenAuth.OpenId.Messages { /// <param name="securitySettings">The security settings for the Provider. Should be <c>null</c> for Relying Parties.</param> /// <returns>The created association.</returns> /// <remarks> - /// <para>The response message is updated to include the details of the created association by this method, - /// but the resulting association is <i>not</i> added to the association store and must be done by the caller.</para> - /// <para>This method is called by both the Provider and the Relying Party, but actually performs - /// quite different operations in either scenario.</para> + /// The response message is updated to include the details of the created association by this method. + /// This method is called by both the Provider and the Relying Party, but actually performs + /// quite different operations in either scenario. /// </remarks> - internal Association CreateAssociation(AssociateRequest request, ProviderSecuritySettings securitySettings) { + internal Association CreateAssociation(AssociateRequest request, ProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { Contract.Requires<ArgumentNullException>(request != null); ErrorUtilities.VerifyInternal(!this.associationCreated, "The association has already been created."); @@ -119,7 +118,7 @@ namespace DotNetOpenAuth.OpenId.Messages { association = this.CreateAssociationAtRelyingParty(request); } else { ErrorUtilities.VerifyArgumentNotNull(securitySettings, "securitySettings"); - association = this.CreateAssociationAtProvider(request, securitySettings); + association = this.CreateAssociationAtProvider(request, associationStore, securitySettings); this.ExpiresIn = association.SecondsTillExpiration; this.AssociationHandle = association.Handle; } @@ -142,7 +141,7 @@ namespace DotNetOpenAuth.OpenId.Messages { /// <para>The response message is updated to include the details of the created association by this method, /// but the resulting association is <i>not</i> added to the association store and must be done by the caller.</para> /// </remarks> - protected abstract Association CreateAssociationAtProvider(AssociateRequest request, ProviderSecuritySettings securitySettings); + protected abstract Association CreateAssociationAtProvider(AssociateRequest request, ProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings); /// <summary> /// Called to create the Association based on a request previously given by the Relying Party. diff --git a/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponseContract.cs b/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponseContract.cs index dd37da6..c2e7a86 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponseContract.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponseContract.cs @@ -15,8 +15,9 @@ namespace DotNetOpenAuth.OpenId.Messages { protected AssociateSuccessfulResponseContract() : base(null, null) { } - protected override Association CreateAssociationAtProvider(AssociateRequest request, ProviderSecuritySettings securitySettings) { + protected override Association CreateAssociationAtProvider(AssociateRequest request, ProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { Contract.Requires<ArgumentNullException>(request != null); + Contract.Requires<ArgumentNullException>(associationStore != null, "associationStore"); Contract.Requires<ArgumentNullException>(securitySettings != null); throw new NotImplementedException(); } diff --git a/src/DotNetOpenAuth/OpenId/Messages/AssociateUnencryptedResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/AssociateUnencryptedResponse.cs index 493fe6b..77079cc 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/AssociateUnencryptedResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/AssociateUnencryptedResponse.cs @@ -48,8 +48,8 @@ namespace DotNetOpenAuth.OpenId.Messages { /// <para>The response message is updated to include the details of the created association by this method, /// but the resulting association is <i>not</i> added to the association store and must be done by the caller.</para> /// </remarks> - protected override Association CreateAssociationAtProvider(AssociateRequest request, ProviderSecuritySettings securitySettings) { - Association association = HmacShaAssociation.Create(Protocol, this.AssociationType, AssociationRelyingPartyType.Smart, securitySettings); + protected override Association CreateAssociationAtProvider(AssociateRequest request, ProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { + Association association = HmacShaAssociation.Create(Protocol, this.AssociationType, AssociationRelyingPartyType.Smart, associationStore, securitySettings); this.MacKey = association.SecretKey; return association; } diff --git a/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationResponse.cs index f1bb5ac..e4e8c7c 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationResponse.cs @@ -44,10 +44,10 @@ namespace DotNetOpenAuth.OpenId.Messages { this.IsValid = request.IsValid; // Confirm the RP should invalidate the association handle only if the association - // really doesn't exist. OpenID 2.0 section 11.4.2.2. + // is not valid (any longer). OpenID 2.0 section 11.4.2.2. IndirectSignedResponse signedResponse = new IndirectSignedResponse(request, provider.Channel); string invalidateHandle = ((ITamperResistantOpenIdMessage)signedResponse).InvalidateHandle; - if (!string.IsNullOrEmpty(invalidateHandle) && provider.AssociationStore.GetAssociation(AssociationRelyingPartyType.Smart, invalidateHandle) == null) { + if (!string.IsNullOrEmpty(invalidateHandle) && provider.AssociationStore.IsValid(signedResponse, AssociationRelyingPartyType.Smart, invalidateHandle) == null) { this.InvalidateHandle = invalidateHandle; } } diff --git a/src/DotNetOpenAuth/OpenId/Provider/AssociationDataBag.cs b/src/DotNetOpenAuth/OpenId/Provider/AssociationDataBag.cs new file mode 100644 index 0000000..1b5be94 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Provider/AssociationDataBag.cs @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------- +// <copyright file="AssociationDataBag.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + internal class AssociationDataBag : DataBag { + /// <summary> + /// Initializes a new instance of the <see cref="AssociationDataBag"/> class. + /// </summary> + public AssociationDataBag() { + } + + [MessagePart(IsRequired = true)] + internal byte[] Secret { get; set; } + + [MessagePart(IsRequired = true)] + internal DateTime ExpiresUtc { get; set; } + + [MessagePart(IsRequired = true)] + internal bool IsPrivateAssociation { + get { return this.AssociationType == AssociationRelyingPartyType.Dumb; } + set { this.AssociationType = value ? AssociationRelyingPartyType.Dumb : AssociationRelyingPartyType.Smart; } + } + + internal AssociationRelyingPartyType AssociationType { get; set; } + + internal static IDataBagFormatter<AssociationDataBag> CreateFormatter(byte[] symmetricSecret) { + return new UriStyleMessageFormatter<AssociationDataBag>(symmetricSecret, signed: true, encrypted: true); + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Provider/AssociationRelyingPartyType.cs b/src/DotNetOpenAuth/OpenId/Provider/AssociationRelyingPartyType.cs new file mode 100644 index 0000000..4d121b1 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Provider/AssociationRelyingPartyType.cs @@ -0,0 +1,26 @@ +//----------------------------------------------------------------------- +// <copyright file="AssociationRelyingPartyType.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + /// <summary> + /// An enumeration that can specify how a given <see cref="Association"/> is used. + /// </summary> + public enum AssociationRelyingPartyType { + /// <summary> + /// The <see cref="Association"/> manages a shared secret between + /// Provider and Relying Party sites that allows the RP to verify + /// the signature on a message from an OP. + /// </summary> + Smart, + + /// <summary> + /// The <see cref="Association"/> manages a secret known alone by + /// a Provider that allows the Provider to verify its own signatures + /// for "dumb" (stateless) relying parties. + /// </summary> + Dumb + } +} diff --git a/src/DotNetOpenAuth/OpenId/Provider/IProviderApplicationStore.cs b/src/DotNetOpenAuth/OpenId/Provider/IProviderApplicationStore.cs index c16f1f9..cea303f 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/IProviderApplicationStore.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/IProviderApplicationStore.cs @@ -12,9 +12,8 @@ namespace DotNetOpenAuth.OpenId.Provider { using DotNetOpenAuth.Messaging.Bindings; /// <summary> - /// A hybrid of all the store interfaces that a Provider requires in order - /// to operate in "smart" mode. + /// A hybrid of all the store interfaces that an OpenID Provider must implement. /// </summary> - public interface IProviderApplicationStore : IAssociationStore<AssociationRelyingPartyType>, INonceStore { + public interface IProviderApplicationStore : INonceStore { } } diff --git a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs index 3b4cb56..304edbe 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs @@ -55,7 +55,6 @@ namespace DotNetOpenAuth.OpenId.Provider { /// </summary> public OpenIdProvider() : this(DotNetOpenAuthSection.Configuration.OpenId.Provider.ApplicationStore.CreateInstance(HttpApplicationStore)) { - Contract.Ensures(this.AssociationStore != null); Contract.Ensures(this.SecuritySettings != null); Contract.Ensures(this.Channel != null); } @@ -65,9 +64,8 @@ namespace DotNetOpenAuth.OpenId.Provider { /// </summary> /// <param name="applicationStore">The application store to use. Cannot be null.</param> public OpenIdProvider(IProviderApplicationStore applicationStore) - : this(applicationStore, applicationStore) { + : this((INonceStore)applicationStore) { Contract.Requires<ArgumentNullException>(applicationStore != null); - Contract.Ensures(this.AssociationStore == applicationStore); Contract.Ensures(this.SecuritySettings != null); Contract.Ensures(this.Channel != null); } @@ -77,14 +75,12 @@ namespace DotNetOpenAuth.OpenId.Provider { /// </summary> /// <param name="associationStore">The association store to use. Cannot be null.</param> /// <param name="nonceStore">The nonce store to use. Cannot be null.</param> - private OpenIdProvider(IAssociationStore<AssociationRelyingPartyType> associationStore, INonceStore nonceStore) { - Contract.Requires<ArgumentNullException>(associationStore != null); + private OpenIdProvider(INonceStore nonceStore) { Contract.Requires<ArgumentNullException>(nonceStore != null); - Contract.Ensures(this.AssociationStore == associationStore); Contract.Ensures(this.SecuritySettings != null); Contract.Ensures(this.Channel != null); - this.AssociationStore = associationStore; + this.AssociationStore = new ProviderAssociationStore(); this.SecuritySettings = DotNetOpenAuthSection.Configuration.OpenId.Provider.SecuritySettings.CreateSecuritySettings(); this.behaviors.CollectionChanged += this.OnBehaviorsChanged; foreach (var behavior in DotNetOpenAuthSection.Configuration.OpenId.Provider.Behaviors.CreateInstances(false)) { @@ -93,7 +89,7 @@ namespace DotNetOpenAuth.OpenId.Provider { this.Channel = new OpenIdChannel(this.AssociationStore, nonceStore, this.SecuritySettings); - Reporting.RecordFeatureAndDependencyUse(this, associationStore, nonceStore); + Reporting.RecordFeatureAndDependencyUse(this, nonceStore); } /// <summary> @@ -175,11 +171,6 @@ namespace DotNetOpenAuth.OpenId.Provider { } /// <summary> - /// Gets the association store. - /// </summary> - internal IAssociationStore<AssociationRelyingPartyType> AssociationStore { get; private set; } - - /// <summary> /// Gets the web request handler to use for discovery and the part of /// authentication where direct messages are sent to an untrusted remote party. /// </summary> @@ -187,6 +178,8 @@ namespace DotNetOpenAuth.OpenId.Provider { get { return this.Channel.WebRequestHandler; } } + internal ProviderAssociationStore AssociationStore { get; private set; } + /// <summary> /// Gets the relying party used for discovery of identifiers sent in unsolicited assertions. /// </summary> diff --git a/src/DotNetOpenAuth/OpenId/Provider/ProviderAssociationStore.cs b/src/DotNetOpenAuth/OpenId/Provider/ProviderAssociationStore.cs new file mode 100644 index 0000000..e503317 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Provider/ProviderAssociationStore.cs @@ -0,0 +1,49 @@ +//----------------------------------------------------------------------- +// <copyright file="ProviderAssociationStore.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + internal class ProviderAssociationStore { + /// <summary> + /// Initializes a new instance of the <see cref="ProviderAssociationStore"/> class. + /// </summary> + internal ProviderAssociationStore() { + this.Secret = MessagingUtilities.GetCryptoRandomData(16); + } + + /// <summary> + /// Gets or sets the symmetric secret this Provider uses for protecting messages to itself. + /// </summary> + internal byte[] Secret { get; set; } + + internal string Encode(AssociationDataBag associationDataBag) { + var formatter = AssociationDataBag.CreateFormatter(this.Secret); + return formatter.Serialize(associationDataBag); + } + + internal Association Decode(IProtocolMessage containingMessage, AssociationRelyingPartyType associationType, string handle) { + var formatter = AssociationDataBag.CreateFormatter(this.Secret); + var bag = formatter.Deserialize(containingMessage, handle); + ErrorUtilities.VerifyProtocol(bag.AssociationType == associationType, "Unexpected association type."); + Association assoc = Association.Deserialize(handle, bag.ExpiresUtc, bag.Secret); + return assoc; + } + + internal bool IsValid(IProtocolMessage containingMessage, AssociationRelyingPartyType associationType, string handle) { + try { + this.Decode(containingMessage, associationType, handle); + return true; + } catch (ProtocolException) { + return false; + } + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Provider/StandardProviderApplicationStore.cs b/src/DotNetOpenAuth/OpenId/Provider/StandardProviderApplicationStore.cs index 4fa2d64..59f79ba 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/StandardProviderApplicationStore.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/StandardProviderApplicationStore.cs @@ -28,80 +28,12 @@ namespace DotNetOpenAuth.OpenId.Provider { private readonly INonceStore nonceStore; /// <summary> - /// The association store to use. - /// </summary> - private readonly IAssociationStore<AssociationRelyingPartyType> associationStore; - - /// <summary> /// Initializes a new instance of the <see cref="StandardProviderApplicationStore"/> class. /// </summary> public StandardProviderApplicationStore() { this.nonceStore = new NonceMemoryStore(DotNetOpenAuthSection.Configuration.OpenId.MaxAuthenticationTime); - this.associationStore = new AssociationMemoryStore<AssociationRelyingPartyType>(); - } - - #region IAssociationStore<AssociationRelyingPartyType> Members - - /// <summary> - /// Saves an <see cref="Association"/> for later recall. - /// </summary> - /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for providers).</param> - /// <param name="association">The association to store.</param> - public void StoreAssociation(AssociationRelyingPartyType distinguishingFactor, Association association) { - this.associationStore.StoreAssociation(distinguishingFactor, association); - } - - /// <summary> - /// Gets the best association (the one with the longest remaining life) for a given key. - /// </summary> - /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - /// <param name="securityRequirements">The security requirements that the returned association must meet.</param> - /// <returns> - /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key. - /// </returns> - /// <remarks> - /// In the event that multiple associations exist for the given - /// <paramref name="distinguishingFactor"/>, it is important for the - /// implementation for this method to use the <paramref name="securityRequirements"/> - /// to pick the best (highest grade or longest living as the host's policy may dictate) - /// association that fits the security requirements. - /// Associations that are returned that do not meet the security requirements will be - /// ignored and a new association created. - /// </remarks> - public Association GetAssociation(AssociationRelyingPartyType distinguishingFactor, SecuritySettings securityRequirements) { - return this.associationStore.GetAssociation(distinguishingFactor, securityRequirements); } - /// <summary> - /// Gets the association for a given key and handle. - /// </summary> - /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - /// <param name="handle">The handle of the specific association that must be recalled.</param> - /// <returns> - /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key and handle. - /// </returns> - public Association GetAssociation(AssociationRelyingPartyType distinguishingFactor, string handle) { - return this.associationStore.GetAssociation(distinguishingFactor, handle); - } - - /// <summary> - /// Removes a specified handle that may exist in the store. - /// </summary> - /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - /// <param name="handle">The handle of the specific association that must be deleted.</param> - /// <returns> - /// True if the association existed in this store previous to this call. - /// </returns> - /// <remarks> - /// No exception should be thrown if the association does not exist in the store - /// before this call. - /// </remarks> - public bool RemoveAssociation(AssociationRelyingPartyType distinguishingFactor, string handle) { - return this.associationStore.RemoveAssociation(distinguishingFactor, handle); - } - - #endregion - #region INonceStore Members /// <summary> diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/AssociationManager.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/AssociationManager.cs index 1ae2726..90a6228 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/AssociationManager.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/AssociationManager.cs @@ -23,7 +23,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// The storage to use for saving and retrieving associations. May be null. /// </summary> - private readonly IAssociationStore<Uri> associationStore; + private readonly IAssociationStore associationStore; /// <summary> /// Backing field for the <see cref="Channel"/> property. @@ -41,7 +41,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <param name="channel">The channel the relying party is using.</param> /// <param name="associationStore">The association store. May be null for dumb mode relying parties.</param> /// <param name="securitySettings">The security settings.</param> - internal AssociationManager(Channel channel, IAssociationStore<Uri> associationStore, RelyingPartySecuritySettings securitySettings) { + internal AssociationManager(Channel channel, IAssociationStore associationStore, RelyingPartySecuritySettings securitySettings) { Contract.Requires<ArgumentNullException>(channel != null); Contract.Requires<ArgumentNullException>(securitySettings != null); @@ -93,7 +93,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// Gets the storage to use for saving and retrieving associations. May be null. /// </summary> - internal IAssociationStore<Uri> AssociationStoreTestHook { + internal IAssociationStore AssociationStoreTestHook { get { return this.associationStore; } } @@ -196,7 +196,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { var associateSuccessfulResponse = associateResponse as AssociateSuccessfulResponse; var associateUnsuccessfulResponse = associateResponse as AssociateUnsuccessfulResponse; if (associateSuccessfulResponse != null) { - Association association = associateSuccessfulResponse.CreateAssociation(associateRequest, null); + Association association = associateSuccessfulResponse.CreateAssociation(associateRequest, null, null); this.associationStore.StoreAssociation(provider.Uri, association); return association; } else if (associateUnsuccessfulResponse != null) { diff --git a/src/DotNetOpenAuth/OpenId/AssociationMemoryStore.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/AssociationMemoryStore.cs index 7a20fd5..00540b4 100644 --- a/src/DotNetOpenAuth/OpenId/AssociationMemoryStore.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/AssociationMemoryStore.cs @@ -4,21 +4,22 @@ // </copyright> //----------------------------------------------------------------------- -namespace DotNetOpenAuth.OpenId { +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; using System.Collections.Generic; using System.Linq; /// <summary> /// Manages a set of associations in memory only (no database). /// </summary> - /// <typeparam name="TKey">The type of the key.</typeparam> + /// <typeparam name="Uri">The type of the key.</typeparam> /// <remarks> /// This class should be used for low-to-medium traffic relying party sites that can afford to lose associations /// if the app pool was ever restarted. High traffic relying parties and providers should write their own - /// implementation of <see cref="IAssociationStore<TKey>"/> that works against their own database schema + /// implementation of <see cref="IAssociationStore"/> that works against their own database schema /// to allow for persistance and recall of associations across servers in a web farm and server restarts. /// </remarks> - internal class AssociationMemoryStore<TKey> : IAssociationStore<TKey> { + internal class AssociationMemoryStore : IAssociationStore { /// <summary> /// How many association store requests should occur between each spring cleaning. /// </summary> @@ -28,7 +29,7 @@ namespace DotNetOpenAuth.OpenId { /// For Relying Parties, this maps OP Endpoints to a set of associations with that endpoint. /// For Providers, this keeps smart and dumb associations in two distinct pools. /// </summary> - private Dictionary<TKey, Associations> serverAssocsTable = new Dictionary<TKey, Associations>(); + private Dictionary<Uri, Associations> serverAssocsTable = new Dictionary<Uri, Associations>(); /// <summary> /// A counter to track how close we are to an expired association cleaning run. @@ -38,14 +39,14 @@ namespace DotNetOpenAuth.OpenId { /// <summary> /// Stores a given association for later recall. /// </summary> - /// <param name="distinguishingFactor">The distinguishing factor, either an OP Endpoint or smart/dumb mode.</param> + /// <param name="providerEndpoint">The distinguishing factor, either an OP Endpoint or smart/dumb mode.</param> /// <param name="association">The association to store.</param> - public void StoreAssociation(TKey distinguishingFactor, Association association) { + public void StoreAssociation(Uri providerEndpoint, Association association) { lock (this) { - if (!this.serverAssocsTable.ContainsKey(distinguishingFactor)) { - this.serverAssocsTable.Add(distinguishingFactor, new Associations()); + if (!this.serverAssocsTable.ContainsKey(providerEndpoint)) { + this.serverAssocsTable.Add(providerEndpoint, new Associations()); } - Associations server_assocs = this.serverAssocsTable[distinguishingFactor]; + Associations server_assocs = this.serverAssocsTable[providerEndpoint]; server_assocs.Set(association); @@ -61,35 +62,35 @@ namespace DotNetOpenAuth.OpenId { /// <summary> /// Gets the best association (the one with the longest remaining life) for a given key. /// </summary> - /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> + /// <param name="providerEndpoint">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> /// <param name="securitySettings">The security settings.</param> /// <returns> /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key. /// </returns> - public Association GetAssociation(TKey distinguishingFactor, SecuritySettings securitySettings) { + public Association GetAssociation(Uri providerEndpoint, SecuritySettings securitySettings) { lock (this) { - return this.GetServerAssociations(distinguishingFactor).Best.FirstOrDefault(assoc => securitySettings.IsAssociationInPermittedRange(assoc)); + return this.GetServerAssociations(providerEndpoint).Best.FirstOrDefault(assoc => securitySettings.IsAssociationInPermittedRange(assoc)); } } /// <summary> /// Gets the association for a given key and handle. /// </summary> - /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> + /// <param name="providerEndpoint">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> /// <param name="handle">The handle of the specific association that must be recalled.</param> /// <returns> /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key and handle. /// </returns> - public Association GetAssociation(TKey distinguishingFactor, string handle) { + public Association GetAssociation(Uri providerEndpoint, string handle) { lock (this) { - return this.GetServerAssociations(distinguishingFactor).Get(handle); + return this.GetServerAssociations(providerEndpoint).Get(handle); } } /// <summary> /// Removes a specified handle that may exist in the store. /// </summary> - /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> + /// <param name="providerEndpoint">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> /// <param name="handle">The handle of the specific association that must be deleted.</param> /// <returns> /// True if the association existed in this store previous to this call. @@ -98,24 +99,24 @@ namespace DotNetOpenAuth.OpenId { /// No exception should be thrown if the association does not exist in the store /// before this call. /// </remarks> - public bool RemoveAssociation(TKey distinguishingFactor, string handle) { + public bool RemoveAssociation(Uri providerEndpoint, string handle) { lock (this) { - return this.GetServerAssociations(distinguishingFactor).Remove(handle); + return this.GetServerAssociations(providerEndpoint).Remove(handle); } } /// <summary> /// Gets the server associations for a given OP Endpoint or dumb/smart mode. /// </summary> - /// <param name="distinguishingFactor">The distinguishing factor, either an OP Endpoint (for relying parties) or smart/dumb (for providers).</param> - /// <returns>The collection of associations that fit the <paramref name="distinguishingFactor"/>.</returns> - internal Associations GetServerAssociations(TKey distinguishingFactor) { + /// <param name="providerEndpoint">The distinguishing factor, either an OP Endpoint (for relying parties) or smart/dumb (for providers).</param> + /// <returns>The collection of associations that fit the <paramref name="providerEndpoint"/>.</returns> + internal Associations GetServerAssociations(Uri providerEndpoint) { lock (this) { - if (!this.serverAssocsTable.ContainsKey(distinguishingFactor)) { - this.serverAssocsTable.Add(distinguishingFactor, new Associations()); + if (!this.serverAssocsTable.ContainsKey(providerEndpoint)) { + this.serverAssocsTable.Add(providerEndpoint, new Associations()); } - return this.serverAssocsTable[distinguishingFactor]; + return this.serverAssocsTable[providerEndpoint]; } } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs index 3a17263..be06a23 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs @@ -212,7 +212,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> /// </remarks> public void AddCallbackArguments(IDictionary<string, string> arguments) { - ErrorUtilities.VerifyOperation(this.RelyingParty.CanSignCallbackArguments, OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IAssociationStore<Uri>).Name, typeof(OpenIdRelyingParty).Name); + ErrorUtilities.VerifyOperation(this.RelyingParty.CanSignCallbackArguments, OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IAssociationStore).Name, typeof(OpenIdRelyingParty).Name); this.returnToArgsMustBeSigned = true; foreach (var pair in arguments) { @@ -239,7 +239,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> /// </remarks> public void AddCallbackArguments(string key, string value) { - ErrorUtilities.VerifyOperation(this.RelyingParty.CanSignCallbackArguments, OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IAssociationStore<Uri>).Name, typeof(OpenIdRelyingParty).Name); + ErrorUtilities.VerifyOperation(this.RelyingParty.CanSignCallbackArguments, OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IAssociationStore).Name, typeof(OpenIdRelyingParty).Name); this.returnToArgsMustBeSigned = true; this.returnToArgs.Add(key, value); @@ -260,7 +260,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> /// </remarks> public void SetCallbackArgument(string key, string value) { - ErrorUtilities.VerifyOperation(this.RelyingParty.CanSignCallbackArguments, OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IAssociationStore<Uri>).Name, typeof(OpenIdRelyingParty).Name); + ErrorUtilities.VerifyOperation(this.RelyingParty.CanSignCallbackArguments, OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IAssociationStore).Name, typeof(OpenIdRelyingParty).Name); this.returnToArgsMustBeSigned = true; this.returnToArgs[key] = value; diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IAssociationStore.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IAssociationStore.cs new file mode 100644 index 0000000..63c6526 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/IAssociationStore.cs @@ -0,0 +1,151 @@ +//----------------------------------------------------------------------- +// <copyright file="IAssociationStore.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Diagnostics.Contracts; + + /// <summary> + /// Stores <see cref="Association"/>s for lookup by their handle, keeping + /// associations separated by a given distinguishing factor (like which server the + /// association is with). + /// </summary> + /// <remarks> + /// Expired associations should be periodically cleared out of an association store. + /// This should be done frequently enough to avoid a memory leak, but sparingly enough + /// to not be a performance drain. Because this balance can vary by host, it is the + /// responsibility of the host to initiate this cleaning. + /// </remarks> + [ContractClass(typeof(IAssociationStoreContract))] + public interface IAssociationStore { + /// <summary> + /// Saves an <see cref="Association"/> for later recall. + /// </summary> + /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param> + /// <param name="association">The association to store.</param> + /// <remarks> + /// TODO: what should implementations do on association handle conflict? + /// </remarks> + void StoreAssociation(Uri providerEndpoint, Association association); + + /// <summary> + /// Gets the best association (the one with the longest remaining life) for a given key. + /// </summary> + /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param> + /// <param name="securityRequirements">The security requirements that the returned association must meet.</param> + /// <returns> + /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key. + /// </returns> + /// <remarks> + /// In the event that multiple associations exist for the given + /// <paramref name="providerEndpoint"/>, it is important for the + /// implementation for this method to use the <paramref name="securityRequirements"/> + /// to pick the best (highest grade or longest living as the host's policy may dictate) + /// association that fits the security requirements. + /// Associations that are returned that do not meet the security requirements will be + /// ignored and a new association created. + /// </remarks> + Association GetAssociation(Uri providerEndpoint, SecuritySettings securityRequirements); + + /// <summary> + /// Gets the association for a given key and handle. + /// </summary> + /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param> + /// <param name="handle">The handle of the specific association that must be recalled.</param> + /// <returns>The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key and handle.</returns> + Association GetAssociation(Uri providerEndpoint, string handle); + + /// <summary>Removes a specified handle that may exist in the store.</summary> + /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param> + /// <param name="handle">The handle of the specific association that must be deleted.</param> + /// <returns>True if the association existed in this store previous to this call.</returns> + /// <remarks> + /// No exception should be thrown if the association does not exist in the store + /// before this call. + /// </remarks> + bool RemoveAssociation(Uri providerEndpoint, string handle); + } + + /// <summary> + /// Code Contract for the <see cref="IAssociationStore<Uri>"/> class. + /// </summary> + /// <typeparam name="Uri">The type of the key.</typeparam> + [ContractClassFor(typeof(IAssociationStore))] + internal abstract class IAssociationStoreContract : IAssociationStore { + #region IAssociationStore Members + + /// <summary> + /// Saves an <see cref="Association"/> for later recall. + /// </summary> + /// <param name="providerEndpoint">The Uri (for relying parties) or Smart/Dumb (for providers).</param> + /// <param name="association">The association to store.</param> + /// <remarks> + /// TODO: what should implementations do on association handle conflict? + /// </remarks> + void IAssociationStore.StoreAssociation(Uri providerEndpoint, Association association) { + Contract.Requires<ArgumentNullException>(providerEndpoint != null); + Contract.Requires<ArgumentNullException>(association != null); + throw new NotImplementedException(); + } + + /// <summary> + /// Gets the best association (the one with the longest remaining life) for a given key. + /// </summary> + /// <param name="providerEndpoint">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> + /// <param name="securityRequirements">The security requirements that the returned association must meet.</param> + /// <returns> + /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key. + /// </returns> + /// <remarks> + /// In the event that multiple associations exist for the given + /// <paramref name="providerEndpoint"/>, it is important for the + /// implementation for this method to use the <paramref name="securityRequirements"/> + /// to pick the best (highest grade or longest living as the host's policy may dictate) + /// association that fits the security requirements. + /// Associations that are returned that do not meet the security requirements will be + /// ignored and a new association created. + /// </remarks> + Association IAssociationStore.GetAssociation(Uri providerEndpoint, SecuritySettings securityRequirements) { + Contract.Requires<ArgumentNullException>(providerEndpoint != null); + Contract.Requires<ArgumentNullException>(securityRequirements != null); + throw new NotImplementedException(); + } + + /// <summary> + /// Gets the association for a given key and handle. + /// </summary> + /// <param name="providerEndpoint">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> + /// <param name="handle">The handle of the specific association that must be recalled.</param> + /// <returns> + /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key and handle. + /// </returns> + Association IAssociationStore.GetAssociation(Uri providerEndpoint, string handle) { + Contract.Requires<ArgumentNullException>(providerEndpoint != null); + Contract.Requires(!String.IsNullOrEmpty(handle)); + throw new NotImplementedException(); + } + + /// <summary> + /// Removes a specified handle that may exist in the store. + /// </summary> + /// <param name="providerEndpoint">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> + /// <param name="handle">The handle of the specific association that must be deleted.</param> + /// <returns> + /// True if the association existed in this store previous to this call. + /// </returns> + /// <remarks> + /// No exception should be thrown if the association does not exist in the store + /// before this call. + /// </remarks> + bool IAssociationStore.RemoveAssociation(Uri providerEndpoint, string handle) { + Contract.Requires<ArgumentNullException>(providerEndpoint != null); + Contract.Requires(!String.IsNullOrEmpty(handle)); + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyApplicationStore.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyApplicationStore.cs index 77838c8..519dae8 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyApplicationStore.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyApplicationStore.cs @@ -16,6 +16,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// A hybrid of all the store interfaces that a Relying Party requires in order /// to operate in "smart" mode. /// </summary> - public interface IRelyingPartyApplicationStore : IAssociationStore<Uri>, INonceStore { + public interface IRelyingPartyApplicationStore : IAssociationStore, INonceStore { } } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs index 57c1f05..02c821f 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs @@ -110,7 +110,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <param name="associationStore">The association store. If null, the relying party will always operate in "dumb mode".</param> /// <param name="nonceStore">The nonce store to use. If null, the relying party will always operate in "dumb mode".</param> [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Unavoidable")] - private OpenIdRelyingParty(IAssociationStore<Uri> associationStore, INonceStore nonceStore) { + private OpenIdRelyingParty(IAssociationStore associationStore, INonceStore nonceStore) { // If we are a smart-mode RP (supporting associations), then we MUST also be // capable of storing nonces to prevent replay attacks. // If we're a dumb-mode RP, then 2.0 OPs are responsible for preventing replays. diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs index 5cfa191..fb1970c 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs @@ -174,7 +174,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { if (this.response.ReturnToParametersSignatureValidated) { return this.GetUntrustedCallbackArgument(key); } else { - Logger.OpenId.WarnFormat(OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IAssociationStore<Uri>).Name, typeof(OpenIdRelyingParty).Name); + Logger.OpenId.WarnFormat(OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IAssociationStore).Name, typeof(OpenIdRelyingParty).Name); return null; } } @@ -214,7 +214,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { if (this.response.ReturnToParametersSignatureValidated) { return this.GetUntrustedCallbackArguments(); } else { - Logger.OpenId.WarnFormat(OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IAssociationStore<Uri>).Name, typeof(OpenIdRelyingParty).Name); + Logger.OpenId.WarnFormat(OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IAssociationStore).Name, typeof(OpenIdRelyingParty).Name); return EmptyDictionary<string, string>.Instance; } } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/PrivateSecretManager.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/PrivateSecretManager.cs index 6d55e39..dfd8f5d 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/PrivateSecretManager.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/PrivateSecretManager.cs @@ -36,14 +36,14 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// The association store /// </summary> - private IAssociationStore<Uri> store; + private IAssociationStore store; /// <summary> /// Initializes a new instance of the <see cref="PrivateSecretManager"/> class. /// </summary> /// <param name="securitySettings">The security settings.</param> /// <param name="store">The association store.</param> - internal PrivateSecretManager(RelyingPartySecuritySettings securitySettings, IAssociationStore<Uri> store) { + internal PrivateSecretManager(RelyingPartySecuritySettings securitySettings, IAssociationStore store) { Contract.Requires<ArgumentNullException>(securitySettings != null); Contract.Requires<ArgumentNullException>(store != null); diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/StandardRelyingPartyApplicationStore.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/StandardRelyingPartyApplicationStore.cs index fdb6931..3e15f6a 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/StandardRelyingPartyApplicationStore.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/StandardRelyingPartyApplicationStore.cs @@ -23,17 +23,17 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// The association store to use. /// </summary> - private readonly IAssociationStore<Uri> associationStore; + private readonly IAssociationStore associationStore; /// <summary> /// Initializes a new instance of the <see cref="StandardRelyingPartyApplicationStore"/> class. /// </summary> public StandardRelyingPartyApplicationStore() { this.nonceStore = new NonceMemoryStore(DotNetOpenAuthSection.Configuration.OpenId.MaxAuthenticationTime); - this.associationStore = new AssociationMemoryStore<Uri>(); + this.associationStore = new AssociationMemoryStore(); } - #region IAssociationStore<Uri> Members + #region IAssociationStore Members /// <summary> /// Saves an <see cref="Association"/> for later recall. diff --git a/src/DotNetOpenAuth/Reporting.cs b/src/DotNetOpenAuth/Reporting.cs index 03d7460..1673881 100644 --- a/src/DotNetOpenAuth/Reporting.cs +++ b/src/DotNetOpenAuth/Reporting.cs @@ -237,6 +237,29 @@ namespace DotNetOpenAuth { /// </summary> /// <param name="value">The object whose type is the feature to set as used.</param> /// <param name="dependency1">Some dependency used by <paramref name="value"/>.</param> + internal static void RecordFeatureAndDependencyUse(object value, object dependency1) { + Contract.Requires(value != null); + + // In release builds, just quietly return. + if (value == null) { + return; + } + + if (Enabled && Configuration.IncludeFeatureUsage) { + StringBuilder builder = new StringBuilder(); + builder.Append(value.GetType().Name); + builder.Append(" "); + builder.Append(dependency1 != null ? dependency1.GetType().Name : "(null)"); + observedFeatures.Add(builder.ToString()); + Touch(); + } + } + + /// <summary> + /// Records the use of a feature by object type. + /// </summary> + /// <param name="value">The object whose type is the feature to set as used.</param> + /// <param name="dependency1">Some dependency used by <paramref name="value"/>.</param> /// <param name="dependency2">Some dependency used by <paramref name="value"/>.</param> internal static void RecordFeatureAndDependencyUse(object value, object dependency1, object dependency2) { Contract.Requires(value != null); |