diff options
Diffstat (limited to 'projecttemplates/RelyingPartyLogic/NonceDbStore.cs')
-rw-r--r-- | projecttemplates/RelyingPartyLogic/NonceDbStore.cs | 132 |
1 files changed, 132 insertions, 0 deletions
diff --git a/projecttemplates/RelyingPartyLogic/NonceDbStore.cs b/projecttemplates/RelyingPartyLogic/NonceDbStore.cs new file mode 100644 index 0000000..2f3c670 --- /dev/null +++ b/projecttemplates/RelyingPartyLogic/NonceDbStore.cs @@ -0,0 +1,132 @@ +//----------------------------------------------------------------------- +// <copyright file="NonceDbStore.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace RelyingPartyLogic { + using System; + using System.Collections.Generic; + using System.Data; + using System.Data.Common; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging.Bindings; + + /// <summary> + /// A database-backed nonce store for OpenID and OAuth services. + /// </summary> + public class NonceDbStore : INonceStore { + private const int NonceClearingInterval = 5; + + /// <summary> + /// A counter that tracks how many nonce stores have been done. + /// </summary> + private static int nonceClearingCounter; + + /// <summary> + /// Initializes a new instance of the <see cref="NonceDbStore"/> class. + /// </summary> + public NonceDbStore() { + } + + #region INonceStore Members + + /// <summary> + /// Stores a given nonce and timestamp. + /// </summary> + /// <param name="context">The context, or namespace, within which the + /// <paramref name="nonce"/> must be unique. + /// The context SHOULD be treated as case-sensitive. + /// The value will never be <c>null</c> but may be the empty string.</param> + /// <param name="nonce">A series of random characters.</param> + /// <param name="timestampUtc">The UTC timestamp that together with the nonce string make it unique + /// within the given <paramref name="context"/>. + /// The timestamp may also be used by the data store to clear out old nonces.</param> + /// <returns> + /// True if the context+nonce+timestamp (combination) was not previously in the database. + /// False if the nonce was stored previously with the same timestamp and context. + /// </returns> + /// <remarks> + /// The nonce must be stored for no less than the maximum time window a message may + /// be processed within before being discarded as an expired message. + /// This maximum message age can be looked up via the + /// <see cref="DotNetOpenAuth.Configuration.MessagingElement.MaximumMessageLifetime"/> + /// property, accessible via the <see cref="DotNetOpenAuth.Configuration.DotNetOpenAuthSection.Configuration"/> + /// property. + /// </remarks> + public bool StoreNonce(string context, string nonce, DateTime timestampUtc) { + try { + using (var dataContext = new TransactedDatabaseEntities(IsolationLevel.ReadCommitted)) { + Nonce nonceEntity = new Nonce { + Context = context, + Code = nonce, + IssuedUtc = timestampUtc, + ExpiresUtc = timestampUtc + DotNetOpenAuthSection.Configuration.Messaging.MaximumMessageLifetime, + }; + + // The database columns [context] and [code] MUST be using + // a case sensitive collation for this to be secure. + dataContext.AddToNonces(nonceEntity); + } + } catch (UpdateException) { + // A nonce collision + return false; + } + + // Only clear nonces after successfully storing a nonce. + // This mitigates cheap DoS attacks that take up a lot of + // database cycles. + ClearNoncesIfAppropriate(); + return true; + } + + #endregion + + /// <summary> + /// Clears the nonces if appropriate. + /// </summary> + private static void ClearNoncesIfAppropriate() { + if (++nonceClearingCounter % NonceClearingInterval == 0) { + using (var dataContext = new TransactedDatabaseEntities(IsolationLevel.ReadCommitted)) { + dataContext.ClearExpiredNonces(); + } + } + } + + /// <summary> + /// A transacted data context. + /// </summary> + protected class TransactedDatabaseEntities : DatabaseEntities { + /// <summary> + /// The transaction for this data context. + /// </summary> + private DbTransaction transaction; + + /// <summary> + /// Initializes a new instance of the <see cref="TransactedDatabaseEntities"/> class. + /// </summary> + /// <param name="isolationLevel">The isolation level.</param> + public TransactedDatabaseEntities(IsolationLevel isolationLevel) { + this.Connection.Open(); + this.transaction = this.Connection.BeginTransaction(isolationLevel); + } + + /// <summary> + /// Releases the resources used by the object context. + /// </summary> + /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param> + protected override void Dispose(bool disposing) { + try { + this.SaveChanges(); + this.transaction.Commit(); + } finally { + this.Connection.Close(); + } + + base.Dispose(disposing); + } + } + } +} |