summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2009-11-18 09:53:32 -0800
committerAndrew Arnott <andrewarnott@gmail.com>2009-11-18 11:43:53 -0800
commit1dc7805c0cea4f94760250a597fc7a980f4c377b (patch)
tree28d392725f0235eb53c9ab2f09c598cb70c10947
parent4728b81b32252207b20abd21b3b2f8e969b63b61 (diff)
downloadDotNetOpenAuth-1dc7805c0cea4f94760250a597fc7a980f4c377b.zip
DotNetOpenAuth-1dc7805c0cea4f94760250a597fc7a980f4c377b.tar.gz
DotNetOpenAuth-1dc7805c0cea4f94760250a597fc7a980f4c377b.tar.bz2
Added custom database store for nonces and associations and wired it up to the OAuth SP and OpenID RP in the project template.
-rw-r--r--projecttemplates/RelyingPartyLogic/CreateDatabase.sql89
-rw-r--r--projecttemplates/RelyingPartyLogic/Model.Designer.cs361
-rw-r--r--projecttemplates/RelyingPartyLogic/Model.OpenIdAssociation.cs18
-rw-r--r--projecttemplates/RelyingPartyLogic/Model.cs32
-rw-r--r--projecttemplates/RelyingPartyLogic/Model.edmx79
-rw-r--r--projecttemplates/RelyingPartyLogic/NonceDbStore.cs132
-rw-r--r--projecttemplates/RelyingPartyLogic/RelyingPartyApplicationDbStore.cs158
-rw-r--r--projecttemplates/RelyingPartyLogic/RelyingPartyLogic.csproj4
-rw-r--r--projecttemplates/RelyingPartyLogic/Utilities.cs31
-rw-r--r--projecttemplates/WebFormsRelyingParty/Web.config6
10 files changed, 899 insertions, 11 deletions
diff --git a/projecttemplates/RelyingPartyLogic/CreateDatabase.sql b/projecttemplates/RelyingPartyLogic/CreateDatabase.sql
index 3e36000..dd42724 100644
--- a/projecttemplates/RelyingPartyLogic/CreateDatabase.sql
+++ b/projecttemplates/RelyingPartyLogic/CreateDatabase.sql
@@ -2,6 +2,85 @@ SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
+CREATE PROCEDURE [dbo].[ClearExpiredNonces]
+AS
+
+DELETE FROM dbo.[Nonce]
+WHERE [Expires] < getutcdate()
+GO
+SET ANSI_NULLS ON
+GO
+SET QUOTED_IDENTIFIER ON
+GO
+CREATE PROCEDURE [dbo].[ClearExpiredAssociations]
+AS
+
+DELETE FROM dbo.OpenIDAssociation
+WHERE [Expiration] < getutcdate()
+GO
+SET ANSI_NULLS ON
+GO
+SET QUOTED_IDENTIFIER ON
+GO
+SET ANSI_PADDING ON
+GO
+CREATE TABLE [dbo].[Nonce](
+ [NonceId] [int] IDENTITY(1,1) NOT NULL,
+ [Context] [varchar](255) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL,
+ [Code] [varchar](255) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
+ [Issued] [datetime] NOT NULL,
+ [Expires] [datetime] NOT NULL,
+ CONSTRAINT [PK_Nonce] PRIMARY KEY CLUSTERED
+(
+ [NonceId] ASC
+)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
+) ON [PRIMARY]
+GO
+SET ANSI_PADDING OFF
+GO
+CREATE UNIQUE NONCLUSTERED INDEX [IX_Nonce_Code] ON [dbo].[Nonce]
+(
+ [Context] ASC,
+ [Code] ASC,
+ [Issued] ASC
+)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
+GO
+CREATE NONCLUSTERED INDEX [IX_Nonce_Expires] ON [dbo].[Nonce]
+(
+ [Expires] ASC
+)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
+GO
+SET ANSI_NULLS ON
+GO
+SET QUOTED_IDENTIFIER ON
+GO
+SET ANSI_PADDING ON
+GO
+CREATE TABLE [dbo].[OpenIDAssociation](
+ [AssociationId] [int] IDENTITY(1,1) NOT NULL,
+ [DistinguishingFactor] [varchar](255) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL,
+ [AssociationHandle] [varchar](255) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL,
+ [Expiration] [datetime] NOT NULL,
+ [PrivateData] [binary](64) NOT NULL,
+ [PrivateDataLength] [int] NOT NULL,
+ CONSTRAINT [PK_OpenIDAssociations] PRIMARY KEY CLUSTERED
+(
+ [AssociationId] ASC
+)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
+) ON [PRIMARY]
+GO
+SET ANSI_PADDING OFF
+GO
+CREATE UNIQUE NONCLUSTERED INDEX [IX_OpenIDAssociations] ON [dbo].[OpenIDAssociation]
+(
+ [DistinguishingFactor] ASC,
+ [AssociationHandle] ASC
+)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
+GO
+SET ANSI_NULLS ON
+GO
+SET QUOTED_IDENTIFIER ON
+GO
CREATE TABLE [dbo].[Consumer](
[ConsumerId] [int] IDENTITY(1,1) NOT NULL,
[ConsumerKey] [nvarchar](255) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL,
@@ -158,17 +237,19 @@ AS
RETURN @userid
GO
+ALTER TABLE [dbo].[Nonce] ADD CONSTRAINT [DF_Nonce_Issued] DEFAULT (getutcdate()) FOR [Issued]
+GO
ALTER TABLE [dbo].[User] ADD CONSTRAINT [DF_User_EmailAddressVerified] DEFAULT ((0)) FOR [EmailAddressVerified]
GO
-ALTER TABLE [dbo].[User] ADD CONSTRAINT [DF_User_CreatedOn] DEFAULT (getdate()) FOR [CreatedOn]
+ALTER TABLE [dbo].[User] ADD CONSTRAINT [DF_User_CreatedOn] DEFAULT (getutcdate()) FOR [CreatedOn]
GO
-ALTER TABLE [dbo].[IssuedToken] ADD CONSTRAINT [DF_IssuedToken_CreatedOn] DEFAULT (getdate()) FOR [CreatedOn]
+ALTER TABLE [dbo].[IssuedToken] ADD CONSTRAINT [DF_IssuedToken_CreatedOn] DEFAULT (getutcdate()) FOR [CreatedOn]
GO
ALTER TABLE [dbo].[IssuedToken] ADD CONSTRAINT [DF_IssuedToken_IsAccessToken] DEFAULT ((0)) FOR [IsAccessToken]
GO
-ALTER TABLE [dbo].[AuthenticationToken] ADD CONSTRAINT [DF_AuthenticationToken_CreatedOn] DEFAULT (getdate()) FOR [CreatedOn]
+ALTER TABLE [dbo].[AuthenticationToken] ADD CONSTRAINT [DF_AuthenticationToken_CreatedOn] DEFAULT (getutcdate()) FOR [CreatedOn]
GO
-ALTER TABLE [dbo].[AuthenticationToken] ADD CONSTRAINT [DF_AuthenticationToken_LastUsed] DEFAULT (getdate()) FOR [LastUsed]
+ALTER TABLE [dbo].[AuthenticationToken] ADD CONSTRAINT [DF_AuthenticationToken_LastUsed] DEFAULT (getutcdate()) FOR [LastUsed]
GO
ALTER TABLE [dbo].[AuthenticationToken] ADD CONSTRAINT [DF_AuthenticationToken_UsageCount] DEFAULT ((0)) FOR [UsageCount]
GO
diff --git a/projecttemplates/RelyingPartyLogic/Model.Designer.cs b/projecttemplates/RelyingPartyLogic/Model.Designer.cs
index af86171..dba46ed 100644
--- a/projecttemplates/RelyingPartyLogic/Model.Designer.cs
+++ b/projecttemplates/RelyingPartyLogic/Model.Designer.cs
@@ -15,7 +15,7 @@
[assembly: global::System.Data.Objects.DataClasses.EdmRelationshipAttribute("DatabaseModel", "FK_IssuedToken_User1", "User", global::System.Data.Metadata.Edm.RelationshipMultiplicity.ZeroOrOne, typeof(RelyingPartyLogic.User), "IssuedToken", global::System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(RelyingPartyLogic.IssuedToken))]
// Original file name:
-// Generation date: 11/16/2009 8:23:18 PM
+// Generation date: 11/18/2009 9:09:23 AM
namespace RelyingPartyLogic
{
@@ -125,6 +125,36 @@ namespace RelyingPartyLogic
}
private global::System.Data.Objects.ObjectQuery<IssuedToken> _IssuedToken;
/// <summary>
+ /// There are no comments for Nonces in the schema.
+ /// </summary>
+ public global::System.Data.Objects.ObjectQuery<Nonce> Nonces
+ {
+ get
+ {
+ if ((this._Nonces == null))
+ {
+ this._Nonces = base.CreateQuery<Nonce>("[Nonces]");
+ }
+ return this._Nonces;
+ }
+ }
+ private global::System.Data.Objects.ObjectQuery<Nonce> _Nonces;
+ /// <summary>
+ /// There are no comments for OpenIdAssociations in the schema.
+ /// </summary>
+ public global::System.Data.Objects.ObjectQuery<OpenIdAssociation> OpenIdAssociations
+ {
+ get
+ {
+ if ((this._OpenIdAssociations == null))
+ {
+ this._OpenIdAssociations = base.CreateQuery<OpenIdAssociation>("[OpenIdAssociations]");
+ }
+ return this._OpenIdAssociations;
+ }
+ }
+ private global::System.Data.Objects.ObjectQuery<OpenIdAssociation> _OpenIdAssociations;
+ /// <summary>
/// There are no comments for Role in the schema.
/// </summary>
public void AddToRole(Role role)
@@ -159,6 +189,20 @@ namespace RelyingPartyLogic
{
base.AddObject("IssuedToken", issuedToken);
}
+ /// <summary>
+ /// There are no comments for Nonces in the schema.
+ /// </summary>
+ public void AddToNonces(Nonce nonce)
+ {
+ base.AddObject("Nonces", nonce);
+ }
+ /// <summary>
+ /// There are no comments for OpenIdAssociations in the schema.
+ /// </summary>
+ public void AddToOpenIdAssociations(OpenIdAssociation openIdAssociation)
+ {
+ base.AddObject("OpenIdAssociations", openIdAssociation);
+ }
}
/// <summary>
/// There are no comments for DatabaseModel.AuthenticationToken in the schema.
@@ -1265,4 +1309,319 @@ namespace RelyingPartyLogic
partial void OnExpirationDateUtcChanging(global::System.Nullable<global::System.DateTime> value);
partial void OnExpirationDateUtcChanged();
}
+ /// <summary>
+ /// There are no comments for DatabaseModel.Nonce in the schema.
+ /// </summary>
+ /// <KeyProperties>
+ /// NonceId
+ /// </KeyProperties>
+ [global::System.Data.Objects.DataClasses.EdmEntityTypeAttribute(NamespaceName="DatabaseModel", Name="Nonce")]
+ [global::System.Runtime.Serialization.DataContractAttribute(IsReference=true)]
+ [global::System.Serializable()]
+ public partial class Nonce : global::System.Data.Objects.DataClasses.EntityObject
+ {
+ /// <summary>
+ /// Create a new Nonce object.
+ /// </summary>
+ /// <param name="nonceId">Initial value of NonceId.</param>
+ /// <param name="context">Initial value of Context.</param>
+ /// <param name="code">Initial value of Code.</param>
+ /// <param name="issuedUtc">Initial value of IssuedUtc.</param>
+ /// <param name="expiresUtc">Initial value of ExpiresUtc.</param>
+ public static Nonce CreateNonce(int nonceId, string context, string code, global::System.DateTime issuedUtc, global::System.DateTime expiresUtc)
+ {
+ Nonce nonce = new Nonce();
+ nonce.NonceId = nonceId;
+ nonce.Context = context;
+ nonce.Code = code;
+ nonce.IssuedUtc = issuedUtc;
+ nonce.ExpiresUtc = expiresUtc;
+ return nonce;
+ }
+ /// <summary>
+ /// There are no comments for Property NonceId in the schema.
+ /// </summary>
+ [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)]
+ [global::System.Runtime.Serialization.DataMemberAttribute()]
+ public int NonceId
+ {
+ get
+ {
+ return this._NonceId;
+ }
+ set
+ {
+ this.OnNonceIdChanging(value);
+ this.ReportPropertyChanging("NonceId");
+ this._NonceId = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value);
+ this.ReportPropertyChanged("NonceId");
+ this.OnNonceIdChanged();
+ }
+ }
+ private int _NonceId;
+ partial void OnNonceIdChanging(int value);
+ partial void OnNonceIdChanged();
+ /// <summary>
+ /// Gets or sets the Provider Endpoint URL the nonce came from.
+ /// </summary>
+ [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)]
+ [global::System.Runtime.Serialization.DataMemberAttribute()]
+ public string Context
+ {
+ get
+ {
+ return this._Context;
+ }
+ set
+ {
+ this.OnContextChanging(value);
+ this.ReportPropertyChanging("Context");
+ this._Context = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value, false);
+ this.ReportPropertyChanged("Context");
+ this.OnContextChanged();
+ }
+ }
+ private string _Context;
+ partial void OnContextChanging(string value);
+ partial void OnContextChanged();
+ /// <summary>
+ /// There are no comments for Property Code in the schema.
+ /// </summary>
+ [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)]
+ [global::System.Runtime.Serialization.DataMemberAttribute()]
+ public string Code
+ {
+ get
+ {
+ return this._Code;
+ }
+ set
+ {
+ this.OnCodeChanging(value);
+ this.ReportPropertyChanging("Code");
+ this._Code = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value, false);
+ this.ReportPropertyChanged("Code");
+ this.OnCodeChanged();
+ }
+ }
+ private string _Code;
+ partial void OnCodeChanging(string value);
+ partial void OnCodeChanged();
+ /// <summary>
+ /// There are no comments for Property IssuedUtc in the schema.
+ /// </summary>
+ [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)]
+ [global::System.Runtime.Serialization.DataMemberAttribute()]
+ public global::System.DateTime IssuedUtc
+ {
+ get
+ {
+ return this._IssuedUtc;
+ }
+ set
+ {
+ this.OnIssuedUtcChanging(value);
+ this.ReportPropertyChanging("IssuedUtc");
+ this._IssuedUtc = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value);
+ this.ReportPropertyChanged("IssuedUtc");
+ this.OnIssuedUtcChanged();
+ }
+ }
+ private global::System.DateTime _IssuedUtc;
+ partial void OnIssuedUtcChanging(global::System.DateTime value);
+ partial void OnIssuedUtcChanged();
+ /// <summary>
+ /// There are no comments for Property ExpiresUtc in the schema.
+ /// </summary>
+ [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)]
+ [global::System.Runtime.Serialization.DataMemberAttribute()]
+ public global::System.DateTime ExpiresUtc
+ {
+ get
+ {
+ return this._ExpiresUtc;
+ }
+ set
+ {
+ this.OnExpiresUtcChanging(value);
+ this.ReportPropertyChanging("ExpiresUtc");
+ this._ExpiresUtc = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value);
+ this.ReportPropertyChanged("ExpiresUtc");
+ this.OnExpiresUtcChanged();
+ }
+ }
+ private global::System.DateTime _ExpiresUtc;
+ partial void OnExpiresUtcChanging(global::System.DateTime value);
+ partial void OnExpiresUtcChanged();
+ }
+ /// <summary>
+ /// There are no comments for DatabaseModel.OpenIdAssociation in the schema.
+ /// </summary>
+ /// <KeyProperties>
+ /// AssociationId
+ /// </KeyProperties>
+ [global::System.Data.Objects.DataClasses.EdmEntityTypeAttribute(NamespaceName="DatabaseModel", Name="OpenIdAssociation")]
+ [global::System.Runtime.Serialization.DataContractAttribute(IsReference=true)]
+ [global::System.Serializable()]
+ public partial class OpenIdAssociation : global::System.Data.Objects.DataClasses.EntityObject
+ {
+ /// <summary>
+ /// Create a new OpenIdAssociation object.
+ /// </summary>
+ /// <param name="associationId">Initial value of AssociationId.</param>
+ /// <param name="distinguishingFactor">Initial value of DistinguishingFactor.</param>
+ /// <param name="associationHandle">Initial value of AssociationHandle.</param>
+ /// <param name="expirationUtc">Initial value of ExpirationUtc.</param>
+ /// <param name="privateData">Initial value of PrivateData.</param>
+ /// <param name="privateDataLength">Initial value of PrivateDataLength.</param>
+ public static OpenIdAssociation CreateOpenIdAssociation(int associationId, string distinguishingFactor, string associationHandle, global::System.DateTime expirationUtc, byte[] privateData, int privateDataLength)
+ {
+ OpenIdAssociation openIdAssociation = new OpenIdAssociation();
+ openIdAssociation.AssociationId = associationId;
+ openIdAssociation.DistinguishingFactor = distinguishingFactor;
+ openIdAssociation.AssociationHandle = associationHandle;
+ openIdAssociation.ExpirationUtc = expirationUtc;
+ openIdAssociation.PrivateData = privateData;
+ openIdAssociation.PrivateDataLength = privateDataLength;
+ return openIdAssociation;
+ }
+ /// <summary>
+ /// There are no comments for Property AssociationId in the schema.
+ /// </summary>
+ [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)]
+ [global::System.Runtime.Serialization.DataMemberAttribute()]
+ public int AssociationId
+ {
+ get
+ {
+ return this._AssociationId;
+ }
+ set
+ {
+ this.OnAssociationIdChanging(value);
+ this.ReportPropertyChanging("AssociationId");
+ this._AssociationId = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value);
+ this.ReportPropertyChanged("AssociationId");
+ this.OnAssociationIdChanged();
+ }
+ }
+ private int _AssociationId;
+ partial void OnAssociationIdChanging(int value);
+ partial void OnAssociationIdChanged();
+ /// <summary>
+ /// Gets or sets the Provider Endpoint URL the association is with.
+ /// </summary>
+ [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)]
+ [global::System.Runtime.Serialization.DataMemberAttribute()]
+ public string DistinguishingFactor
+ {
+ get
+ {
+ return this._DistinguishingFactor;
+ }
+ set
+ {
+ this.OnDistinguishingFactorChanging(value);
+ this.ReportPropertyChanging("DistinguishingFactor");
+ this._DistinguishingFactor = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value, false);
+ this.ReportPropertyChanged("DistinguishingFactor");
+ this.OnDistinguishingFactorChanged();
+ }
+ }
+ private string _DistinguishingFactor;
+ partial void OnDistinguishingFactorChanging(string value);
+ partial void OnDistinguishingFactorChanged();
+ /// <summary>
+ /// There are no comments for Property AssociationHandle in the schema.
+ /// </summary>
+ [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)]
+ [global::System.Runtime.Serialization.DataMemberAttribute()]
+ public string AssociationHandle
+ {
+ get
+ {
+ return this._AssociationHandle;
+ }
+ set
+ {
+ this.OnAssociationHandleChanging(value);
+ this.ReportPropertyChanging("AssociationHandle");
+ this._AssociationHandle = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value, false);
+ this.ReportPropertyChanged("AssociationHandle");
+ this.OnAssociationHandleChanged();
+ }
+ }
+ private string _AssociationHandle;
+ partial void OnAssociationHandleChanging(string value);
+ partial void OnAssociationHandleChanged();
+ /// <summary>
+ /// There are no comments for Property ExpirationUtc in the schema.
+ /// </summary>
+ [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)]
+ [global::System.Runtime.Serialization.DataMemberAttribute()]
+ public global::System.DateTime ExpirationUtc
+ {
+ get
+ {
+ return this._ExpirationUtc;
+ }
+ set
+ {
+ this.OnExpirationUtcChanging(value);
+ this.ReportPropertyChanging("ExpirationUtc");
+ this._ExpirationUtc = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value);
+ this.ReportPropertyChanged("ExpirationUtc");
+ this.OnExpirationUtcChanged();
+ }
+ }
+ private global::System.DateTime _ExpirationUtc;
+ partial void OnExpirationUtcChanging(global::System.DateTime value);
+ partial void OnExpirationUtcChanged();
+ /// <summary>
+ /// There are no comments for Property PrivateData in the schema.
+ /// </summary>
+ [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)]
+ [global::System.Runtime.Serialization.DataMemberAttribute()]
+ public byte[] PrivateData
+ {
+ get
+ {
+ return global::System.Data.Objects.DataClasses.StructuralObject.GetValidValue(this._PrivateData);
+ }
+ set
+ {
+ this.OnPrivateDataChanging(value);
+ this.ReportPropertyChanging("PrivateData");
+ this._PrivateData = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value, false);
+ this.ReportPropertyChanged("PrivateData");
+ this.OnPrivateDataChanged();
+ }
+ }
+ private byte[] _PrivateData;
+ partial void OnPrivateDataChanging(byte[] value);
+ partial void OnPrivateDataChanged();
+ /// <summary>
+ /// There are no comments for Property PrivateDataLength in the schema.
+ /// </summary>
+ [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)]
+ [global::System.Runtime.Serialization.DataMemberAttribute()]
+ public int PrivateDataLength
+ {
+ get
+ {
+ return this._PrivateDataLength;
+ }
+ set
+ {
+ this.OnPrivateDataLengthChanging(value);
+ this.ReportPropertyChanging("PrivateDataLength");
+ this._PrivateDataLength = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value);
+ this.ReportPropertyChanged("PrivateDataLength");
+ this.OnPrivateDataLengthChanged();
+ }
+ }
+ private int _PrivateDataLength;
+ partial void OnPrivateDataLengthChanging(int value);
+ partial void OnPrivateDataLengthChanged();
+ }
}
diff --git a/projecttemplates/RelyingPartyLogic/Model.OpenIdAssociation.cs b/projecttemplates/RelyingPartyLogic/Model.OpenIdAssociation.cs
new file mode 100644
index 0000000..94ab09a
--- /dev/null
+++ b/projecttemplates/RelyingPartyLogic/Model.OpenIdAssociation.cs
@@ -0,0 +1,18 @@
+//-----------------------------------------------------------------------
+// <copyright file="Model.OpenIdAssociation.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace RelyingPartyLogic {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+
+ public partial class OpenIdAssociation {
+ partial void OnPrivateDataChanged() {
+ this.PrivateDataLength = this.PrivateData.Length;
+ }
+ }
+}
diff --git a/projecttemplates/RelyingPartyLogic/Model.cs b/projecttemplates/RelyingPartyLogic/Model.cs
new file mode 100644
index 0000000..10c1518
--- /dev/null
+++ b/projecttemplates/RelyingPartyLogic/Model.cs
@@ -0,0 +1,32 @@
+//-----------------------------------------------------------------------
+// <copyright file="Model.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace RelyingPartyLogic {
+ using System;
+ using System.Collections.Generic;
+ using System.Data;
+ using System.Data.Common;
+ using System.Data.EntityClient;
+ using System.Data.Objects;
+ using System.Linq;
+ using System.Text;
+
+ public partial class DatabaseEntities {
+ /// <summary>
+ /// Clears the expired nonces.
+ /// </summary>
+ internal void ClearExpiredNonces() {
+ this.ExecuteCommand("ClearExpiredNonces");
+ }
+
+ /// <summary>
+ /// Clears the expired associations.
+ /// </summary>
+ internal void ClearExpiredAssociations() {
+ this.ExecuteCommand("ClearExpiredAssociations");
+ }
+ }
+}
diff --git a/projecttemplates/RelyingPartyLogic/Model.edmx b/projecttemplates/RelyingPartyLogic/Model.edmx
index 21fa98a..76fd57a 100644
--- a/projecttemplates/RelyingPartyLogic/Model.edmx
+++ b/projecttemplates/RelyingPartyLogic/Model.edmx
@@ -9,6 +9,8 @@
<EntitySet Name="AuthenticationToken" EntityType="DatabaseModel.Store.AuthenticationToken" store:Type="Tables" Schema="dbo" />
<EntitySet Name="Consumer" EntityType="DatabaseModel.Store.Consumer" store:Type="Tables" Schema="dbo" />
<EntitySet Name="IssuedToken" EntityType="DatabaseModel.Store.IssuedToken" store:Type="Tables" Schema="dbo" />
+ <EntitySet Name="Nonce" EntityType="DatabaseModel.Store.Nonce" store:Type="Tables" Schema="dbo" />
+ <EntitySet Name="OpenIDAssociation" EntityType="DatabaseModel.Store.OpenIDAssociation" store:Type="Tables" Schema="dbo" />
<EntitySet Name="Role" EntityType="DatabaseModel.Store.Role" store:Type="Tables" Schema="dbo" />
<EntitySet Name="User" EntityType="DatabaseModel.Store.User" store:Type="Tables" Schema="dbo" />
<EntitySet Name="UserRole" EntityType="DatabaseModel.Store.UserRole" store:Type="Tables" Schema="dbo" />
@@ -75,6 +77,27 @@
<Property Name="IsAccessToken" Type="bit" Nullable="false" />
<Property Name="Scope" Type="nvarchar" MaxLength="255" />
</EntityType>
+ <EntityType Name="Nonce">
+ <Key>
+ <PropertyRef Name="NonceId" />
+ </Key>
+ <Property Name="NonceId" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />
+ <Property Name="Context" Type="varchar" Nullable="false" MaxLength="255" />
+ <Property Name="Code" Type="varchar" Nullable="false" MaxLength="255" />
+ <Property Name="Issued" Type="datetime" Nullable="false" />
+ <Property Name="Expires" Type="datetime" Nullable="false" />
+ </EntityType>
+ <EntityType Name="OpenIDAssociation">
+ <Key>
+ <PropertyRef Name="AssociationId" />
+ </Key>
+ <Property Name="AssociationId" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />
+ <Property Name="DistinguishingFactor" Type="varchar" Nullable="false" MaxLength="255" />
+ <Property Name="AssociationHandle" Type="varchar" Nullable="false" MaxLength="255" />
+ <Property Name="Expiration" Type="datetime" Nullable="false" />
+ <Property Name="PrivateData" Type="binary" Nullable="false" MaxLength="32" />
+ <Property Name="PrivateDataLength" Type="int" Nullable="false" />
+ </EntityType>
<EntityType Name="Role">
<Key>
<PropertyRef Name="RoleId" />
@@ -171,6 +194,8 @@
</Dependent>
</ReferentialConstraint>
</Association>
+ <Function Name="ClearExpiredAssociations" Aggregate="false" BuiltIn="false" NiladicFunction="false" IsComposable="false" ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo" />
+ <Function Name="ClearExpiredNonces" Aggregate="false" BuiltIn="false" NiladicFunction="false" IsComposable="false" ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo" />
</Schema></edmx:StorageModels>
<!-- CSDL content -->
<edmx:ConceptualModels>
@@ -193,7 +218,10 @@
<End Role="IssuedToken" EntitySet="IssuedToken" /></AssociationSet>
<AssociationSet Name="FK_IssuedToken_User1" Association="DatabaseModel.FK_IssuedToken_User1">
<End Role="User" EntitySet="User" />
- <End Role="IssuedToken" EntitySet="IssuedToken" /></AssociationSet></EntityContainer>
+ <End Role="IssuedToken" EntitySet="IssuedToken" /></AssociationSet>
+ <EntitySet Name="Nonces" EntityType="DatabaseModel.Nonce" />
+ <EntitySet Name="OpenIdAssociations" EntityType="DatabaseModel.OpenIdAssociation" />
+ <FunctionImport Name="ClearExpiredNonces" /></EntityContainer>
<EntityType Name="AuthenticationToken" Abstract="false">
<Key>
<PropertyRef Name="AuthenticationTokenId" /></Key>
@@ -266,7 +294,28 @@
<End Type="DatabaseModel.IssuedToken" Role="IssuedToken" Multiplicity="*" /></Association>
<Association Name="FK_IssuedToken_User1">
<End Type="DatabaseModel.User" Role="User" Multiplicity="0..1" />
- <End Type="DatabaseModel.IssuedToken" Role="IssuedToken" Multiplicity="*" /></Association></Schema>
+ <End Type="DatabaseModel.IssuedToken" Role="IssuedToken" Multiplicity="*" /></Association>
+ <EntityType Name="Nonce" a:TypeAccess="Public" xmlns:a="http://schemas.microsoft.com/ado/2006/04/codegeneration">
+ <Key>
+ <PropertyRef Name="NonceId" /></Key>
+ <Property Name="NonceId" Type="Int32" Nullable="false" />
+ <Property Name="Context" Type="String" Nullable="false" >
+ <Documentation>
+ <Summary>Gets or sets the Provider Endpoint URL the nonce came from.</Summary></Documentation></Property>
+ <Property Name="Code" Type="String" Nullable="false" />
+ <Property Name="IssuedUtc" Type="DateTime" Nullable="false" />
+ <Property Name="ExpiresUtc" Type="DateTime" Nullable="false" /></EntityType>
+ <EntityType Name="OpenIdAssociation" a:TypeAccess="Public" xmlns:a="http://schemas.microsoft.com/ado/2006/04/codegeneration">
+ <Key>
+ <PropertyRef Name="AssociationId" /></Key>
+ <Property Name="AssociationId" Type="Int32" Nullable="false" />
+ <Property Name="DistinguishingFactor" Type="String" Nullable="false" >
+ <Documentation>
+ <Summary>Gets or sets the Provider Endpoint URL the association is with.</Summary></Documentation></Property>
+ <Property Name="AssociationHandle" Type="String" Nullable="false" />
+ <Property Name="ExpirationUtc" Type="DateTime" Nullable="false" />
+ <Property Name="PrivateData" Type="Binary" Nullable="false" />
+ <Property Name="PrivateDataLength" Type="Int32" Nullable="false" /></EntityType></Schema>
</edmx:ConceptualModels>
<!-- C-S mapping content -->
<edmx:Mappings>
@@ -356,7 +405,25 @@
<ScalarProperty Name="IssuedTokenId" ColumnName="IssuedTokenId" /></EndProperty>
<EndProperty Name="User">
<ScalarProperty Name="UserId" ColumnName="UserId" /></EndProperty>
- <Condition ColumnName="UserId" IsNull="false" /></AssociationSetMapping></EntityContainerMapping>
+ <Condition ColumnName="UserId" IsNull="false" /></AssociationSetMapping>
+ <EntitySetMapping Name="Nonces">
+ <EntityTypeMapping TypeName="IsTypeOf(DatabaseModel.Nonce)">
+ <MappingFragment StoreEntitySet="Nonce">
+ <ScalarProperty Name="ExpiresUtc" ColumnName="Expires" />
+ <ScalarProperty Name="IssuedUtc" ColumnName="Issued" />
+ <ScalarProperty Name="Code" ColumnName="Code" />
+ <ScalarProperty Name="Context" ColumnName="Context" />
+ <ScalarProperty Name="NonceId" ColumnName="NonceId" /></MappingFragment></EntityTypeMapping></EntitySetMapping>
+ <EntitySetMapping Name="OpenIdAssociations">
+ <EntityTypeMapping TypeName="IsTypeOf(DatabaseModel.OpenIdAssociation)">
+ <MappingFragment StoreEntitySet="OpenIDAssociation">
+ <ScalarProperty Name="PrivateDataLength" ColumnName="PrivateDataLength" />
+ <ScalarProperty Name="PrivateData" ColumnName="PrivateData" />
+ <ScalarProperty Name="ExpirationUtc" ColumnName="Expiration" />
+ <ScalarProperty Name="AssociationHandle" ColumnName="AssociationHandle" />
+ <ScalarProperty Name="DistinguishingFactor" ColumnName="DistinguishingFactor" />
+ <ScalarProperty Name="AssociationId" ColumnName="AssociationId" /></MappingFragment></EntityTypeMapping></EntitySetMapping>
+ <FunctionImportMapping FunctionImportName="ClearExpiredNonces" FunctionName="DatabaseModel.Store.ClearExpiredNonces" /></EntityContainerMapping>
</Mapping>
</edmx:Mappings>
</edmx:Runtime>
@@ -374,7 +441,7 @@
</edmx:Options>
<!-- Diagram content (shape and connector positions) -->
<edmx:Diagrams>
- <Diagram Name="Model" ZoomLevel="86">
+ <Diagram Name="Model" ZoomLevel="98">
<EntityTypeShape EntityType="DatabaseModel.AuthenticationToken" Width="1.875" PointX="5.25" PointY="0.75" Height="2.5571907552083339" IsExpanded="true" />
<EntityTypeShape EntityType="DatabaseModel.Role" Width="1.5" PointX="0.75" PointY="1.25" Height="1.59568359375" IsExpanded="true" />
<EntityTypeShape EntityType="DatabaseModel.User" Width="1.75" PointX="2.875" PointY="0.5" Height="3.1340950520833339" IsExpanded="true" />
@@ -406,6 +473,8 @@
<AssociationConnector Association="DatabaseModel.FK_IssuedToken_User1" >
<ConnectorPoint PointX="3.75" PointY="3.6340950520833339" />
<ConnectorPoint PointX="3.75" PointY="4.0627779870647478" />
- <ConnectorPoint PointX="5.25" PointY="4.0627779870647478" /></AssociationConnector></Diagram></edmx:Diagrams>
+ <ConnectorPoint PointX="5.25" PointY="4.0627779870647478" /></AssociationConnector>
+ <EntityTypeShape EntityType="DatabaseModel.Nonce" Width="1.5" PointX="0.5" PointY="7.75" Height="1.9802864583333326" />
+ <EntityTypeShape EntityType="DatabaseModel.OpenIdAssociation" Width="1.75" PointX="2.25" PointY="7.75" Height="1.9802864583333333" /></Diagram></edmx:Diagrams>
</edmx:Designer>
</edmx:Edmx> \ No newline at end of file
diff --git a/projecttemplates/RelyingPartyLogic/NonceDbStore.cs b/projecttemplates/RelyingPartyLogic/NonceDbStore.cs
new file mode 100644
index 0000000..2f3c670
--- /dev/null
+++ b/projecttemplates/RelyingPartyLogic/NonceDbStore.cs
@@ -0,0 +1,132 @@
+//-----------------------------------------------------------------------
+// <copyright file="NonceDbStore.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+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;
+
+ /// <summary>
+ /// A database-backed nonce store for OpenID and OAuth services.
+ /// </summary>
+ public class NonceDbStore : INonceStore {
+ private const int NonceClearingInterval = 5;
+
+ /// <summary>
+ /// A counter that tracks how many nonce stores have been done.
+ /// </summary>
+ private static int nonceClearingCounter;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="NonceDbStore"/> class.
+ /// </summary>
+ public NonceDbStore() {
+ }
+
+ #region INonceStore Members
+
+ /// <summary>
+ /// Stores a given nonce and timestamp.
+ /// </summary>
+ /// <param name="context">The context, or namespace, within which the
+ /// <paramref name="nonce"/> must be unique.
+ /// The context SHOULD be treated as case-sensitive.
+ /// The value will never be <c>null</c> but may be the empty string.</param>
+ /// <param name="nonce">A series of random characters.</param>
+ /// <param name="timestampUtc">The UTC timestamp that together with the nonce string make it unique
+ /// within the given <paramref name="context"/>.
+ /// The timestamp may also be used by the data store to clear out old nonces.</param>
+ /// <returns>
+ /// 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.
+ /// </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.
+ /// This maximum message age can be looked up via the
+ /// <see cref="DotNetOpenAuth.Configuration.MessagingElement.MaximumMessageLifetime"/>
+ /// property, accessible via the <see cref="DotNetOpenAuth.Configuration.DotNetOpenAuthSection.Configuration"/>
+ /// property.
+ /// </remarks>
+ 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
+
+ /// <summary>
+ /// Clears the nonces if appropriate.
+ /// </summary>
+ private static void ClearNoncesIfAppropriate() {
+ if (++nonceClearingCounter % NonceClearingInterval == 0) {
+ using (var dataContext = new TransactedDatabaseEntities(IsolationLevel.ReadCommitted)) {
+ dataContext.ClearExpiredNonces();
+ }
+ }
+ }
+
+ /// <summary>
+ /// A transacted data context.
+ /// </summary>
+ protected class TransactedDatabaseEntities : DatabaseEntities {
+ /// <summary>
+ /// The transaction for this data context.
+ /// </summary>
+ private DbTransaction transaction;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TransactedDatabaseEntities"/> class.
+ /// </summary>
+ /// <param name="isolationLevel">The isolation level.</param>
+ public TransactedDatabaseEntities(IsolationLevel isolationLevel) {
+ this.Connection.Open();
+ this.transaction = this.Connection.BeginTransaction(isolationLevel);
+ }
+
+ /// <summary>
+ /// Releases the resources used by the object context.
+ /// </summary>
+ /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
+ protected override void Dispose(bool disposing) {
+ try {
+ this.SaveChanges();
+ this.transaction.Commit();
+ } finally {
+ this.Connection.Close();
+ }
+
+ base.Dispose(disposing);
+ }
+ }
+ }
+}
diff --git a/projecttemplates/RelyingPartyLogic/RelyingPartyApplicationDbStore.cs b/projecttemplates/RelyingPartyLogic/RelyingPartyApplicationDbStore.cs
new file mode 100644
index 0000000..9a2f8bb
--- /dev/null
+++ b/projecttemplates/RelyingPartyLogic/RelyingPartyApplicationDbStore.cs
@@ -0,0 +1,158 @@
+//-----------------------------------------------------------------------
+// <copyright file="RelyingPartyApplicationDbStore.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace RelyingPartyLogic {
+ using System;
+ using System.Collections.Generic;
+ using System.Data;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.OpenId;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+
+ /// <summary>
+ /// A database-backed state store for OpenID relying parties.
+ /// </summary>
+ public class RelyingPartyApplicationDbStore : NonceDbStore, IRelyingPartyApplicationStore {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RelyingPartyApplicationDbStore"/> class.
+ /// </summary>
+ public RelyingPartyApplicationDbStore() {
+ }
+
+ #region IAssociationStore<Uri> Members
+
+ /// <summary>
+ /// Saves an <see cref="Association"/> for later recall.
+ /// </summary>
+ /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for providers).</param>
+ /// <param name="association">The association to store.</param>
+ /// <remarks>
+ /// TODO: what should implementations do on association handle conflict?
+ /// </remarks>
+ 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);
+ }
+ }
+
+ /// <summary>
+ /// Gets the best association (the one with the longest remaining life) for a given key.
+ /// </summary>
+ /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param>
+ /// <param name="securityRequirements">The security requirements that the returned association must meet.</param>
+ /// <returns>
+ /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key.
+ /// </returns>
+ /// <remarks>
+ /// In the event that multiple associations exist for the given
+ /// <paramref name="distinguishingFactor"/>, it is important for the
+ /// implementation for this method to use the <paramref name="securityRequirements"/>
+ /// 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.
+ /// </remarks>
+ 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();
+ }
+ }
+
+ /// <summary>
+ /// Gets the association for a given key and handle.
+ /// </summary>
+ /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param>
+ /// <param name="handle">The handle of the specific association that must be recalled.</param>
+ /// <returns>
+ /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key and handle.
+ /// </returns>
+ 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();
+ }
+ }
+
+ /// <summary>
+ /// Removes a specified handle that may exist in the store.
+ /// </summary>
+ /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param>
+ /// <param name="handle">The handle of the specific association that must be deleted.</param>
+ /// <returns>
+ /// True if the association existed in this store previous to this call.
+ /// </returns>
+ /// <remarks>
+ /// No exception should be thrown if the association does not exist in the store
+ /// before this call.
+ /// </remarks>
+ 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;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Clears all expired associations from the store.
+ /// </summary>
+ /// <remarks>
+ /// 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.
+ /// </remarks>
+ public void ClearExpiredAssociations() {
+ using (var dataContext = new TransactedDatabaseEntities(IsolationLevel.ReadCommitted)) {
+ dataContext.ClearExpiredAssociations();
+ }
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Deserializes an association from the database.
+ /// </summary>
+ /// <param name="association">The association from the database.</param>
+ /// <returns>The deserialized association.</returns>
+ 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);
+ }
+ }
+}
diff --git a/projecttemplates/RelyingPartyLogic/RelyingPartyLogic.csproj b/projecttemplates/RelyingPartyLogic/RelyingPartyLogic.csproj
index 1e7e759..9eba352 100644
--- a/projecttemplates/RelyingPartyLogic/RelyingPartyLogic.csproj
+++ b/projecttemplates/RelyingPartyLogic/RelyingPartyLogic.csproj
@@ -82,6 +82,7 @@
<Reference Include="System.Web.Mobile" />
</ItemGroup>
<ItemGroup>
+ <Compile Include="Model.cs" />
<Compile Include="Model.IssuedToken.cs" />
<Compile Include="Database.cs" />
<Compile Include="DataRoleProvider.cs" />
@@ -94,7 +95,9 @@
</Compile>
<Compile Include="Model.IssuedAccessToken.cs" />
<Compile Include="Model.IssuedRequestToken.cs" />
+ <Compile Include="Model.OpenIdAssociation.cs" />
<Compile Include="Model.User.cs" />
+ <Compile Include="NonceDbStore.cs" />
<Compile Include="OAuthAuthenticationModule.cs" />
<Compile Include="OAuthAuthorizationManager.cs" />
<Compile Include="OAuthConsumerTokenManager.cs" />
@@ -104,6 +107,7 @@
<Compile Include="OAuthTokenManager.cs" />
<Compile Include="Policies.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="RelyingPartyApplicationDbStore.cs" />
<Compile Include="Utilities.cs" />
</ItemGroup>
<ItemGroup>
diff --git a/projecttemplates/RelyingPartyLogic/Utilities.cs b/projecttemplates/RelyingPartyLogic/Utilities.cs
index affbe26..5c9ae52 100644
--- a/projecttemplates/RelyingPartyLogic/Utilities.cs
+++ b/projecttemplates/RelyingPartyLogic/Utilities.cs
@@ -7,6 +7,10 @@
namespace RelyingPartyLogic {
using System;
using System.Collections.Generic;
+ using System.Data;
+ using System.Data.Common;
+ using System.Data.EntityClient;
+ using System.Data.Objects;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -17,7 +21,7 @@ namespace RelyingPartyLogic {
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Smo;
- public class Utilities {
+ public static class Utilities {
internal const string DefaultNamespace = "RelyingPartyLogic";
/// <summary>
@@ -61,6 +65,31 @@ GO
}
}
+ /// <summary>
+ /// Executes a SQL command against the SQL connection.
+ /// </summary>
+ /// <param name="objectContext">The object context.</param>
+ /// <param name="command">The command to execute.</param>
+ /// <returns>The result of executing the command.</returns>
+ public static int ExecuteCommand(this ObjectContext objectContext, string command) {
+ DbConnection connection = ((EntityConnection)objectContext.Connection).StoreConnection;
+ bool opening = (connection.State == ConnectionState.Closed);
+ if (opening) {
+ connection.Open();
+ }
+
+ DbCommand cmd = connection.CreateCommand();
+ cmd.CommandText = command;
+ cmd.CommandType = CommandType.StoredProcedure;
+ try {
+ return cmd.ExecuteNonQuery();
+ } finally {
+ if (opening && connection.State == ConnectionState.Open) {
+ connection.Close();
+ }
+ }
+ }
+
internal static void VerifyThrowNotLocalTime(DateTime value) {
// When we want UTC time, we have to accept Unspecified kind
// because that's how it is set to us in the database.
diff --git a/projecttemplates/WebFormsRelyingParty/Web.config b/projecttemplates/WebFormsRelyingParty/Web.config
index 0861893..901626b 100644
--- a/projecttemplates/WebFormsRelyingParty/Web.config
+++ b/projecttemplates/WebFormsRelyingParty/Web.config
@@ -48,8 +48,14 @@
with OPs that use Attribute Exchange (in various formats). -->
<add type="DotNetOpenAuth.OpenId.Behaviors.AXFetchAsSregTransform, DotNetOpenAuth" />
</behaviors>
+ <store type="RelyingPartyLogic.RelyingPartyApplicationDbStore, RelyingPartyLogic"/>
</relyingParty>
</openid>
+ <oauth>
+ <serviceProvider>
+ <store type="RelyingPartyLogic.NonceDbStore, RelyingPartyLogic"/>
+ </serviceProvider>
+ </oauth>
</dotNetOpenAuth>
<!-- log4net is a 3rd party (free) logger library that dotnetopenid will use if present but does not require. -->
<log4net>