//----------------------------------------------------------------------- // // Copyright (c) Outercurve Foundation. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOpenAuth.OpenId.RelyingParty { using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Linq; using DotNetOpenAuth.Messaging; /// /// A dictionary of handle/Association pairs. /// /// /// Each method is locked, even if it is only one line, so that they are thread safe /// against each other, particularly the ones that enumerate over the list, since they /// can break if the collection is changed by another thread during enumeration. /// [DebuggerDisplay("Count = {assocs.Count}")] [ContractVerification(true)] internal class Associations { /// /// The lookup table where keys are the association handles and values are the associations themselves. /// [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] private readonly KeyedCollection associations = new KeyedCollectionDelegate(assoc => assoc.Handle); /// /// Initializes a new instance of the class. /// public Associations() { } /// /// Gets the s ordered in order of descending issue date /// (most recently issued comes first). An empty sequence if no valid associations exist. /// /// /// This property is used by relying parties that are initiating authentication requests. /// It does not apply to Providers, which always need a specific association by handle. /// public IEnumerable Best { get { Contract.Ensures(Contract.Result>() != null); lock (this.associations) { return this.associations.OrderByDescending(assoc => assoc.Issued); } } } /// /// Stores an in the collection. /// /// The association to add to the collection. public void Set(Association association) { Requires.NotNull(association, "association"); Contract.Ensures(this.Get(association.Handle) == association); lock (this.associations) { this.associations.Remove(association.Handle); // just in case one already exists. this.associations.Add(association); } Contract.Assume(this.Get(association.Handle) == association); } /// /// Returns the with the given handle. Null if not found. /// /// The handle to the required association. /// The desired association, or null if none with the given handle could be found. [Pure] public Association Get(string handle) { Requires.NotNullOrEmpty(handle, "handle"); lock (this.associations) { if (this.associations.Contains(handle)) { return this.associations[handle]; } else { return null; } } } /// /// Removes the with the given handle. /// /// The handle to the required association. /// Whether an with the given handle was in the collection for removal. public bool Remove(string handle) { Requires.NotNullOrEmpty(handle, "handle"); lock (this.associations) { return this.associations.Remove(handle); } } /// /// Removes all expired associations from the collection. /// public void ClearExpired() { lock (this.associations) { var expireds = this.associations.Where(assoc => assoc.IsExpired).ToList(); foreach (Association assoc in expireds) { this.associations.Remove(assoc.Handle); } } } #if CONTRACTS_FULL /// /// Verifies conditions that should be true for any valid state of this object. /// [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] [ContractInvariantMethod] private void ObjectInvariant() { Contract.Invariant(this.associations != null); } #endif } }