diff options
Diffstat (limited to 'src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/Associations.cs')
-rw-r--r-- | src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/Associations.cs | 127 |
1 files changed, 127 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/Associations.cs b/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/Associations.cs new file mode 100644 index 0000000..b171bec --- /dev/null +++ b/src/DotNetOpenAuth.OpenId/OpenId/RelyingParty/Associations.cs @@ -0,0 +1,127 @@ +//----------------------------------------------------------------------- +// <copyright file="Associations.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +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; + + /// <summary> + /// A dictionary of handle/Association pairs. + /// </summary> + /// <remarks> + /// 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. + /// </remarks> + [DebuggerDisplay("Count = {assocs.Count}")] + [ContractVerification(true)] + internal class Associations { + /// <summary> + /// The lookup table where keys are the association handles and values are the associations themselves. + /// </summary> + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + private readonly KeyedCollection<string, Association> associations = new KeyedCollectionDelegate<string, Association>(assoc => assoc.Handle); + + /// <summary> + /// Initializes a new instance of the <see cref="Associations"/> class. + /// </summary> + public Associations() { + } + + /// <summary> + /// Gets the <see cref="Association"/>s ordered in order of descending issue date + /// (most recently issued comes first). An empty sequence if no valid associations exist. + /// </summary> + /// <remarks> + /// 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. + /// </remarks> + public IEnumerable<Association> Best { + get { + Contract.Ensures(Contract.Result<IEnumerable<Association>>() != null); + + lock (this.associations) { + return this.associations.OrderByDescending(assoc => assoc.Issued); + } + } + } + + /// <summary> + /// Stores an <see cref="Association"/> in the collection. + /// </summary> + /// <param name="association">The association to add to the collection.</param> + public void Set(Association association) { + Contract.Requires<ArgumentNullException>(association != null); + 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); + } + + /// <summary> + /// Returns the <see cref="Association"/> with the given handle. Null if not found. + /// </summary> + /// <param name="handle">The handle to the required association.</param> + /// <returns>The desired association, or null if none with the given handle could be found.</returns> + [Pure] + public Association Get(string handle) { + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(handle)); + + lock (this.associations) { + if (this.associations.Contains(handle)) { + return this.associations[handle]; + } else { + return null; + } + } + } + + /// <summary> + /// Removes the <see cref="Association"/> with the given handle. + /// </summary> + /// <param name="handle">The handle to the required association.</param> + /// <returns>Whether an <see cref="Association"/> with the given handle was in the collection for removal.</returns> + public bool Remove(string handle) { + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(handle)); + lock (this.associations) { + return this.associations.Remove(handle); + } + } + + /// <summary> + /// Removes all expired associations from the collection. + /// </summary> + 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 + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [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 + } +} |