diff options
Diffstat (limited to 'projecttemplates/RelyingPartyLogic')
9 files changed, 893 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. |