summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--projecttemplates/RelyingPartyLogic/CreateDatabase.sql89
-rw-r--r--projecttemplates/RelyingPartyLogic/Model.Designer.cs361
-rw-r--r--projecttemplates/RelyingPartyLogic/Model.OpenIdAssociation.cs18
-rw-r--r--projecttemplates/RelyingPartyLogic/Model.cs32
-rw-r--r--projecttemplates/RelyingPartyLogic/Model.edmx79
-rw-r--r--projecttemplates/RelyingPartyLogic/NonceDbStore.cs132
-rw-r--r--projecttemplates/RelyingPartyLogic/RelyingPartyApplicationDbStore.cs158
-rw-r--r--projecttemplates/RelyingPartyLogic/RelyingPartyLogic.csproj4
-rw-r--r--projecttemplates/RelyingPartyLogic/Utilities.cs31
-rw-r--r--projecttemplates/WebFormsRelyingParty/Web.config6
-rw-r--r--samples/OAuthServiceProvider/Members/Authorize.aspx20
-rw-r--r--samples/OpenIdRelyingPartyWebForms/Code/CustomStore.cs10
-rw-r--r--src/DotNetOpenAuth.Test/OAuth/OAuthCoordinator.cs3
-rw-r--r--src/DotNetOpenAuth.Test/OpenId/DiffieHellmanTests.cs4
-rw-r--r--src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd41
-rw-r--r--src/DotNetOpenAuth/Configuration/OAuthServiceProviderElement.cs15
-rw-r--r--src/DotNetOpenAuth/DotNetOpenAuth.csproj2
-rw-r--r--src/DotNetOpenAuth/Logger.cs1
-rw-r--r--src/DotNetOpenAuth/Messaging/Bindings/INonceStore.cs9
-rw-r--r--src/DotNetOpenAuth/Messaging/Bindings/NonceMemoryStore.cs7
-rw-r--r--src/DotNetOpenAuth/Messaging/MessageReceivingEndpoint.cs7
-rw-r--r--src/DotNetOpenAuth/Messaging/MessagingUtilities.cs13
-rw-r--r--src/DotNetOpenAuth/OAuth/ServiceProvider.cs36
-rw-r--r--src/DotNetOpenAuth/OpenId/Association.cs14
-rw-r--r--src/DotNetOpenAuth/OpenId/Extensions/AliasManager.cs7
-rw-r--r--src/DotNetOpenAuth/OpenId/Interop/OpenIdRelyingPartyShim.cs4
-rw-r--r--src/DotNetOpenAuth/OpenId/Messages/IOpenIdMessageExtension.cs108
-rw-r--r--src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs9
-rw-r--r--src/DotNetOpenAuth/OpenId/OpenIdStrings.resx3
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs4
-rw-r--r--src/DotNetOpenAuth/Strings.Designer.cs11
-rw-r--r--src/DotNetOpenAuth/Strings.resx5
-rw-r--r--src/DotNetOpenAuth/Xrds/TypeElement.cs4
-rw-r--r--src/DotNetOpenAuth/Xrds/XrdsNode.cs1
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 &lt;oauth/serviceProvider&gt; 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&lt;TElement&gt; 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&lt;TKey&gt;.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);