//----------------------------------------------------------------------- // // Copyright (c) Andrew Arnott. All rights reserved. // //----------------------------------------------------------------------- namespace RelyingPartyLogic { using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Text; using DotNetOpenAuth.OpenId; using DotNetOpenAuth.OpenId.RelyingParty; /// /// A database-backed state store for OpenID relying parties. /// public class RelyingPartyApplicationDbStore : NonceDbStore, IRelyingPartyApplicationStore { /// /// Initializes a new instance of the class. /// public RelyingPartyApplicationDbStore() { } #region IAssociationStore Members /// /// Saves an for later recall. /// /// The Uri (for relying parties) or Smart/Dumb (for providers). /// The association to store. /// /// TODO: what should implementations do on association handle conflict? /// public void StoreAssociation(Uri distinguishingFactor, Association association) { using (var dataContext = new TransactedDatabaseEntities(System.Data.IsolationLevel.ReadCommitted)) { var sharedAssociation = new OpenIdAssociation { DistinguishingFactor = distinguishingFactor.AbsoluteUri, AssociationHandle = association.Handle, ExpirationUtc = association.Expires, PrivateData = association.SerializePrivateData(), }; dataContext.AddToOpenIdAssociations(sharedAssociation); } } /// /// Gets the best association (the one with the longest remaining life) for a given key. /// /// The Uri (for relying parties) or Smart/Dumb (for Providers). /// The security requirements that the returned association must meet. /// /// The requested association, or null if no unexpired s exist for the given key. /// /// /// In the event that multiple associations exist for the given /// , it is important for the /// implementation for this method to use the /// 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. /// public Association GetAssociation(Uri distinguishingFactor, SecuritySettings securityRequirements) { using (var dataContext = new TransactedDatabaseEntities(System.Data.IsolationLevel.ReadCommitted)) { var relevantAssociations = from assoc in dataContext.OpenIdAssociations where assoc.DistinguishingFactor == distinguishingFactor.AbsoluteUri where assoc.ExpirationUtc > DateTime.UtcNow where assoc.PrivateDataLength * 8 >= securityRequirements.MinimumHashBitLength where assoc.PrivateDataLength * 8 <= securityRequirements.MaximumHashBitLength orderby assoc.ExpirationUtc descending select assoc; var qualifyingAssociations = relevantAssociations.AsEnumerable() .Select(assoc => DeserializeAssociation(assoc)); return qualifyingAssociations.FirstOrDefault(); } } /// /// Gets the association for a given key and handle. /// /// The Uri (for relying parties) or Smart/Dumb (for Providers). /// The handle of the specific association that must be recalled. /// /// The requested association, or null if no unexpired s exist for the given key and handle. /// public Association GetAssociation(Uri distinguishingFactor, string handle) { using (var dataContext = new TransactedDatabaseEntities(System.Data.IsolationLevel.ReadCommitted)) { var associations = from assoc in dataContext.OpenIdAssociations where assoc.DistinguishingFactor == distinguishingFactor.AbsoluteUri where assoc.AssociationHandle == handle where assoc.ExpirationUtc > DateTime.UtcNow select assoc; return associations.AsEnumerable() .Select(assoc => DeserializeAssociation(assoc)) .FirstOrDefault(); } } /// /// Removes a specified handle that may exist in the store. /// /// The Uri (for relying parties) or Smart/Dumb (for Providers). /// The handle of the specific association that must be deleted. /// /// True if the association existed in this store previous to this call. /// /// /// No exception should be thrown if the association does not exist in the store /// before this call. /// public bool RemoveAssociation(Uri distinguishingFactor, string handle) { using (var dataContext = new TransactedDatabaseEntities(System.Data.IsolationLevel.ReadCommitted)) { var association = dataContext.OpenIdAssociations.FirstOrDefault(a => a.DistinguishingFactor == distinguishingFactor.AbsoluteUri && a.AssociationHandle == handle); if (association != null) { dataContext.DeleteObject(association); return true; } else { return false; } } } /// /// Clears all expired associations from the store. /// /// /// 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. /// public void ClearExpiredAssociations() { using (var dataContext = new TransactedDatabaseEntities(IsolationLevel.ReadCommitted)) { dataContext.ClearExpiredAssociations(dataContext.Transaction); } } #endregion /// /// Deserializes an association from the database. /// /// The association from the database. /// The deserialized association. private static Association DeserializeAssociation(OpenIdAssociation association) { if (association == null) { throw new ArgumentNullException("association"); } byte[] privateData = new byte[association.PrivateDataLength]; Array.Copy(association.PrivateData, privateData, association.PrivateDataLength); return Association.Deserialize(association.AssociationHandle, association.ExpirationUtc, privateData); } } }