//----------------------------------------------------------------------- // // Copyright (c) Andrew Arnott. All rights reserved. // //----------------------------------------------------------------------- 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; /// /// A database-backed nonce store for OpenID and OAuth services. /// public class NonceDbStore : INonceStore { private const int NonceClearingInterval = 5; /// /// A counter that tracks how many nonce stores have been done. /// private static int nonceClearingCounter; /// /// Initializes a new instance of the class. /// public NonceDbStore() { } #region INonceStore Members /// /// Stores a given nonce and timestamp. /// /// The context, or namespace, within which the /// must be unique. /// The context SHOULD be treated as case-sensitive. /// The value will never be null but may be the empty string. /// A series of random characters. /// The UTC timestamp that together with the nonce string make it unique /// within the given . /// The timestamp may also be used by the data store to clear out old nonces. /// /// 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. /// /// /// 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 /// /// property, accessible via the /// property. /// 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 /// /// Clears the nonces if appropriate. /// private static void ClearNoncesIfAppropriate() { if (++nonceClearingCounter % NonceClearingInterval == 0) { using (var dataContext = new TransactedDatabaseEntities(IsolationLevel.ReadCommitted)) { dataContext.ClearExpiredNonces(); } } } /// /// A transacted data context. /// protected class TransactedDatabaseEntities : DatabaseEntities { /// /// The transaction for this data context. /// private DbTransaction transaction; /// /// Initializes a new instance of the class. /// /// The isolation level. public TransactedDatabaseEntities(IsolationLevel isolationLevel) { this.Connection.Open(); this.transaction = this.Connection.BeginTransaction(isolationLevel); } /// /// Releases the resources used by the object context. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected override void Dispose(bool disposing) { try { this.SaveChanges(); this.transaction.Commit(); } finally { this.Connection.Close(); } base.Dispose(disposing); } } } }