diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2009-11-27 12:10:03 -0700 |
---|---|---|
committer | Andrew Arnott <andrewarnott@gmail.com> | 2009-11-27 12:10:03 -0700 |
commit | 0ed1e01369d918ad828fa64728f89f9d2c675fdb (patch) | |
tree | 85ac9fb81153d6b8fb3774046f4fc28ab3f5df1f | |
parent | 1243c92a0172784f32938a2081f76463f90f102d (diff) | |
parent | 3d0b19ba07c1044b433d97e90ffe0489fee967dc (diff) | |
download | DotNetOpenAuth-0ed1e01369d918ad828fa64728f89f9d2c675fdb.zip DotNetOpenAuth-0ed1e01369d918ad828fa64728f89f9d2c675fdb.tar.gz DotNetOpenAuth-0ed1e01369d918ad828fa64728f89f9d2c675fdb.tar.bz2 |
Merge branch 'master' into extensibleDiscovery
Conflicts:
src/DotNetOpenAuth/OpenId/ProviderEndpointDescription.cs
34 files changed, 1189 insertions, 59 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> diff --git a/samples/OAuthServiceProvider/Members/Authorize.aspx b/samples/OAuthServiceProvider/Members/Authorize.aspx index 2f5edf1..321d7f3 100644 --- a/samples/OAuthServiceProvider/Members/Authorize.aspx +++ b/samples/OAuthServiceProvider/Members/Authorize.aspx @@ -12,15 +12,33 @@ runat="server" Text="[consumer]" /> wants access to your <asp:Label ID="desiredAccessLabel" Font-Bold="true" runat="server" Text="[protected resource]" />. </p> <p>Do you want to allow this? </p> - <div> + <div style="display: none" id="responseButtonsDiv"> <asp:Button ID="allowAccessButton" runat="server" Text="Yes" OnClick="allowAccessButton_Click" /> <asp:Button ID="denyAccessButton" runat="server" Text="No" OnClick="denyAccessButton_Click" /> </div> + <div id="javascriptDisabled"> + <b>Javascript appears to be disabled in your browser. </b>This page requires Javascript + to be enabled to better protect your security. + </div> <p>If you grant access now, you can revoke it at any time by returning to this page. </p> <asp:Panel runat="server" BackColor="Red" ForeColor="White" Font-Bold="true" Visible="false" ID="OAuth10ConsumerWarning"> This website is registered with service_PROVIDER_DOMAIN_NAME to make authorization requests, but has not been configured to send requests securely. If you grant access but you did not initiate this request at consumer_DOMAIN_NAME, it may be possible for other users of consumer_DOMAIN_NAME to access your data. We recommend you deny access unless you are certain that you initiated this request directly with consumer_DOMAIN_NAME. </asp:Panel> + <script language="javascript" type="text/javascript"> + //<![CDATA[ + // we use HTML to hide the action buttons and Javascript to show them + // to protect against click-jacking in an iframe whose javascript is disabled. + document.getElementById('responseButtonsDiv').style.display = 'block'; + document.getElementById('javascriptDisabled').style.display = 'none'; + + // Frame busting code (to protect us from being hosted in an iframe). + // This protects us from click-jacking. + if (document.location !== window.top.location) { + window.top.location = document.location; + } + //]]> + </script> </asp:View> <asp:View runat="server"> <p>Authorization has been granted.</p> diff --git a/samples/OpenIdRelyingPartyWebForms/Code/CustomStore.cs b/samples/OpenIdRelyingPartyWebForms/Code/CustomStore.cs index 07f209b..c4a3982 100644 --- a/samples/OpenIdRelyingPartyWebForms/Code/CustomStore.cs +++ b/samples/OpenIdRelyingPartyWebForms/Code/CustomStore.cs @@ -19,16 +19,6 @@ 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> diff --git a/src/DotNetOpenAuth.Test/OAuth/OAuthCoordinator.cs b/src/DotNetOpenAuth.Test/OAuth/OAuthCoordinator.cs index 4373402..5967a03 100644 --- a/src/DotNetOpenAuth.Test/OAuth/OAuthCoordinator.cs +++ b/src/DotNetOpenAuth.Test/OAuth/OAuthCoordinator.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.Test { using System; using System.Diagnostics.Contracts; using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.OAuth; using DotNetOpenAuth.OAuth.ChannelElements; using DotNetOpenAuth.Test.Mocks; @@ -58,7 +59,7 @@ namespace DotNetOpenAuth.Test { WebConsumer consumer = new WebConsumer(this.serviceDescription, consumerTokenManager) { OAuthChannel = consumerChannel, }; - ServiceProvider serviceProvider = new ServiceProvider(this.serviceDescription, serviceTokenManager) { + ServiceProvider serviceProvider = new ServiceProvider(this.serviceDescription, serviceTokenManager, new NonceMemoryStore()) { OAuthChannel = serviceProviderChannel, }; diff --git a/src/DotNetOpenAuth.Test/OpenId/DiffieHellmanTests.cs b/src/DotNetOpenAuth.Test/OpenId/DiffieHellmanTests.cs index fbbea71..426e19a 100644 --- a/src/DotNetOpenAuth.Test/OpenId/DiffieHellmanTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/DiffieHellmanTests.cs @@ -13,7 +13,7 @@ namespace DotNetOpenAuth.Test.OpenId { using Org.Mentalis.Security.Cryptography; [TestClass] - public class DiffieHellmanTests { + public class DiffieHellmanTests : OpenIdTestBase { [TestMethod] public void Test() { string s1 = Test1(); @@ -28,7 +28,9 @@ namespace DotNetOpenAuth.Test.OpenId { try { string line; + int lineNumber = 0; while ((line = reader.ReadLine()) != null) { + TestContext.WriteLine("\tLine {0}", ++lineNumber); string[] parts = line.Trim().Split(' '); byte[] x = Convert.FromBase64String(parts[0]); DiffieHellmanManaged dh = new DiffieHellmanManaged(AssociateDiffieHellmanRequest.DefaultMod, AssociateDiffieHellmanRequest.DefaultGen, x); diff --git a/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd b/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd index 0bef2df..7d10b21 100644 --- a/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd +++ b/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd @@ -293,6 +293,47 @@ <xs:attribute name="maxAuthenticationTime" type="xs:string" /> </xs:complexType> </xs:element> + <xs:element name="oauth"> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="consumer"> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="security"> + <xs:complexType> + + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> + <xs:element name="serviceProvider"> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="security"> + <xs:complexType> + <xs:attribute name="minimumRequiredOAuthVersion" default="V10"> + <xs:simpleType> + <xs:restriction base="xs:NMTOKEN"> + <xs:enumeration value="V10" /> + <xs:enumeration value="V10a" /> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + <xs:attribute name="maxAuthorizationTime" type="xs:string" default="0:05" /> + </xs:complexType> + </xs:element> + <xs:element name="store"> + <xs:complexType> + <xs:attribute name="type" type="xs:string"/> + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> </xs:choice> </xs:complexType> </xs:element> diff --git a/src/DotNetOpenAuth/Configuration/OAuthServiceProviderElement.cs b/src/DotNetOpenAuth/Configuration/OAuthServiceProviderElement.cs index 5ff528d..8e910a0 100644 --- a/src/DotNetOpenAuth/Configuration/OAuthServiceProviderElement.cs +++ b/src/DotNetOpenAuth/Configuration/OAuthServiceProviderElement.cs @@ -6,12 +6,18 @@ namespace DotNetOpenAuth.Configuration { using System.Configuration; + using DotNetOpenAuth.Messaging.Bindings; /// <summary> /// Represents the <oauth/serviceProvider> element in the host's .config file. /// </summary> internal class OAuthServiceProviderElement : ConfigurationElement { /// <summary> + /// The name of the custom store sub-element. + /// </summary> + private const string StoreConfigName = "store"; + + /// <summary> /// Gets the name of the security sub-element. /// </summary> private const string SecuritySettingsConfigName = "security"; @@ -23,6 +29,15 @@ namespace DotNetOpenAuth.Configuration { } /// <summary> + /// Gets or sets the type to use for storing application state. + /// </summary> + [ConfigurationProperty(StoreConfigName)] + public TypeConfigurationElement<INonceStore> ApplicationStore { + get { return (TypeConfigurationElement<INonceStore>)this[StoreConfigName] ?? new TypeConfigurationElement<INonceStore>(); } + set { this[StoreConfigName] = value; } + } + + /// <summary> /// Gets or sets the security settings. /// </summary> [ConfigurationProperty(SecuritySettingsConfigName)] diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj index 95a7150..5246b96 100644 --- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj +++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj @@ -75,7 +75,7 @@ http://opensource.org/licenses/ms-pl.html </CodeContractsCustomRewriterAssembly> <CodeContractsCustomRewriterClass> </CodeContractsCustomRewriterClass> - <CodeContractsRuntimeCheckingLevel>Full</CodeContractsRuntimeCheckingLevel> + <CodeContractsRuntimeCheckingLevel>ReleaseRequires</CodeContractsRuntimeCheckingLevel> <CodeContractsRunCodeAnalysis>False</CodeContractsRunCodeAnalysis> <CodeContractsBuildReferenceAssembly>True</CodeContractsBuildReferenceAssembly> <CodeContractsNonNullObligations>False</CodeContractsNonNullObligations> diff --git a/src/DotNetOpenAuth/Logger.cs b/src/DotNetOpenAuth/Logger.cs index 1c1b8a5..a9dbef2 100644 --- a/src/DotNetOpenAuth/Logger.cs +++ b/src/DotNetOpenAuth/Logger.cs @@ -143,6 +143,7 @@ namespace DotNetOpenAuth { /// <param name="name">A name that will be included in the log file.</param> /// <returns>The <see cref="ILog"/> instance created with the given name.</returns> internal static ILog CreateWithBanner(string name) { + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(name)); ILog log = Create(name); log.Info(Util.LibraryVersion); return log; diff --git a/src/DotNetOpenAuth/Messaging/Bindings/INonceStore.cs b/src/DotNetOpenAuth/Messaging/Bindings/INonceStore.cs index fff251a..6b6e2e1 100644 --- a/src/DotNetOpenAuth/Messaging/Bindings/INonceStore.cs +++ b/src/DotNetOpenAuth/Messaging/Bindings/INonceStore.cs @@ -19,11 +19,12 @@ namespace DotNetOpenAuth.Messaging.Bindings { /// 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="timestamp">The timestamp that together with the nonce string make it unique. + /// <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 nonce+timestamp (combination) was not previously in the database. - /// False if the nonce was stored previously with the same timestamp. + /// 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 @@ -33,6 +34,6 @@ namespace DotNetOpenAuth.Messaging.Bindings { /// property, accessible via the <see cref="DotNetOpenAuth.Configuration.DotNetOpenAuthSection.Configuration"/> /// property. /// </remarks> - bool StoreNonce(string context, string nonce, DateTime timestamp); + bool StoreNonce(string context, string nonce, DateTime timestampUtc); } } diff --git a/src/DotNetOpenAuth/Messaging/Bindings/NonceMemoryStore.cs b/src/DotNetOpenAuth/Messaging/Bindings/NonceMemoryStore.cs index 3d624a6..6e64acc 100644 --- a/src/DotNetOpenAuth/Messaging/Bindings/NonceMemoryStore.cs +++ b/src/DotNetOpenAuth/Messaging/Bindings/NonceMemoryStore.cs @@ -47,6 +47,13 @@ namespace DotNetOpenAuth.Messaging.Bindings { /// <summary> /// Initializes a new instance of the <see cref="NonceMemoryStore"/> class. /// </summary> + internal NonceMemoryStore() + : this(StandardExpirationBindingElement.MaximumMessageAge) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="NonceMemoryStore"/> class. + /// </summary> /// <param name="maximumMessageAge">The maximum age a message can be before it is discarded.</param> internal NonceMemoryStore(TimeSpan maximumMessageAge) { this.maximumMessageAge = maximumMessageAge; diff --git a/src/DotNetOpenAuth/Messaging/MessageReceivingEndpoint.cs b/src/DotNetOpenAuth/Messaging/MessageReceivingEndpoint.cs index c7ac581..e26a672 100644 --- a/src/DotNetOpenAuth/Messaging/MessageReceivingEndpoint.cs +++ b/src/DotNetOpenAuth/Messaging/MessageReceivingEndpoint.cs @@ -21,7 +21,10 @@ namespace DotNetOpenAuth.Messaging { /// <param name="locationUri">The URL of this endpoint.</param> /// <param name="method">The HTTP method(s) allowed.</param> public MessageReceivingEndpoint(string locationUri, HttpDeliveryMethods method) - : this(new Uri(locationUri), method) { } + : this(new Uri(locationUri), method) { + Contract.Requires<ArgumentNullException>(locationUri != null); + Contract.Requires<ArgumentOutOfRangeException>(method != HttpDeliveryMethods.None); + } /// <summary> /// Initializes a new instance of the <see cref="MessageReceivingEndpoint"/> class. @@ -30,7 +33,7 @@ namespace DotNetOpenAuth.Messaging { /// <param name="method">The HTTP method(s) allowed.</param> public MessageReceivingEndpoint(Uri location, HttpDeliveryMethods method) { Contract.Requires<ArgumentNullException>(location != null); - Contract.Requires<ArgumentOutOfRangeException>(method != HttpDeliveryMethods.None, "method"); + Contract.Requires<ArgumentOutOfRangeException>(method != HttpDeliveryMethods.None); Contract.Requires<ArgumentOutOfRangeException>((method & HttpDeliveryMethods.HttpVerbMask) != 0, MessagingStrings.GetOrPostFlagsRequired); this.Location = location; diff --git a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs index ba68227..e1e3f59 100644 --- a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs +++ b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs @@ -326,6 +326,10 @@ namespace DotNetOpenAuth.Messaging { /// The positions are NOT reset after copying is complete. /// </remarks> internal static int CopyTo(this Stream copyFrom, Stream copyTo) { + Contract.Requires<ArgumentNullException>(copyFrom != null); + Contract.Requires<ArgumentNullException>(copyTo != null); + Contract.Requires<ArgumentException>(copyFrom.CanRead, MessagingStrings.StreamUnreadable); + Contract.Requires<ArgumentException>(copyTo.CanWrite, MessagingStrings.StreamUnwritable); return CopyTo(copyFrom, copyTo, int.MaxValue); } @@ -366,6 +370,7 @@ namespace DotNetOpenAuth.Messaging { /// <returns>A seekable stream with the same contents as the original.</returns> internal static Stream CreateSnapshot(this Stream copyFrom) { Contract.Requires<ArgumentNullException>(copyFrom != null); + Contract.Requires<ArgumentException>(copyFrom.CanRead); MemoryStream copyTo = new MemoryStream(copyFrom.CanSeek ? (int)copyFrom.Length : 4 * 1024); copyFrom.CopyTo(copyTo); @@ -380,6 +385,7 @@ namespace DotNetOpenAuth.Messaging { /// <returns>The newly created instance.</returns> internal static HttpWebRequest Clone(this HttpWebRequest request) { Contract.Requires<ArgumentNullException>(request != null); + Contract.Requires<ArgumentException>(request.RequestUri != null); return Clone(request, request.RequestUri); } @@ -547,6 +553,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="second">The second dictionary in the comparison. May not be null.</param> /// <returns>True if the arrays equal; false otherwise.</returns> internal static bool AreEquivalent<TKey, TValue>(IDictionary<TKey, TValue> first, IDictionary<TKey, TValue> second) { + Contract.Requires<ArgumentNullException>(first != null); + Contract.Requires<ArgumentNullException>(second != null); return AreEquivalent(first.ToArray(), second.ToArray()); } @@ -752,6 +760,9 @@ namespace DotNetOpenAuth.Messaging { /// <param name="comparer">A comparison function to compare keys.</param> /// <returns>An System.Linq.IOrderedEnumerable<TElement> whose elements are sorted according to a key.</returns> internal static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Comparison<TKey> comparer) { + Contract.Requires<ArgumentNullException>(source != null); + Contract.Requires<ArgumentNullException>(comparer != null); + Contract.Requires<ArgumentNullException>(keySelector != null); Contract.Ensures(Contract.Result<IOrderedEnumerable<TSource>>() != null); return System.Linq.Enumerable.OrderBy<TSource, TKey>(source, keySelector, new ComparisonHelper<TKey>(comparer)); } @@ -850,6 +861,8 @@ namespace DotNetOpenAuth.Messaging { /// host actually having this configuration element present. /// </remarks> internal static string EscapeUriDataStringRfc3986(string value) { + Contract.Requires<ArgumentNullException>(value != null); + // Start with RFC 2396 escaping by calling the .NET method to do the work. // This MAY sometimes exhibit RFC 3986 behavior (according to the documentation). // If it does, the escaping we do that follows it will be a no-op since the diff --git a/src/DotNetOpenAuth/OAuth/ServiceProvider.cs b/src/DotNetOpenAuth/OAuth/ServiceProvider.cs index 5883273..e2c82bb 100644 --- a/src/DotNetOpenAuth/OAuth/ServiceProvider.cs +++ b/src/DotNetOpenAuth/OAuth/ServiceProvider.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OAuth { using System; using System.Collections.Generic; + using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Globalization; @@ -36,6 +37,12 @@ namespace DotNetOpenAuth.OAuth { /// </remarks> public class ServiceProvider : IDisposable { /// <summary> + /// The name of the key to use in the HttpApplication cache to store the + /// instance of <see cref="NonceMemoryStore"/> to use. + /// </summary> + private const string ApplicationStoreKey = "DotNetOpenAuth.OAuth.ServiceProvider.HttpApplicationStore"; + + /// <summary> /// The length of the verifier code (in raw bytes before base64 encoding) to generate. /// </summary> private const int VerifierCodeLength = 5; @@ -61,7 +68,7 @@ namespace DotNetOpenAuth.OAuth { /// <param name="tokenManager">The host's method of storing and recalling tokens and secrets.</param> /// <param name="messageTypeProvider">An object that can figure out what type of message is being received for deserialization.</param> public ServiceProvider(ServiceProviderDescription serviceDescription, IServiceProviderTokenManager tokenManager, OAuthServiceProviderMessageFactory messageTypeProvider) - : this(serviceDescription, tokenManager, new NonceMemoryStore(StandardExpirationBindingElement.MaximumMessageAge), messageTypeProvider) { + : this(serviceDescription, tokenManager, DotNetOpenAuthSection.Configuration.OAuth.ServiceProvider.ApplicationStore.CreateInstance(HttpApplicationStore), messageTypeProvider) { Contract.Requires<ArgumentNullException>(serviceDescription != null); Contract.Requires<ArgumentNullException>(tokenManager != null); Contract.Requires<ArgumentNullException>(messageTypeProvider != null); @@ -98,6 +105,33 @@ namespace DotNetOpenAuth.OAuth { } /// <summary> + /// Gets the standard state storage mechanism that uses ASP.NET's + /// HttpApplication state dictionary to store associations and nonces. + /// </summary> + [EditorBrowsable(EditorBrowsableState.Advanced)] + public static INonceStore HttpApplicationStore { + get { + Contract.Ensures(Contract.Result<INonceStore>() != null); + + HttpContext context = HttpContext.Current; + ErrorUtilities.VerifyOperation(context != null, Strings.StoreRequiredWhenNoHttpContextAvailable, typeof(INonceStore).Name); + var store = (INonceStore)context.Application[ApplicationStoreKey]; + if (store == null) { + context.Application.Lock(); + try { + if ((store = (INonceStore)context.Application[ApplicationStoreKey]) == null) { + context.Application[ApplicationStoreKey] = store = new NonceMemoryStore(StandardExpirationBindingElement.MaximumMessageAge); + } + } finally { + context.Application.UnLock(); + } + } + + return store; + } + } + + /// <summary> /// Gets the description of this Service Provider. /// </summary> public ServiceProviderDescription ServiceDescription { get; private set; } diff --git a/src/DotNetOpenAuth/OpenId/Association.cs b/src/DotNetOpenAuth/OpenId/Association.cs index d2c84cb..311ba58 100644 --- a/src/DotNetOpenAuth/OpenId/Association.cs +++ b/src/DotNetOpenAuth/OpenId/Association.cs @@ -54,7 +54,7 @@ namespace DotNetOpenAuth.OpenId { public string Handle { get; private set; } /// <summary> - /// Gets the time when this <see cref="Association"/> will expire. + /// Gets the UTC time when this <see cref="Association"/> will expire. /// </summary> public DateTime Expires { get { return this.Issued + this.TotalLifeLength; } @@ -83,7 +83,7 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> - /// Gets or sets the time that this <see cref="Association"/> was first created. + /// Gets or sets the UTC time that this <see cref="Association"/> was first created. /// </summary> internal DateTime Issued { get; set; } @@ -146,8 +146,8 @@ namespace DotNetOpenAuth.OpenId { /// <param name="handle"> /// The <see cref="Handle"/> property of the previous <see cref="Association"/> instance. /// </param> - /// <param name="expires"> - /// The value of the <see cref="Expires"/> property of the previous <see cref="Association"/> instance. + /// <param name="expiresUtc"> + /// The UTC value of the <see cref="Expires"/> property of the previous <see cref="Association"/> instance. /// </param> /// <param name="privateData"> /// The byte array returned by a call to <see cref="SerializePrivateData"/> on the previous @@ -158,13 +158,13 @@ namespace DotNetOpenAuth.OpenId { /// from a custom association store's /// <see cref="IAssociationStore<TKey>.GetAssociation(TKey, SecuritySettings)"/> method. /// </returns> - public static Association Deserialize(string handle, DateTime expires, byte[] privateData) { + public static Association Deserialize(string handle, DateTime expiresUtc, byte[] privateData) { Contract.Requires<ArgumentNullException>(!String.IsNullOrEmpty(handle)); Contract.Requires<ArgumentNullException>(privateData != null); Contract.Ensures(Contract.Result<Association>() != null); - expires = expires.ToUniversalTimeSafe(); - TimeSpan remainingLifeLength = expires - DateTime.UtcNow; + expiresUtc = expiresUtc.ToUniversalTimeSafe(); + TimeSpan remainingLifeLength = expiresUtc - DateTime.UtcNow; byte[] secret = privateData; // the whole of privateData is the secret key for now. // We figure out what derived type to instantiate based on the length of the secret. try { diff --git a/src/DotNetOpenAuth/OpenId/Extensions/AliasManager.cs b/src/DotNetOpenAuth/OpenId/Extensions/AliasManager.cs index 13f9907..0a84266 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/AliasManager.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/AliasManager.cs @@ -20,7 +20,7 @@ namespace DotNetOpenAuth.OpenId.Extensions { /// <summary> /// The format of auto-generated aliases. /// </summary> - private readonly string aliasFormat = "alias{0}"; + private const string AliasFormat = "alias{0}"; /// <summary> /// Tracks extension Type URIs and aliases assigned to them. @@ -68,6 +68,8 @@ namespace DotNetOpenAuth.OpenId.Extensions { /// <param name="typeUris">The type URIs to create aliases for.</param> /// <param name="preferredTypeUriToAliases">An optional dictionary of URI/alias pairs that suggest preferred aliases to use if available for certain type URIs.</param> public void AssignAliases(IEnumerable<string> typeUris, IDictionary<string, string> preferredTypeUriToAliases) { + Contract.Requires<ArgumentNullException>(typeUris != null); + // First go through the actually used type URIs and see which ones have matching preferred aliases. if (preferredTypeUriToAliases != null) { foreach (string typeUri in typeUris) { @@ -126,6 +128,7 @@ namespace DotNetOpenAuth.OpenId.Extensions { /// <returns>The Type URI.</returns> /// <exception cref="ArgumentOutOfRangeException">Thrown if the given alias does not have a matching TypeURI.</exception> public string ResolveAlias(string alias) { + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(alias)); string typeUri = this.TryResolveAlias(alias); if (typeUri == null) { throw new ArgumentOutOfRangeException("alias"); @@ -175,7 +178,7 @@ namespace DotNetOpenAuth.OpenId.Extensions { private string AssignNewAlias(string typeUri) { Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(typeUri)); ErrorUtilities.VerifyInternal(!this.typeUriToAliasMap.ContainsKey(typeUri), "Oops! This type URI already has an alias!"); - string alias = string.Format(CultureInfo.InvariantCulture, this.aliasFormat, this.typeUriToAliasMap.Count + 1); + string alias = string.Format(CultureInfo.InvariantCulture, AliasFormat, this.typeUriToAliasMap.Count + 1); this.typeUriToAliasMap.Add(typeUri, alias); this.aliasToTypeUriMap.Add(alias, typeUri); return alias; diff --git a/src/DotNetOpenAuth/OpenId/Interop/OpenIdRelyingPartyShim.cs b/src/DotNetOpenAuth/OpenId/Interop/OpenIdRelyingPartyShim.cs index 41c4e21..86e80ba 100644 --- a/src/DotNetOpenAuth/OpenId/Interop/OpenIdRelyingPartyShim.cs +++ b/src/DotNetOpenAuth/OpenId/Interop/OpenIdRelyingPartyShim.cs @@ -16,8 +16,8 @@ namespace DotNetOpenAuth.OpenId.Interop { using DotNetOpenAuth.OpenId.RelyingParty; /// <summary> - /// The COM interface describing the DotNetOpenId functionality available to - /// COM client relying parties. + /// The COM interface describing the DotNetOpenAuth functionality available to + /// COM client OpenID relying parties. /// </summary> [Guid("56BD3DB0-EE0D-4191-ADFC-1F3705CD2636")] [InterfaceType(ComInterfaceType.InterfaceIsDual)] diff --git a/src/DotNetOpenAuth/OpenId/Messages/IOpenIdMessageExtension.cs b/src/DotNetOpenAuth/OpenId/Messages/IOpenIdMessageExtension.cs index 95080e6..57233ac 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/IOpenIdMessageExtension.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/IOpenIdMessageExtension.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OpenId.Messages { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.Linq; using System.Text; using DotNetOpenAuth.Messaging; @@ -19,6 +20,7 @@ namespace DotNetOpenAuth.OpenId.Messages { /// [<see cref="SerializableAttribute"/>] to allow serializing state servers /// to cache messages, particularly responses. /// </remarks> + [ContractClass(typeof(IOpenIdMessageExtensionContract))] public interface IOpenIdMessageExtension : IExtensionMessage { /// <summary> /// Gets the TypeURI the extension uses in the OpenID protocol and in XRDS advertisements. @@ -51,4 +53,110 @@ namespace DotNetOpenAuth.OpenId.Messages { /// </value> bool IsSignedByRemoteParty { get; set; } } + + /// <summary> + /// Code contract class for the IOpenIdMessageExtension interface. + /// </summary> + [ContractClassFor(typeof(IOpenIdMessageExtension))] + internal class IOpenIdMessageExtensionContract : IOpenIdMessageExtension { + /// <summary> + /// Prevents a default instance of the <see cref="IOpenIdMessageExtensionContract"/> class from being created. + /// </summary> + private IOpenIdMessageExtensionContract() { + } + + #region IOpenIdMessageExtension Members + + /// <summary> + /// Gets the TypeURI the extension uses in the OpenID protocol and in XRDS advertisements. + /// </summary> + string IOpenIdMessageExtension.TypeUri { + get { + Contract.Ensures(!String.IsNullOrEmpty(Contract.Result<string>())); + throw new NotImplementedException(); + } + } + + /// <summary> + /// Gets the additional TypeURIs that are supported by this extension, in preferred order. + /// May be empty if none other than <see cref="IOpenIdMessageExtension.TypeUri"/> is supported, but + /// should not be null. + /// </summary> + /// <remarks> + /// Useful for reading in messages with an older version of an extension. + /// The value in the <see cref="IOpenIdMessageExtension.TypeUri"/> property is always checked before + /// trying this list. + /// If you do support multiple versions of an extension using this method, + /// consider adding a CreateResponse method to your request extension class + /// so that the response can have the context it needs to remain compatible + /// given the version of the extension in the request message. + /// The <see cref="Extensions.SimpleRegistration.ClaimsRequest.CreateResponse"/> for an example. + /// </remarks> + IEnumerable<string> IOpenIdMessageExtension.AdditionalSupportedTypeUris { + get { + Contract.Ensures(Contract.Result<IEnumerable<string>>() != null); + throw new NotImplementedException(); + } + } + + /// <summary> + /// Gets or sets a value indicating whether this extension was + /// signed by the sender. + /// </summary> + /// <value> + /// <c>true</c> if this instance is signed by the sender; otherwise, <c>false</c>. + /// </value> + bool IOpenIdMessageExtension.IsSignedByRemoteParty { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + #endregion + + #region IMessage Members + + /// <summary> + /// Gets the version of the protocol or extension this message is prepared to implement. + /// </summary> + /// <remarks> + /// Implementations of this interface should ensure that this property never returns null. + /// </remarks> + Version IMessage.Version { + get { + Contract.Ensures(Contract.Result<Version>() != null); + throw new NotImplementedException(); + } + } + + /// <summary> + /// Gets the extra, non-standard Protocol parameters included in the message. + /// </summary> + /// <remarks> + /// Implementations of this interface should ensure that this property never returns null. + /// </remarks> + IDictionary<string, string> IMessage.ExtraData { + get { + Contract.Ensures(Contract.Result<IDictionary<string, string>>() != null); + throw new NotImplementedException(); + } + } + + /// <summary> + /// Checks the message state for conformity to the protocol specification + /// and throws an exception if the message is invalid. + /// </summary> + /// <remarks> + /// <para>Some messages have required fields, or combinations of fields that must relate to each other + /// in specialized ways. After deserializing a message, this method checks the state of the + /// message to see if it conforms to the protocol.</para> + /// <para>Note that this property should <i>not</i> check signatures or perform any state checks + /// outside this scope of this particular message.</para> + /// </remarks> + /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception> + void IMessage.EnsureValidMessage() { + throw new NotImplementedException(); + } + + #endregion + } } diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs index 05daed0..33a16f8 100644 --- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs +++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs @@ -632,15 +632,6 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> - /// Looks up a localized string similar to No current HttpContext was detected, so an {0} instance must be explicitly provided or specified in the .config file. Call the constructor overload that takes an {0}.. - /// </summary> - internal static string StoreRequiredWhenNoHttpContextAvailable { - get { - return ResourceManager.GetString("StoreRequiredWhenNoHttpContextAvailable", resourceCulture); - } - } - - /// <summary> /// Looks up a localized string similar to The type must implement {0}.. /// </summary> internal static string TypeMustImplementX { diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx index 919d873..c5f506d 100644 --- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx +++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx @@ -244,9 +244,6 @@ Discovered endpoint info: <data name="XriResolutionFailed" xml:space="preserve"> <value>XRI resolution failed.</value> </data> - <data name="StoreRequiredWhenNoHttpContextAvailable" xml:space="preserve"> - <value>No current HttpContext was detected, so an {0} instance must be explicitly provided or specified in the .config file. Call the constructor overload that takes an {0}.</value> - </data> <data name="AttributeAlreadyAdded" xml:space="preserve"> <value>An attribute with type URI '{0}' has already been added.</value> </data> diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs index 8d6b14a..76b0f10 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs @@ -41,7 +41,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// The name of the key to use in the HttpApplication cache to store the /// instance of <see cref="StandardRelyingPartyApplicationStore"/> to use. /// </summary> - private const string ApplicationStoreKey = "DotNetOpenAuth.OpenId.RelyingParty.OpenIdRelyingParty.ApplicationStore"; + private const string ApplicationStoreKey = "DotNetOpenAuth.OpenId.RelyingParty.OpenIdRelyingParty.HttpApplicationStore"; /// <summary> /// Backing store for the <see cref="Behaviors"/> property. @@ -138,7 +138,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { Contract.Ensures(Contract.Result<IRelyingPartyApplicationStore>() != null); HttpContext context = HttpContext.Current; - ErrorUtilities.VerifyOperation(context != null, OpenIdStrings.StoreRequiredWhenNoHttpContextAvailable, typeof(IRelyingPartyApplicationStore).Name); + ErrorUtilities.VerifyOperation(context != null, Strings.StoreRequiredWhenNoHttpContextAvailable, typeof(IRelyingPartyApplicationStore).Name); var store = (IRelyingPartyApplicationStore)context.Application[ApplicationStoreKey]; if (store == null) { context.Application.Lock(); diff --git a/src/DotNetOpenAuth/Strings.Designer.cs b/src/DotNetOpenAuth/Strings.Designer.cs index 43fec22..38c89f7 100644 --- a/src/DotNetOpenAuth/Strings.Designer.cs +++ b/src/DotNetOpenAuth/Strings.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:2.0.50727.4918 +// Runtime Version:2.0.50727.4927 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -70,6 +70,15 @@ namespace DotNetOpenAuth { } /// <summary> + /// Looks up a localized string similar to No current HttpContext was detected, so an {0} instance must be explicitly provided or specified in the .config file. Call the constructor overload that takes an {0}.. + /// </summary> + internal static string StoreRequiredWhenNoHttpContextAvailable { + get { + return ResourceManager.GetString("StoreRequiredWhenNoHttpContextAvailable", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to The configuration XAML reference to {0} requires a current HttpContext to resolve.. /// </summary> internal static string ConfigurationXamlReferenceRequiresHttpContext { diff --git a/src/DotNetOpenAuth/Strings.resx b/src/DotNetOpenAuth/Strings.resx index bbfa162..a7f080d 100644 --- a/src/DotNetOpenAuth/Strings.resx +++ b/src/DotNetOpenAuth/Strings.resx @@ -120,7 +120,10 @@ <data name="ConfigurationTypeMustBePublic" xml:space="preserve"> <value>The configuration-specified type {0} must be public, and is not.</value> </data> + <data name="StoreRequiredWhenNoHttpContextAvailable" xml:space="preserve"> + <value>No current HttpContext was detected, so an {0} instance must be explicitly provided or specified in the .config file. Call the constructor overload that takes an {0}.</value> + </data> <data name="ConfigurationXamlReferenceRequiresHttpContext" xml:space="preserve"> <value>The configuration XAML reference to {0} requires a current HttpContext to resolve.</value> </data> -</root>
\ No newline at end of file +</root> diff --git a/src/DotNetOpenAuth/Xrds/TypeElement.cs b/src/DotNetOpenAuth/Xrds/TypeElement.cs index f6c2217..c413629 100644 --- a/src/DotNetOpenAuth/Xrds/TypeElement.cs +++ b/src/DotNetOpenAuth/Xrds/TypeElement.cs @@ -5,6 +5,8 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.Xrds { + using System; + using System.Diagnostics.Contracts; using System.Xml.XPath; /// <summary> @@ -18,6 +20,8 @@ namespace DotNetOpenAuth.Xrds { /// <param name="parent">The parent.</param> public TypeElement(XPathNavigator typeElement, ServiceElement parent) : base(typeElement, parent) { + Contract.Requires<ArgumentNullException>(typeElement != null); + Contract.Requires<ArgumentNullException>(parent != null); } /// <summary> diff --git a/src/DotNetOpenAuth/Xrds/XrdsNode.cs b/src/DotNetOpenAuth/Xrds/XrdsNode.cs index 5e7d7e7..f8fa0af 100644 --- a/src/DotNetOpenAuth/Xrds/XrdsNode.cs +++ b/src/DotNetOpenAuth/Xrds/XrdsNode.cs @@ -45,6 +45,7 @@ namespace DotNetOpenAuth.Xrds { /// <param name="document">The document's root node, which this instance represents.</param> protected XrdsNode(XPathNavigator document) { Contract.Requires<ArgumentNullException>(document != null); + Contract.Requires<ArgumentException>(document.NameTable != null); this.Node = document; this.XmlNamespaceResolver = new XmlNamespaceManager(document.NameTable); |