summaryrefslogtreecommitdiffstats
path: root/samples/RelyingPartyWebForms/Code/CustomStore.cs
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2009-01-31 21:32:01 -0800
committerAndrew <andrewarnott@gmail.com>2009-01-31 21:32:01 -0800
commite865ec7da293496d6c2e67039b86c4ef9ded5a6c (patch)
tree116d6a4e8c0892d2be13acd31bacae004de4bb8b /samples/RelyingPartyWebForms/Code/CustomStore.cs
parentf6d5493a42d07389421763c2f1da35b4ca7d9f45 (diff)
downloadDotNetOpenAuth-e865ec7da293496d6c2e67039b86c4ef9ded5a6c.zip
DotNetOpenAuth-e865ec7da293496d6c2e67039b86c4ef9ded5a6c.tar.gz
DotNetOpenAuth-e865ec7da293496d6c2e67039b86c4ef9ded5a6c.tar.bz2
Added OpenID sample sites: RP MVC, RP WebForms, RP Classic ASP, OP WebForms.
Diffstat (limited to 'samples/RelyingPartyWebForms/Code/CustomStore.cs')
-rw-r--r--samples/RelyingPartyWebForms/Code/CustomStore.cs137
1 files changed, 137 insertions, 0 deletions
diff --git a/samples/RelyingPartyWebForms/Code/CustomStore.cs b/samples/RelyingPartyWebForms/Code/CustomStore.cs
new file mode 100644
index 0000000..b250919
--- /dev/null
+++ b/samples/RelyingPartyWebForms/Code/CustomStore.cs
@@ -0,0 +1,137 @@
+namespace RelyingPartyWebForms.Code {
+ using System;
+ using System.Data;
+ using System.Globalization;
+ using System.Security.Cryptography;
+ using DotNetOpenAuth.OpenId;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+
+ /// <summary>
+ /// This custom store serializes all elements to demonstrate peristent and/or shared storage.
+ /// This is common in a web farm, for example.
+ /// </summary>
+ /// <remarks>
+ /// This doesn't actually serialize anything to a persistent store, so restarting the web server
+ /// will still clear everything this store is supposed to remember.
+ /// But we "persist" all associations and nonces into a DataTable to demonstrate
+ /// that using a database is possible.
+ /// </remarks>
+ public class CustomStore : IRelyingPartyApplicationStore {
+ private static CustomStoreDataSet dataSet = new CustomStoreDataSet();
+
+ #region IPrivateSecretStore Members
+
+ /// <summary>
+ /// Gets or sets a secret key that can be used for signing.
+ /// </summary>
+ /// <value>A 64-byte binary value, which may contain null bytes.</value>
+ public byte[] PrivateSecret { get; set; }
+
+ #endregion
+
+ #region INonceStore Members
+
+ /// <summary>
+ /// Stores a given nonce and timestamp.
+ /// </summary>
+ /// <param name="nonce">A series of random characters.</param>
+ /// <param name="timestamp">The timestamp that together with the nonce string make it unique.
+ /// The timestamp may also be used by the data store to clear out old nonces.</param>
+ /// <returns>
+ /// True if the nonce+timestamp (combination) was not previously in the database.
+ /// False if the nonce was stored previously with the same timestamp.
+ /// </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.
+ /// If the binding element is applicable to your channel, this expiration window
+ /// is retrieved or set using the
+ /// <see cref="StandardExpirationBindingElement.MaximumMessageAge"/> property.
+ /// </remarks>
+ public bool StoreNonce(string nonce, DateTime timestamp) {
+ // IMPORTANT: If actually persisting to a database that can be reached from
+ // different servers/instances of this class at once, it is vitally important
+ // to protect against race condition attacks by one or more of these:
+ // 1) setting a UNIQUE constraint on the nonce CODE in the SQL table
+ // 2) Using a transaction with repeatable reads to guarantee that a check
+ // that verified a nonce did not exist will prevent that nonce from being
+ // added by another process while this process is adding it.
+ // And then you'll want to catch the exception that the SQL database can throw
+ // at you in the result of a race condition somewhere in your web site UI code
+ // and display some message to have the user try to log in again, and possibly
+ // warn them about a replay attack.
+ lock (this) {
+ if (dataSet.Nonce.FindByCode(nonce) != null) {
+ return false;
+ }
+
+ // TODO: calculate the actual expiration date.
+ dataSet.Nonce.AddNonceRow(nonce, timestamp.ToLocalTime(), (timestamp + TimeSpan.FromHours(1)).ToLocalTime());
+ return true;
+ }
+ }
+
+ public void ClearExpiredNonces() {
+ this.removeExpiredRows(dataSet.Nonce, dataSet.Nonce.ExpiresColumn.ColumnName);
+ }
+
+ #endregion
+
+ #region IAssociationStore<Uri> Members
+
+ public void StoreAssociation(Uri distinguishingFactor, Association assoc) {
+ var assocRow = dataSet.Association.NewAssociationRow();
+ assocRow.DistinguishingFactor = distinguishingFactor.AbsoluteUri;
+ assocRow.Handle = assoc.Handle;
+ assocRow.Expires = assoc.Expires.ToLocalTime();
+ assocRow.PrivateData = assoc.SerializePrivateData();
+ dataSet.Association.AddAssociationRow(assocRow);
+ }
+
+ public Association GetAssociation(Uri distinguishingFactor) {
+ // properly escape the URL to prevent injection attacks.
+ string value = distinguishingFactor.AbsoluteUri.Replace("'", "''");
+ string filter = string.Format(
+ CultureInfo.InvariantCulture,
+ "{0} = '{1}'",
+ dataSet.Association.DistinguishingFactorColumn.ColumnName,
+ value);
+ string sort = dataSet.Association.ExpiresColumn.ColumnName + " DESC";
+ DataView view = new DataView(dataSet.Association, filter, sort, DataViewRowState.CurrentRows);
+ if (view.Count == 0) {
+ return null;
+ }
+ var row = (CustomStoreDataSet.AssociationRow)view[0].Row;
+ return Association.Deserialize(row.Handle, row.Expires.ToUniversalTime(), row.PrivateData);
+ }
+
+ public Association GetAssociation(Uri distinguishingFactor, string handle) {
+ var assocRow = dataSet.Association.FindByDistinguishingFactorHandle(distinguishingFactor.AbsoluteUri, handle);
+ return Association.Deserialize(assocRow.Handle, assocRow.Expires, assocRow.PrivateData);
+ }
+
+ public bool RemoveAssociation(Uri distinguishingFactor, string handle) {
+ var row = dataSet.Association.FindByDistinguishingFactorHandle(distinguishingFactor.AbsoluteUri, handle);
+ if (row != null) {
+ dataSet.Association.RemoveAssociationRow(row);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public void ClearExpiredAssociations() {
+ this.removeExpiredRows(dataSet.Association, dataSet.Association.ExpiresColumn.ColumnName);
+ }
+
+ #endregion
+
+ private void removeExpiredRows(DataTable table, string expiredColumnName) {
+ string filter = string.Format(CultureInfo.InvariantCulture, "{0} < #{1}#", expiredColumnName, DateTime.Now);
+ DataView view = new DataView(table, filter, null, DataViewRowState.CurrentRows);
+ for (int i = view.Count - 1; i >= 0; i--) {
+ view.Delete(i);
+ }
+ }
+ }
+}