diff options
Diffstat (limited to 'src')
283 files changed, 11876 insertions, 3786 deletions
diff --git a/src/DotNetOpenAuth.BuildTasks/AddProjectItems.cs b/src/DotNetOpenAuth.BuildTasks/AddProjectItems.cs new file mode 100644 index 0000000..30fa284 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/AddProjectItems.cs @@ -0,0 +1,62 @@ +//----------------------------------------------------------------------- +// <copyright file="AddProjectItems.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using Microsoft.Build.BuildEngine; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + using System.Collections; + + public class AddProjectItems : Task { + /// <summary> + /// Gets or sets the projects to add items to. + /// </summary> + /// <value>The projects.</value> + [Required] + public ITaskItem[] Projects { get; set; } + + /// <summary> + /// Gets or sets the items to add to each project. + /// </summary> + /// <value>The items.</value> + /// <remarks> + /// Use the metadata "ItemType" on each item to specify the item type to use for the new + /// project item. If the metadata is absent, "None" is used as the item type. + /// </remarks> + [Required] + public ITaskItem[] Items { get; set; } + + /// <summary> + /// Executes this instance. + /// </summary> + public override bool Execute() { + foreach (var projectTaskItem in this.Projects) { + var project = new Project(); + project.Load(projectTaskItem.ItemSpec); + + foreach (var projectItem in this.Items) { + string itemType = projectItem.GetMetadata("ItemType"); + if (string.IsNullOrEmpty(itemType)) { + itemType = "None"; + } + BuildItem newItem = project.AddNewItem(itemType, projectItem.ItemSpec, false); + var customMetadata = projectItem.CloneCustomMetadata(); + foreach (DictionaryEntry entry in customMetadata) { + newItem.SetMetadata((string)entry.Key, (string)entry.Value); + } + } + + project.Save(projectTaskItem.ItemSpec); + } + + return !this.Log.HasLoggedErrors; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/ChangeAssemblyReference.cs b/src/DotNetOpenAuth.BuildTasks/ChangeAssemblyReference.cs new file mode 100644 index 0000000..3ad5eb0 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/ChangeAssemblyReference.cs @@ -0,0 +1,47 @@ +using System; +using System.Linq; +using System.IO; +using System.Xml; + +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Microsoft.Build.BuildEngine; + +namespace DotNetOpenAuth.BuildTasks { + /// <summary> + /// Replaces Reference items HintPaths in a set of projects. + /// </summary> + public class ChangeAssemblyReference : Task { + /// <summary> + /// The projects to alter. + /// </summary> + [Required] + public ITaskItem[] Projects { get; set; } + /// <summary> + /// The project reference to remove. + /// </summary> + [Required] + public string OldReference { get; set; } + /// <summary> + /// The assembly reference to add. + /// </summary> + [Required] + public string NewReference { get; set; } + + const string msbuildNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"; + public override bool Execute() { + foreach (var project in Projects) { + Project doc = new Project(); + doc.Load(project.ItemSpec); + + var reference = doc.GetEvaluatedItemsByName("Reference").OfType<BuildItem>(). + Where(item => string.Equals(item.GetMetadata("HintPath"), OldReference, StringComparison.OrdinalIgnoreCase)).Single(); + reference.SetMetadata("HintPath", NewReference); + + doc.Save(project.ItemSpec); + } + + return true; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/ChangeProjectReferenceToAssemblyReference.cs b/src/DotNetOpenAuth.BuildTasks/ChangeProjectReferenceToAssemblyReference.cs new file mode 100644 index 0000000..f940a72 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/ChangeProjectReferenceToAssemblyReference.cs @@ -0,0 +1,52 @@ +using System; +using System.Linq; +using System.IO; +using System.Xml; + +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Microsoft.Build.BuildEngine; + +namespace DotNetOpenAuth.BuildTasks { + /// <summary> + /// Replaces ProjectReference items in a set of projects with Reference items. + /// </summary> + public class ChangeProjectReferenceToAssemblyReference : Task { + /// <summary> + /// The projects to alter. + /// </summary> + [Required] + public ITaskItem[] Projects { get; set; } + /// <summary> + /// The project reference to remove. + /// </summary> + [Required] + public string ProjectReference { get; set; } + /// <summary> + /// The assembly reference to add. + /// </summary> + [Required] + public string Reference { get; set; } + + const string msbuildNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"; + public override bool Execute() { + foreach (var project in Projects) { + Log.LogMessage(MessageImportance.Normal, "Changing P2P references to assembly references in \"{0}\".", project.ItemSpec); + + Project doc = new Project(); + doc.Load(project.ItemSpec); + + var projectReference = doc.EvaluatedItems.OfType<BuildItem>().Where( + item => item.Name == "ProjectReference" && item.Include == ProjectReference).Single(); + doc.RemoveItem(projectReference); + + var newReference = doc.AddNewItem("Reference", Path.GetFileNameWithoutExtension(Reference), true); + newReference.SetMetadata("HintPath", Reference); + + doc.Save(project.ItemSpec); + } + + return true; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/CheckAdminRights.cs b/src/DotNetOpenAuth.BuildTasks/CheckAdminRights.cs new file mode 100644 index 0000000..9cfd35d --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/CheckAdminRights.cs @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------- +// <copyright file="CheckAdminRights.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using System.Security.Principal; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + public class CheckAdminRights : Task { + /// <summary> + /// Gets or sets a value indicating whether this process has elevated permissions. + /// </summary> + [Output] + public bool IsElevated { get; set; } + + /// <summary> + /// Executes this instance. + /// </summary> + public override bool Execute() { + WindowsIdentity id = WindowsIdentity.GetCurrent(); + WindowsPrincipal p = new WindowsPrincipal(id); + this.IsElevated = p.IsInRole(WindowsBuiltInRole.Administrator); + + return true; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/CompareFiles.cs b/src/DotNetOpenAuth.BuildTasks/CompareFiles.cs new file mode 100644 index 0000000..691df20 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/CompareFiles.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.Build.Utilities; +using Microsoft.Build.Framework; +using System.IO; + +namespace DotNetOpenAuth.BuildTasks { + public class CompareFiles : Task { + /// <summary> + /// One set of items to compare. + /// </summary> + [Required] + public ITaskItem[] OriginalItems { get; set; } + + /// <summary> + /// The other set of items to compare. + /// </summary> + [Required] + public ITaskItem[] NewItems { get; set; } + + /// <summary> + /// Gets whether the items lists contain items that are identical going down the list. + /// </summary> + [Output] + public bool AreSame { get; private set; } + + /// <summary> + /// Same as <see cref="AreSame"/>, but opposite. + /// </summary> + [Output] + public bool AreChanged { get { return !AreSame; } } + + public override bool Execute() { + AreSame = AreFilesIdentical(); + return true; + } + + private bool AreFilesIdentical() { + if (OriginalItems.Length != NewItems.Length) { + return false; + } + + for (int i = 0; i < OriginalItems.Length; i++) { + if (!IsContentOfFilesTheSame(OriginalItems[i].ItemSpec, NewItems[i].ItemSpec)) { + return false; + } + } + + return true; + } + + private bool IsContentOfFilesTheSame(string file1, string file2) { + // If exactly one file is missing, that's different. + if (File.Exists(file1) ^ File.Exists(file2)) return false; + // If both are missing, that's the same. + if (!File.Exists(file1)) return true; + // If both are present, we need to do a content comparison. + using (FileStream fileStream1 = File.OpenRead(file1)) { + using (FileStream fileStream2 = File.OpenRead(file2)) { + if (fileStream1.Length != fileStream2.Length) return false; + byte[] buffer1 = new byte[4096]; + byte[] buffer2 = new byte[buffer1.Length]; + int bytesRead; + do { + bytesRead = fileStream1.Read(buffer1, 0, buffer1.Length); + if (fileStream2.Read(buffer2, 0, buffer2.Length) != bytesRead) { + // We should never get here since we compared file lengths, but + // this is a sanity check. + return false; + } + for (int i = 0; i < bytesRead; i++) { + if (buffer1[i] != buffer2[i]) { + return false; + } + } + } while (bytesRead == buffer1.Length); + } + } + + return true; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/CopyWithTokenSubstitution.cs b/src/DotNetOpenAuth.BuildTasks/CopyWithTokenSubstitution.cs new file mode 100644 index 0000000..3b81978 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/CopyWithTokenSubstitution.cs @@ -0,0 +1,108 @@ +//----------------------------------------------------------------------- +// <copyright file="CopyWithTokenSubstitution.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + /// <summary> + /// Copies files and performs a search and replace for given tokens in their contents during the process. + /// </summary> + public class CopyWithTokenSubstitution : Task { + /// <summary> + /// Gets or sets a value indicating whether the task should + /// skip the copying of files that are unchanged between the source and destination. + /// </summary> + /// <value> + /// <c>true</c> to skip copying files where the destination files are newer than the source files; otherwise, <c>false</c> to copy all files. + /// </value> + public bool SkipUnchangedFiles { get; set; } + + /// <summary> + /// Gets or sets the files to copy. + /// </summary> + /// <value>The files to copy.</value> + [Required] + public ITaskItem[] SourceFiles { get; set; } + + /// <summary> + /// Gets or sets a list of files to copy the source files to. + /// </summary> + /// <value>The list of files to copy the source files to.</value> + [Required] + public ITaskItem[] DestinationFiles { get; set; } + + /// <summary> + /// Gets or sets the destination files actually copied to. + /// </summary> + /// <remarks> + /// In the case of error partway through, or files not copied due to already being up to date, + /// this can be a subset of the <see cref="DestinationFiles"/> array. + /// </remarks> + [Output] + public ITaskItem[] CopiedFiles { get; set; } + + /// <summary> + /// Executes this instance. + /// </summary> + /// <returns><c>true</c> if the operation was successful.</returns> + public override bool Execute() { + if (this.SourceFiles.Length != this.DestinationFiles.Length) { + Log.LogError("{0} inputs and {1} outputs given.", this.SourceFiles.Length, this.DestinationFiles.Length); + return false; + } + + var copiedFiles = new List<ITaskItem>(this.DestinationFiles.Length); + + for (int i = 0; i < this.SourceFiles.Length; i++) { + string sourcePath = this.SourceFiles[i].ItemSpec; + string destPath = this.DestinationFiles[i].ItemSpec; + + if (this.SkipUnchangedFiles && File.GetLastWriteTimeUtc(sourcePath) < File.GetLastWriteTimeUtc(destPath)) { + Log.LogMessage(MessageImportance.Low, "Skipping \"{0}\" -> \"{1}\" because the destination is up to date.", sourcePath, destPath); + continue; + } + + Log.LogMessage(MessageImportance.Normal, "Transforming \"{0}\" -> \"{1}\"", sourcePath, destPath); + + string[] beforeTokens = this.SourceFiles[i].GetMetadata("BeforeTokens").Split(';'); + string[] afterTokens = this.SourceFiles[i].GetMetadata("AfterTokens").Split(';'); + if (beforeTokens.Length != afterTokens.Length) { + Log.LogError("Unequal number of before and after tokens. Before: \"{0}\". After \"{1}\".", beforeTokens, afterTokens); + return false; + } + + using (StreamReader sr = File.OpenText(sourcePath)) { + if (!Directory.Exists(Path.GetDirectoryName(destPath))) { + Directory.CreateDirectory(Path.GetDirectoryName(destPath)); + } + using (StreamWriter sw = File.CreateText(destPath)) { + StringBuilder line = new StringBuilder(); + while (!sr.EndOfStream) { + line.Length = 0; + line.Append(sr.ReadLine()); + for (int j = 0; j < beforeTokens.Length; j++) { + line.Replace(beforeTokens[j], afterTokens[j]); + } + + sw.WriteLine(line); + } + } + } + + copiedFiles.Add(this.DestinationFiles[i]); + } + + this.CopiedFiles = copiedFiles.ToArray(); + return true; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/CreateWebApplication.cs b/src/DotNetOpenAuth.BuildTasks/CreateWebApplication.cs new file mode 100644 index 0000000..4980898 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/CreateWebApplication.cs @@ -0,0 +1,95 @@ +//----------------------------------------------------------------------- +// <copyright file="CreateWebApplication.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using System; + using System.Linq; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + using Microsoft.Web.Administration; + + /// <summary> + /// Creates or updates web applications within an existing web site in IIS. + /// </summary> + public class CreateWebApplication : Task { + /// <summary> + /// Gets or sets the name of the application pool that should host the web application. + /// </summary> + /// <value>The name of an existing application pool.</value> + public string ApplicationPoolName { get; set; } + + /// <summary> + /// Gets or sets the name of the web site under which to create the web application. + /// </summary> + /// <value>The name of the existing web site.</value> + [Required] + public string WebSiteName { get; set; } + + /// <summary> + /// Gets or sets the virtual paths within the web site that will access these applications. + /// </summary> + /// <value>The virtual path, which must start with '/'.</value> + [Required] + public ITaskItem[] VirtualPaths { get; set; } + + /// <summary> + /// Gets or sets the full file system paths to the web applications. + /// </summary> + /// <value>The physical path.</value> + [Required] + public ITaskItem[] PhysicalPaths { get; set; } + + /// <summary> + /// Executes this instance. + /// </summary> + /// <returns>A value indicating whether the task completed successfully.</returns> + public override bool Execute() { + var serverManager = new ServerManager(); + + if (this.PhysicalPaths.Length != this.VirtualPaths.Length) { + Log.LogError(TaskStrings.MismatchingArrayLengths, "PhysicalPath", "VirtualPath"); + return false; + } + + if (this.VirtualPaths.Length == 0) { + // Nothing to do. + return true; + } + + // Find the root web site that this web application will be created under. + var site = serverManager.Sites.FirstOrDefault(s => string.Equals(s.Name, this.WebSiteName, StringComparison.OrdinalIgnoreCase)); + if (site == null) { + Log.LogError(TaskStrings.NoMatchingWebSiteFound, this.WebSiteName); + return false; + } + + Log.LogMessage(MessageImportance.Normal, "Creating web applications under web site: {0}", site.Name); + + for (int i = 0; i < this.PhysicalPaths.Length; i++) { + string physicalPath = this.PhysicalPaths[i].ItemSpec; + string virtualPath = this.VirtualPaths[i].ItemSpec; + + Log.LogMessage(MessageImportance.Normal, "\t{0} -> {1}", virtualPath, physicalPath); + + var app = site.Applications.FirstOrDefault(a => string.Equals(a.Path, virtualPath, StringComparison.OrdinalIgnoreCase)); + if (app == null) { + app = site.Applications.Add(virtualPath, physicalPath); + } else { + // Ensure physical path is set correctly. + var appRoot = app.VirtualDirectories.First(vd => vd.Path == "/"); + appRoot.PhysicalPath = physicalPath; + } + + if (!string.IsNullOrEmpty(this.ApplicationPoolName)) { + app.ApplicationPoolName = this.ApplicationPoolName; + } + } + + serverManager.CommitChanges(); + return true; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/DeleteWebApplication.cs b/src/DotNetOpenAuth.BuildTasks/DeleteWebApplication.cs new file mode 100644 index 0000000..930a8c4 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/DeleteWebApplication.cs @@ -0,0 +1,66 @@ +//----------------------------------------------------------------------- +// <copyright file="DeleteWebApplication.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using System; + using System.Linq; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + using Microsoft.Web.Administration; + + /// <summary> + /// Deletes a web application from IIS. + /// </summary> + public class DeleteWebApplication : Task { + /// <summary> + /// Gets or sets the name of the web site under which to create the web application. + /// </summary> + /// <value>The name of the existing web site.</value> + [Required] + public string WebSiteName { get; set; } + + /// <summary> + /// Gets or sets the virtual paths within the web site that will access these applications. + /// </summary> + /// <value>The virtual path, which must start with '/'.</value> + [Required] + public ITaskItem[] VirtualPaths { get; set; } + + /// <summary> + /// Executes this instance. + /// </summary> + /// <returns>A value indicating whether the task completed successfully.</returns> + public override bool Execute() { + var serverManager = new ServerManager(); + + // Find the root web site that this web application will be created under. + var site = serverManager.Sites.FirstOrDefault(s => string.Equals(s.Name, this.WebSiteName, StringComparison.OrdinalIgnoreCase)); + if (site == null) { + Log.LogMessage(MessageImportance.Low, TaskStrings.NoMatchingWebSiteFound, this.WebSiteName); + return true; + } + + if (this.VirtualPaths.Length == 0) { + // Nothing to do. + return true; + } + + foreach (ITaskItem path in this.VirtualPaths) { + var app = site.Applications.FirstOrDefault(a => string.Equals(a.Path, path.ItemSpec, StringComparison.OrdinalIgnoreCase)); + if (app != null) { + site.Applications.Remove(app); + Log.LogMessage(MessageImportance.Normal, TaskStrings.DeletedWebApplication, app.Path); + } else { + Log.LogMessage(MessageImportance.Low, TaskStrings.WebApplicationNotFoundSoNotDeleted, path.ItemSpec); + } + } + + serverManager.CommitChanges(); + + return true; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/DiscoverProjectTemplates.cs b/src/DotNetOpenAuth.BuildTasks/DiscoverProjectTemplates.cs new file mode 100644 index 0000000..0162c16 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/DiscoverProjectTemplates.cs @@ -0,0 +1,63 @@ +//----------------------------------------------------------------------- +// <copyright file="DiscoverProjectTemplates.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text; + using System.Xml.Linq; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + public class DiscoverProjectTemplates : Task { + public ITaskItem[] TopLevelTemplates { get; set; } + + [Output] + public ITaskItem[] ProjectTemplates { get; set; } + + [Output] + public ITaskItem[] ProjectTemplateContents { get; set; } + + /// <summary> + /// Executes this instance. + /// </summary> + public override bool Execute() { + List<ITaskItem> projectTemplates = new List<ITaskItem>(); + List<ITaskItem> projectTemplateContents = new List<ITaskItem>(); + foreach (ITaskItem topLevelTemplate in this.TopLevelTemplates) { + var vsTemplate = XElement.Load(topLevelTemplate.ItemSpec); + var templateContent = vsTemplate.Element(XName.Get("TemplateContent", MergeProjectWithVSTemplate.VSTemplateNamespace)); + var projectCollection = templateContent.Element(XName.Get("ProjectCollection", MergeProjectWithVSTemplate.VSTemplateNamespace)); + var links = projectCollection.Elements(XName.Get("ProjectTemplateLink", MergeProjectWithVSTemplate.VSTemplateNamespace)); + var subTemplates = links.Select( + link => (ITaskItem)new TaskItem( + link.Value, + new Dictionary<string, string> { + { "TopLevelTemplate", topLevelTemplate.ItemSpec }, + { "TopLevelTemplateFileName", Path.GetFileNameWithoutExtension(topLevelTemplate.ItemSpec) }, + })); + projectTemplates.AddRange(subTemplates); + + foreach (var link in links.Select(link => link.Value)) { + string[] files = Directory.GetFiles(Path.Combine(Path.GetDirectoryName(topLevelTemplate.ItemSpec), Path.GetDirectoryName(link)), "*.*", SearchOption.AllDirectories); + projectTemplateContents.AddRange(files.Select(file => (ITaskItem)new TaskItem( + file, + new Dictionary<string, string> { + { "TopLevelTemplate", topLevelTemplate.ItemSpec }, + { "TopLevelTemplateFileName", Path.GetFileNameWithoutExtension(topLevelTemplate.ItemSpec) }, + }))); + } + } + + this.ProjectTemplates = projectTemplates.ToArray(); + this.ProjectTemplateContents = projectTemplateContents.ToArray(); + + return !this.Log.HasLoggedErrors; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.csproj b/src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.csproj new file mode 100644 index 0000000..0db99e9 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.csproj @@ -0,0 +1,126 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProductVersion>9.0.30729</ProductVersion> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{AC231A51-EF60-437C-A33F-AF8ADEB8EB74}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>DotNetOpenAuth.BuildTasks</RootNamespace> + <AssemblyName>DotNetOpenAuth.BuildTasks</AssemblyName> + <TargetFrameworkVersion>v3.5</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>..\..\lib\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <CodeContractsEnableRuntimeChecking>True</CodeContractsEnableRuntimeChecking> + <CodeContractsRuntimeOnlyPublicSurface>False</CodeContractsRuntimeOnlyPublicSurface> + <CodeContractsRuntimeThrowOnFailure>False</CodeContractsRuntimeThrowOnFailure> + <CodeContractsRuntimeCallSiteRequires>False</CodeContractsRuntimeCallSiteRequires> + <CodeContractsRunCodeAnalysis>False</CodeContractsRunCodeAnalysis> + <CodeContractsNonNullObligations>False</CodeContractsNonNullObligations> + <CodeContractsBoundsObligations>False</CodeContractsBoundsObligations> + <CodeContractsArithmeticObligations>False</CodeContractsArithmeticObligations> + <CodeContractsRedundantAssumptions>False</CodeContractsRedundantAssumptions> + <CodeContractsRunInBackground>True</CodeContractsRunInBackground> + <CodeContractsShowSquigglies>False</CodeContractsShowSquigglies> + <CodeContractsUseBaseLine>False</CodeContractsUseBaseLine> + <CodeContractsEmitXMLDocs>False</CodeContractsEmitXMLDocs> + <CodeContractsCustomRewriterAssembly> + </CodeContractsCustomRewriterAssembly> + <CodeContractsCustomRewriterClass> + </CodeContractsCustomRewriterClass> + <CodeContractsLibPaths> + </CodeContractsLibPaths> + <CodeContractsPlatformPath> + </CodeContractsPlatformPath> + <CodeContractsExtraAnalysisOptions> + </CodeContractsExtraAnalysisOptions> + <CodeContractsBaseLineFile> + </CodeContractsBaseLineFile> + <CodeContractsRuntimeCheckingLevel>Full</CodeContractsRuntimeCheckingLevel> + <CodeContractsReferenceAssembly>%28none%29</CodeContractsReferenceAssembly> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>..\..\lib\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="Microsoft.Build.Engine" /> + <Reference Include="Microsoft.Build.Framework" /> + <Reference Include="Microsoft.Build.Utilities.v3.5"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> + <Reference Include="Microsoft.Contracts, Version=1.0.0.0, Culture=neutral, PublicKeyToken=736440c9b414ea16, processorArchitecture=MSIL" /> + <Reference Include="Microsoft.Web.Administration, Version=7.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>$(SystemRoot)\System32\inetsrv\Microsoft.Web.Administration.dll</HintPath> + </Reference> + <Reference Include="System" /> + <Reference Include="System.Core"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> + <Reference Include="System.Data" /> + <Reference Include="System.Xml" /> + <Reference Include="System.Xml.Linq"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> + </ItemGroup> + <ItemGroup> + <Compile Include="AddProjectItems.cs" /> + <Compile Include="ChangeProjectReferenceToAssemblyReference.cs" /> + <Compile Include="CompareFiles.cs" /> + <Compile Include="ChangeAssemblyReference.cs" /> + <Compile Include="CopyWithTokenSubstitution.cs" /> + <Compile Include="CreateWebApplication.cs" /> + <Compile Include="DeleteWebApplication.cs" /> + <Compile Include="DiscoverProjectTemplates.cs" /> + <Compile Include="ECMAScriptPacker.cs" /> + <Compile Include="FilterItems.cs" /> + <Compile Include="FixupReferenceHintPaths.cs" /> + <Compile Include="FixupShippingToolSamples.cs" /> + <Compile Include="MergeProjectWithVSTemplate.cs" /> + <Compile Include="GetBuildVersion.cs" /> + <Compile Include="CheckAdminRights.cs" /> + <Compile Include="JsPack.cs" /> + <Compile Include="ParseMaster.cs" /> + <Compile Include="Purge.cs" /> + <Compile Include="ReSignDelaySignedAssemblies.cs" /> + <Compile Include="SetEnvironmentVariable.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="SignatureVerification.cs" /> + <Compile Include="SnToolTask.cs" /> + <Compile Include="TaskStrings.Designer.cs"> + <AutoGen>True</AutoGen> + <DesignTime>True</DesignTime> + <DependentUpon>TaskStrings.resx</DependentUpon> + </Compile> + <Compile Include="Trim.cs" /> + </ItemGroup> + <ItemGroup> + <EmbeddedResource Include="TaskStrings.resx"> + <Generator>ResXFileCodeGenerator</Generator> + <LastGenOutput>TaskStrings.Designer.cs</LastGenOutput> + </EmbeddedResource> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.sln b/src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.sln new file mode 100644 index 0000000..c875882 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.sln @@ -0,0 +1,26 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.BuildTasks", "DotNetOpenAuth.BuildTasks.csproj", "{AC231A51-EF60-437C-A33F-AF8ADEB8EB74}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{ABBE14A3-0404-4123-9093-E598C3DD3E9B}" + ProjectSection(SolutionItems) = preProject + ..\..\build.proj = ..\..\build.proj + ..\..\lib\DotNetOpenAuth.BuildTasks.targets = ..\..\lib\DotNetOpenAuth.BuildTasks.targets + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AC231A51-EF60-437C-A33F-AF8ADEB8EB74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AC231A51-EF60-437C-A33F-AF8ADEB8EB74}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AC231A51-EF60-437C-A33F-AF8ADEB8EB74}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AC231A51-EF60-437C-A33F-AF8ADEB8EB74}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/DotNetOpenAuth.BuildTasks/ECMAScriptPacker.cs b/src/DotNetOpenAuth.BuildTasks/ECMAScriptPacker.cs new file mode 100644 index 0000000..d63d5b4 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/ECMAScriptPacker.cs @@ -0,0 +1,486 @@ +using System; +using System.Text; +using System.Text.RegularExpressions; +using System.Collections; +using System.Collections.Specialized; +using System.Web; +using System.IO; + +/* + packer, version 2.0 (beta) (2005/02/01) + Copyright 2004-2005, Dean Edwards + Web: http://dean.edwards.name/ + + This software is licensed under the CC-GNU LGPL + Web: http://creativecommons.org/licenses/LGPL/2.1/ + + Ported to C# by Jesse Hansen, twindagger2k@msn.com + modified slightly by Andrew Arnott +*/ + +// http://dean.edwards.name/packer/ + +namespace Dean.Edwards +{ + /// <summary> + /// Packs a javascript file into a smaller area, removing unnecessary characters from the output. + /// </summary> + internal class ECMAScriptPacker + { + /// <summary> + /// The encoding level to use. See http://dean.edwards.name/packer/usage/ for more info. + /// </summary> + public enum PackerEncoding { None = 0, Numeric = 10, Mid = 36, Normal = 62, HighAscii = 95 }; + + private PackerEncoding encoding = PackerEncoding.Normal; + private bool fastDecode = true; + private bool specialChars = false; + private bool enabled = true; + + string IGNORE = "$1"; + + /// <summary> + /// The encoding level for this instance + /// </summary> + public PackerEncoding Encoding + { + get { return encoding; } + set { encoding = value; } + } + + /// <summary> + /// Adds a subroutine to the output to speed up decoding + /// </summary> + public bool FastDecode + { + get { return fastDecode; } + set { fastDecode = value; } + } + + /// <summary> + /// Replaces special characters + /// </summary> + public bool SpecialChars + { + get { return specialChars; } + set { specialChars = value; } + } + + /// <summary> + /// Packer enabled + /// </summary> + public bool Enabled + { + get { return enabled; } + set { enabled = value; } + } + + public ECMAScriptPacker() + { + Encoding = PackerEncoding.Normal; + FastDecode = true; + SpecialChars = false; + } + + /// <summary> + /// Constructor + /// </summary> + /// <param name="encoding">The encoding level for this instance</param> + /// <param name="fastDecode">Adds a subroutine to the output to speed up decoding</param> + /// <param name="specialChars">Replaces special characters</param> + public ECMAScriptPacker(PackerEncoding encoding, bool fastDecode, bool specialChars) + { + Encoding = encoding; + FastDecode = fastDecode; + SpecialChars = specialChars; + } + + /// <summary> + /// Packs the script + /// </summary> + /// <param name="script">the script to pack</param> + /// <returns>the packed script</returns> + public string Pack(string script) + { + if (enabled) + { + script += "\n"; + script = basicCompression(script); + if (SpecialChars) + script = encodeSpecialChars(script); + if (Encoding != PackerEncoding.None) + script = encodeKeywords(script); + } + return script; + } + + //zero encoding - just removal of whitespace and comments + private string basicCompression(string script) + { + ParseMaster parser = new ParseMaster(); + // make safe + parser.EscapeChar = '\\'; + // protect strings + parser.Add("'[^'\\n\\r]*'", IGNORE); + parser.Add("\"[^\"\\n\\r]*\"", IGNORE); + // remove comments + parser.Add("\\/\\/[^\\n\\r]*[\\n\\r]"); + parser.Add("\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\/"); + // protect regular expressions + parser.Add("\\s+(\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?)", "$2"); + parser.Add("[^\\w\\$\\/'\"*)\\?:]\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?", IGNORE); + // remove: ;;; doSomething(); + if (specialChars) + parser.Add(";;[^\\n\\r]+[\\n\\r]"); + // remove redundant semi-colons + parser.Add(";+\\s*([};])", "$2"); + // remove white-space + parser.Add("(\\b|\\$)\\s+(\\b|\\$)", "$2 $3"); + parser.Add("([+\\-])\\s+([+\\-])", "$2 $3"); + parser.Add("\\s+"); + // done + return parser.Exec(script); + } + + WordList encodingLookup; + private string encodeSpecialChars(string script) + { + ParseMaster parser = new ParseMaster(); + // replace: $name -> n, $$name -> na + parser.Add("((\\$+)([a-zA-Z\\$_]+))(\\d*)", + new ParseMaster.MatchGroupEvaluator(encodeLocalVars)); + + // replace: _name -> _0, double-underscore (__name) is ignored + Regex regex = new Regex("\\b_[A-Za-z\\d]\\w*"); + + // build the word list + encodingLookup = analyze(script, regex, new EncodeMethod(encodePrivate)); + + parser.Add("\\b_[A-Za-z\\d]\\w*", new ParseMaster.MatchGroupEvaluator(encodeWithLookup)); + + script = parser.Exec(script); + return script; + } + + private string encodeKeywords(string script) + { + // escape high-ascii values already in the script (i.e. in strings) + if (Encoding == PackerEncoding.HighAscii) script = escape95(script); + // create the parser + ParseMaster parser = new ParseMaster(); + EncodeMethod encode = getEncoder(Encoding); + + // for high-ascii, don't encode single character low-ascii + Regex regex = new Regex( + (Encoding == PackerEncoding.HighAscii) ? "\\w\\w+" : "\\w+" + ); + // build the word list + encodingLookup = analyze(script, regex, encode); + + // encode + parser.Add((Encoding == PackerEncoding.HighAscii) ? "\\w\\w+" : "\\w+", + new ParseMaster.MatchGroupEvaluator(encodeWithLookup)); + + // if encoded, wrap the script in a decoding function + return (script == string.Empty) ? "" : bootStrap(parser.Exec(script), encodingLookup); + } + + private string bootStrap(string packed, WordList keywords) + { + // packed: the packed script + packed = "'" + escape(packed) + "'"; + + // ascii: base for encoding + int ascii = Math.Min(keywords.Sorted.Count, (int) Encoding); + if (ascii == 0) + ascii = 1; + + // count: number of words contained in the script + int count = keywords.Sorted.Count; + + // keywords: list of words contained in the script + foreach (object key in keywords.Protected.Keys) + { + keywords.Sorted[(int) key] = ""; + } + // convert from a string to an array + StringBuilder sbKeywords = new StringBuilder("'"); + foreach (string word in keywords.Sorted) + sbKeywords.Append(word + "|"); + sbKeywords.Remove(sbKeywords.Length-1, 1); + string keywordsout = sbKeywords.ToString() + "'.split('|')"; + + string encode; + string inline = "c"; + + switch (Encoding) + { + case PackerEncoding.Mid: + encode = "function(c){return c.toString(36)}"; + inline += ".toString(a)"; + break; + case PackerEncoding.Normal: + encode = "function(c){return(c<a?\"\":e(parseInt(c/a)))+" + + "((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))}"; + inline += ".toString(a)"; + break; + case PackerEncoding.HighAscii: + encode = "function(c){return(c<a?\"\":e(c/a))+" + + "String.fromCharCode(c%a+161)}"; + inline += ".toString(a)"; + break; + default: + encode = "function(c){return c}"; + break; + } + + // decode: code snippet to speed up decoding + string decode = ""; + if (fastDecode) + { + decode = "if(!''.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[function(e){return d[e]}];e=function(){return'\\\\w+'};c=1;}"; + if (Encoding == PackerEncoding.HighAscii) + decode = decode.Replace("\\\\w", "[\\xa1-\\xff]"); + else if (Encoding == PackerEncoding.Numeric) + decode = decode.Replace("e(c)", inline); + if (count == 0) + decode = decode.Replace("c=1", "c=0"); + } + + // boot function + string unpack = "function(p,a,c,k,e,d){while(c--)if(k[c])p=p.replace(new RegExp('\\\\b'+e(c)+'\\\\b','g'),k[c]);return p;}"; + Regex r; + if (fastDecode) + { + //insert the decoder + r = new Regex("\\{"); + unpack = r.Replace(unpack, "{" + decode + ";", 1); + } + + if (Encoding == PackerEncoding.HighAscii) + { + // get rid of the word-boundries for regexp matches + r = new Regex("'\\\\\\\\b'\\s*\\+|\\+\\s*'\\\\\\\\b'"); + unpack = r.Replace(unpack, ""); + } + if (Encoding == PackerEncoding.HighAscii || ascii > (int) PackerEncoding.Normal || fastDecode) + { + // insert the encode function + r = new Regex("\\{"); + unpack = r.Replace(unpack, "{e=" + encode + ";", 1); + } + else + { + r = new Regex("e\\(c\\)"); + unpack = r.Replace(unpack, inline); + } + // no need to pack the boot function since i've already done it + string _params = "" + packed + "," + ascii + "," + count + "," + keywordsout; + if (fastDecode) + { + //insert placeholders for the decoder + _params += ",0,{}"; + } + // the whole thing + return "eval(" + unpack + "(" + _params + "))\n"; + } + + private string escape(string input) + { + Regex r = new Regex("([\\\\'])"); + return r.Replace(input, "\\$1"); + } + + private EncodeMethod getEncoder(PackerEncoding encoding) + { + switch (encoding) + { + case PackerEncoding.Mid: + return new EncodeMethod(encode36); + case PackerEncoding.Normal: + return new EncodeMethod(encode62); + case PackerEncoding.HighAscii: + return new EncodeMethod(encode95); + default: + return new EncodeMethod(encode10); + } + } + + private string encode10(int code) + { + return code.ToString(); + } + + //lookups seemed like the easiest way to do this since + // I don't know of an equivalent to .toString(36) + private static string lookup36 = "0123456789abcdefghijklmnopqrstuvwxyz"; + + private string encode36(int code) + { + string encoded = ""; + int i = 0; + do + { + int digit = (code / (int) Math.Pow(36, i)) % 36; + encoded = lookup36[digit] + encoded; + code -= digit * (int) Math.Pow(36, i++); + } while (code > 0); + return encoded; + } + + private static string lookup62 = lookup36 + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + private string encode62(int code) + { + string encoded = ""; + int i = 0; + do + { + int digit = (code / (int) Math.Pow(62, i)) % 62; + encoded = lookup62[digit] + encoded; + code -= digit * (int) Math.Pow(62, i++); + } while (code > 0); + return encoded; + } + + private static string lookup95 = ""; + + private string encode95(int code) + { + string encoded = ""; + int i = 0; + do + { + int digit = (code / (int) Math.Pow(95, i)) % 95; + encoded = lookup95[digit] + encoded; + code -= digit * (int) Math.Pow(95, i++); + } while (code > 0); + return encoded; + } + + private string escape95(string input) + { + Regex r = new Regex("[\xa1-\xff]"); + return r.Replace(input, new MatchEvaluator(escape95Eval)); + } + + private string escape95Eval(Match match) + { + return "\\x" + ((int) match.Value[0]).ToString("x"); //return hexadecimal value + } + + private string encodeLocalVars(Match match, int offset) + { + int length = match.Groups[offset + 2].Length; + int start = length - Math.Max(length - match.Groups[offset + 3].Length, 0); + return match.Groups[offset + 1].Value.Substring(start, length) + + match.Groups[offset + 4].Value; + } + + private string encodeWithLookup(Match match, int offset) + { + return (string) encodingLookup.Encoded[match.Groups[offset].Value]; + } + + private delegate string EncodeMethod(int code); + + private string encodePrivate(int code) + { + return "_" + code; + } + + private WordList analyze(string input, Regex regex, EncodeMethod encodeMethod) + { + // analyse + // retreive all words in the script + MatchCollection all = regex.Matches(input); + WordList rtrn; + rtrn.Sorted = new StringCollection(); // list of words sorted by frequency + rtrn.Protected = new HybridDictionary(); // dictionary of word->encoding + rtrn.Encoded = new HybridDictionary(); // instances of "protected" words + if (all.Count > 0) + { + StringCollection unsorted = new StringCollection(); // same list, not sorted + HybridDictionary Protected = new HybridDictionary(); // "protected" words (dictionary of word->"word") + HybridDictionary values = new HybridDictionary(); // dictionary of charCode->encoding (eg. 256->ff) + HybridDictionary count = new HybridDictionary(); // word->count + int i = all.Count, j = 0; + string word; + // count the occurrences - used for sorting later + do + { + word = "$" + all[--i].Value; + if (count[word] == null) + { + count[word] = 0; + unsorted.Add(word); + // make a dictionary of all of the protected words in this script + // these are words that might be mistaken for encoding + Protected["$" + (values[j] = encodeMethod(j))] = j++; + } + // increment the word counter + count[word] = (int) count[word] + 1; + } while (i > 0); + /* prepare to sort the word list, first we must protect + words that are also used as codes. we assign them a code + equivalent to the word itself. + e.g. if "do" falls within our encoding range + then we store keywords["do"] = "do"; + this avoids problems when decoding */ + i = unsorted.Count; + string[] sortedarr = new string[unsorted.Count]; + do + { + word = unsorted[--i]; + if (Protected[word] != null) + { + sortedarr[(int) Protected[word]] = word.Substring(1); + rtrn.Protected[(int) Protected[word]] = true; + count[word] = 0; + } + } while (i > 0); + string[] unsortedarr = new string[unsorted.Count]; + unsorted.CopyTo(unsortedarr, 0); + // sort the words by frequency + Array.Sort(unsortedarr, (IComparer) new CountComparer(count)); + j = 0; + /*because there are "protected" words in the list + we must add the sorted words around them */ + do + { + if (sortedarr[i] == null) + sortedarr[i] = unsortedarr[j++].Substring(1); + rtrn.Encoded[sortedarr[i]] = values[i]; + } while (++i < unsortedarr.Length); + rtrn.Sorted.AddRange(sortedarr); + } + return rtrn; + } + + private struct WordList + { + public StringCollection Sorted; + public HybridDictionary Encoded; + public HybridDictionary Protected; + } + + private class CountComparer : IComparer + { + HybridDictionary count; + + public CountComparer(HybridDictionary count) + { + this.count = count; + } + + #region IComparer Members + + public int Compare(object x, object y) + { + return (int) count[y] - (int) count[x]; + } + + #endregion + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/FilterItems.cs b/src/DotNetOpenAuth.BuildTasks/FilterItems.cs new file mode 100644 index 0000000..97631c6 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/FilterItems.cs @@ -0,0 +1,24 @@ +namespace DotNetOpenAuth.BuildTasks { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using Microsoft.Build.Utilities; + using Microsoft.Build.Framework; + + public class FilterItems : Task { + [Required] + public ITaskItem[] InputItems { get; set; } + + [Required] + public ITaskItem[] StartsWithAny { get; set; } + + [Output] + public ITaskItem[] FilteredItems { get; set; } + + public override bool Execute() { + FilteredItems = InputItems.Where(item => StartsWithAny.Any(filter => item.ItemSpec.StartsWith(filter.ItemSpec, StringComparison.OrdinalIgnoreCase))).ToArray(); + return true; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/FixupReferenceHintPaths.cs b/src/DotNetOpenAuth.BuildTasks/FixupReferenceHintPaths.cs new file mode 100644 index 0000000..13a4b8f --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/FixupReferenceHintPaths.cs @@ -0,0 +1,71 @@ +//----------------------------------------------------------------------- +// <copyright file="FixupReferenceHintPaths.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Text; + using Microsoft.Build.BuildEngine; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + public class FixupReferenceHintPaths : Task { + /// <summary> + /// Gets or sets the projects to fixup references for. + /// </summary> + [Required] + public ITaskItem[] Projects { get; set; } + + /// <summary> + /// Gets or sets the set of full paths to assemblies that may be found in any of the <see cref="Projects"/>. + /// </summary> + [Required] + public ITaskItem[] References { get; set; } + + /// <summary> + /// Executes this instance. + /// </summary> + public override bool Execute() { + if (this.References.Length == 0 || this.Projects.Length == 0) { + this.Log.LogMessage(MessageImportance.Low, "Skipping reference hintpath fixup because no projects or no references were supplied."); + return !this.Log.HasLoggedErrors; + } + + // Figure out what the assembly names are of the references that are available. + AssemblyName[] availableReferences = new AssemblyName[this.References.Length]; + for (int i = 0; i < this.References.Length; i++) { + availableReferences[i] = AssemblyName.GetAssemblyName(this.References[i].ItemSpec); + } + + foreach (var projectTaskItem in this.Projects) { + var project = new Project(); + Uri projectUri = new Uri(projectTaskItem.GetMetadata("FullPath")); + project.Load(projectTaskItem.ItemSpec); + + foreach (BuildItem referenceItem in project.GetEvaluatedItemsByName("Reference")) { + var referenceAssemblyName = new AssemblyName(referenceItem.Include); + var matchingReference = availableReferences.FirstOrDefault(r => string.Equals(r.Name, referenceAssemblyName.Name, StringComparison.OrdinalIgnoreCase)); + if (matchingReference != null) { + var originalSuppliedReferenceItem = this.References[Array.IndexOf(availableReferences, matchingReference)]; + string hintPath = originalSuppliedReferenceItem.GetMetadata("HintPath"); + if (string.IsNullOrEmpty(hintPath)) { + hintPath = projectUri.MakeRelativeUri(new Uri(matchingReference.CodeBase)).OriginalString.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + } + this.Log.LogMessage("Fixing up HintPath to \"{0}\" in project \"{1}\".", referenceAssemblyName.Name, projectTaskItem.ItemSpec); + referenceItem.SetMetadata("HintPath", hintPath); + } + } + + project.Save(projectTaskItem.ItemSpec); + } + + return !this.Log.HasLoggedErrors; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/FixupShippingToolSamples.cs b/src/DotNetOpenAuth.BuildTasks/FixupShippingToolSamples.cs new file mode 100644 index 0000000..92b0235 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/FixupShippingToolSamples.cs @@ -0,0 +1,48 @@ +//----------------------------------------------------------------------- +// <copyright file="FixupShippingToolSamples.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using Microsoft.Build.Utilities; + using Microsoft.Build.Framework; + using System.IO; + using Microsoft.Build.BuildEngine; + + /// <summary> + /// Removes imports that only apply when a shipping tool sample builds as part of + /// the entire project, but not when it's part of a source code sample. + /// </summary> + public class FixupShippingToolSamples : Task { + [Required] + public ITaskItem[] Projects { get; set; } + + /// <summary> + /// Executes this instance. + /// </summary> + /// <returns></returns> + public override bool Execute() { + foreach (ITaskItem projectTaskItem in this.Projects) { + this.Log.LogMessage("Fixing up the {0} sample for shipping as source code.", Path.GetFileNameWithoutExtension(projectTaskItem.ItemSpec)); + + var project = new Project(); + Uri projectUri = new Uri(projectTaskItem.GetMetadata("FullPath")); + project.Load(projectTaskItem.ItemSpec, ProjectLoadSettings.IgnoreMissingImports); + + project.Imports.Cast<Import>() + .Where(import => import.ProjectPath.StartsWith(@"..\..\tools\", StringComparison.OrdinalIgnoreCase)) + .ToList() + .ForEach(import => project.Imports.RemoveImport(import)); + + project.Save(projectTaskItem.ItemSpec); + } + + return !this.Log.HasLoggedErrors; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/GetBuildVersion.cs b/src/DotNetOpenAuth.BuildTasks/GetBuildVersion.cs new file mode 100644 index 0000000..e40eb78 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/GetBuildVersion.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.Build.Utilities; +using Microsoft.Build.Framework; +using System.IO; + +namespace DotNetOpenAuth.BuildTasks { + public class GetBuildVersion : Task { + + /// <summary> + /// Gets the version string to use in the compiled assemblies. + /// </summary> + [Output] + public string Version { get; private set; } + + /// <summary> + /// The file that contains the version base (Major.Minor.Build) to use. + /// </summary> + [Required] + public string VersionFile { get; set; } + + public override bool Execute() { + try { + Version typedVersion = ReadVersionFromFile(); + typedVersion = new Version(typedVersion.Major, typedVersion.Minor, typedVersion.Build, CalculateJDate(DateTime.Now)); + Version = typedVersion.ToString(); + } catch (ArgumentOutOfRangeException ex) { + Log.LogErrorFromException(ex); + return false; + } + + return true; + } + + private Version ReadVersionFromFile() { + string[] lines = File.ReadAllLines(VersionFile); + string versionLine = lines[0]; + return new Version(versionLine); + } + + private int CalculateJDate(DateTime date) { + int yearLastDigit = date.Year % 10; + DateTime firstOfYear = new DateTime(date.Year, 1, 1); + int dayOfYear = (date - firstOfYear).Days + 1; + int jdate = yearLastDigit * 1000 + dayOfYear; + return jdate; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/JsPack.cs b/src/DotNetOpenAuth.BuildTasks/JsPack.cs new file mode 100644 index 0000000..fa8c7f0 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/JsPack.cs @@ -0,0 +1,75 @@ +//----------------------------------------------------------------------- +// <copyright file="JsPack.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + /// <summary> + /// Compresses .js files. + /// </summary> + public class JsPack : Task { + /// <summary> + /// The Javascript packer to use. + /// </summary> + private Dean.Edwards.ECMAScriptPacker packer = new Dean.Edwards.ECMAScriptPacker(); + + /// <summary> + /// Gets or sets the set of javascript files to compress. + /// </summary> + /// <value>The inputs.</value> + [Required] + public ITaskItem[] Inputs { get; set; } + + /// <summary> + /// Gets or sets the paths where the packed javascript files should be saved. + /// </summary> + /// <value>The outputs.</value> + [Required] + public ITaskItem[] Outputs { get; set; } + + /// <summary> + /// Executes this instance. + /// </summary> + /// <returns>A value indicating whether the packing was successful.</returns> + public override bool Execute() { + if (this.Inputs.Length != this.Outputs.Length) { + Log.LogError("{0} inputs and {1} outputs given.", this.Inputs.Length, this.Outputs.Length); + return false; + } + + for (int i = 0; i < this.Inputs.Length; i++) { + if (!File.Exists(this.Outputs[i].ItemSpec) || File.GetLastWriteTime(this.Outputs[i].ItemSpec) < File.GetLastWriteTime(this.Inputs[i].ItemSpec)) { + Log.LogMessage(MessageImportance.Normal, TaskStrings.PackingJsFile, this.Inputs[i].ItemSpec, this.Outputs[i].ItemSpec); + string input = File.ReadAllText(this.Inputs[i].ItemSpec); + string output = this.packer.Pack(input); + if (!Directory.Exists(Path.GetDirectoryName(this.Outputs[i].ItemSpec))) { + Directory.CreateDirectory(Path.GetDirectoryName(this.Outputs[i].ItemSpec)); + } + + // Minification removes all comments, including copyright notices + // that must remain. So if there's metadata on this item with + // a copyright notice on it, stick it on the front of the file. + string copyright = this.Inputs[i].GetMetadata("Copyright"); + if (!string.IsNullOrEmpty(copyright)) { + output = "/*" + copyright + "*/" + output; + } + + File.WriteAllText(this.Outputs[i].ItemSpec, output, Encoding.UTF8); + } else { + Log.LogMessage(MessageImportance.Low, TaskStrings.SkipPackingJsFile, this.Inputs[i].ItemSpec); + } + } + + return true; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/MergeProjectWithVSTemplate.cs b/src/DotNetOpenAuth.BuildTasks/MergeProjectWithVSTemplate.cs new file mode 100644 index 0000000..1a8a17d --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/MergeProjectWithVSTemplate.cs @@ -0,0 +1,103 @@ +//----------------------------------------------------------------------- +// <copyright file="MergeProjectWithVSTemplate.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + using System.Xml.Linq; + using System.IO; + using Microsoft.Build.BuildEngine; + using System.Diagnostics.Contracts; + + public class MergeProjectWithVSTemplate : Task { + internal const string VSTemplateNamespace = "http://schemas.microsoft.com/developer/vstemplate/2005"; + + [Required] + public string[] ProjectItemTypes { get; set; } + + [Required] + public string[] ReplaceParametersExtensions { get; set; } + + [Required] + public ITaskItem[] Templates { get; set; } + + /// <summary> + /// Executes this instance. + /// </summary> + public override bool Execute() { + foreach(ITaskItem sourceTemplateTaskItem in this.Templates) { + var template = XElement.Load(sourceTemplateTaskItem.ItemSpec); + var templateContentElement = template.Element(XName.Get("TemplateContent", VSTemplateNamespace)); + var projectElement = templateContentElement.Element(XName.Get("Project", VSTemplateNamespace)); + if (projectElement == null) { + Log.LogMessage("Skipping merge of \"{0}\" with a project because no project was referenced from the template.", sourceTemplateTaskItem.ItemSpec); + continue; + } + + var projectPath = Path.Combine(Path.GetDirectoryName(sourceTemplateTaskItem.ItemSpec), projectElement.Attribute("File").Value); + Log.LogMessage("Merging project \"{0}\" with \"{1}\".", projectPath, sourceTemplateTaskItem.ItemSpec); + var sourceProject = new Project(); + sourceProject.Load(projectPath); + + // Collect the project items from the project that are appropriate + // to include in the .vstemplate file. + var itemsByFolder = from item in sourceProject.EvaluatedItems.Cast<BuildItem>() + where this.ProjectItemTypes.Contains(item.Name) + orderby item.Include + group item by Path.GetDirectoryName(item.Include); + foreach (var folder in itemsByFolder) { + XElement parentNode = FindOrCreateParent(folder.Key, projectElement); + + foreach (var item in folder) { + bool replaceParameters = this.ReplaceParametersExtensions.Contains(Path.GetExtension(item.Include)); + var itemName = XName.Get("ProjectItem", VSTemplateNamespace); + var projectItem = parentNode.Elements(itemName).FirstOrDefault(el => string.Equals(el.Value, Path.GetFileName(item.Include), StringComparison.OrdinalIgnoreCase)); + if (projectItem == null) { + projectItem = new XElement(itemName, Path.GetFileName(item.Include)); + parentNode.Add(projectItem); + } + if (replaceParameters) { + projectItem.SetAttributeValue("ReplaceParameters", "true"); + } + } + } + + template.Save(sourceTemplateTaskItem.ItemSpec); + } + + return !Log.HasLoggedErrors; + } + + private static XElement FindOrCreateParent(string directoryName, XElement projectElement) { + Contract.Requires<ArgumentNullException>(projectElement != null); + + if (string.IsNullOrEmpty(directoryName)) { + return projectElement; + } + + string[] segments = directoryName.Split(Path.DirectorySeparatorChar); + XElement parent = projectElement; + for (int i = 0; i < segments.Length; i++) { + var candidateName = XName.Get("Folder", VSTemplateNamespace); + var candidate = parent.Elements(XName.Get("Folder", VSTemplateNamespace)).FirstOrDefault(n => n.Attribute("Name").Value == segments[i]); + if (candidate == null) { + candidate = new XElement( + candidateName, + new XAttribute("Name", segments[i])); + parent.Add(candidate); + } + + parent = candidate; + } + + return parent; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/ParseMaster.cs b/src/DotNetOpenAuth.BuildTasks/ParseMaster.cs new file mode 100644 index 0000000..7edba29 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/ParseMaster.cs @@ -0,0 +1,250 @@ +using System; +using System.Text; +using System.Text.RegularExpressions; +using System.Collections; +using System.Collections.Specialized; + +/* + ParseMaster, version 1.0 (pre-release) (2005/02/01) x4 + Copyright 2005, Dean Edwards + Web: http://dean.edwards.name/ + + This software is licensed under the CC-GNU LGPL + Web: http://creativecommons.org/licenses/LGPL/2.1/ + + Ported to C# by Jesse Hansen, twindagger2k@msn.com +*/ + +namespace Dean.Edwards +{ + /// <summary> + /// a multi-pattern parser + /// </summary> + internal class ParseMaster + { + // used to determine nesting levels + Regex GROUPS = new Regex("\\("), + SUB_REPLACE = new Regex("\\$"), + INDEXED = new Regex("^\\$\\d+$"), + ESCAPE = new Regex("\\\\."), + QUOTE = new Regex("'"), + DELETED = new Regex("\\x01[^\\x01]*\\x01"); + + /// <summary> + /// Delegate to call when a regular expression is found. + /// Use match.Groups[offset + <group number>].Value to get + /// the correct subexpression + /// </summary> + public delegate string MatchGroupEvaluator(Match match, int offset); + + private string DELETE(Match match, int offset) + { + return "\x01" + match.Groups[offset].Value + "\x01"; + } + + private bool ignoreCase = false; + private char escapeChar = '\0'; + + /// <summary> + /// Ignore Case? + /// </summary> + public bool IgnoreCase + { + get { return ignoreCase; } + set { ignoreCase = value; } + } + + /// <summary> + /// Escape Character to use + /// </summary> + public char EscapeChar + { + get { return escapeChar; } + set { escapeChar = value; } + } + + /// <summary> + /// Add an expression to be deleted + /// </summary> + /// <param name="expression">Regular Expression String</param> + public void Add(string expression) + { + Add(expression, string.Empty); + } + + /// <summary> + /// Add an expression to be replaced with the replacement string + /// </summary> + /// <param name="expression">Regular Expression String</param> + /// <param name="replacement">Replacement String. Use $1, $2, etc. for groups</param> + public void Add(string expression, string replacement) + { + if (replacement == string.Empty) + add(expression, new MatchGroupEvaluator(DELETE)); + + add(expression, replacement); + } + + /// <summary> + /// Add an expression to be replaced using a callback function + /// </summary> + /// <param name="expression">Regular expression string</param> + /// <param name="replacement">Callback function</param> + public void Add(string expression, MatchGroupEvaluator replacement) + { + add(expression, replacement); + } + + /// <summary> + /// Executes the parser + /// </summary> + /// <param name="input">input string</param> + /// <returns>parsed string</returns> + public string Exec(string input) + { + return DELETED.Replace(unescape(getPatterns().Replace(escape(input), new MatchEvaluator(replacement))), string.Empty); + //long way for debugging + /*input = escape(input); + Regex patterns = getPatterns(); + input = patterns.Replace(input, new MatchEvaluator(replacement)); + input = DELETED.Replace(input, string.Empty); + return input;*/ + } + + ArrayList patterns = new ArrayList(); + private void add(string expression, object replacement) + { + Pattern pattern = new Pattern(); + pattern.expression = expression; + pattern.replacement = replacement; + //count the number of sub-expressions + // - add 1 because each group is itself a sub-expression + pattern.length = GROUPS.Matches(internalEscape(expression)).Count + 1; + + //does the pattern deal with sup-expressions? + if (replacement is string && SUB_REPLACE.IsMatch((string) replacement)) + { + string sreplacement = (string) replacement; + // a simple lookup (e.g. $2) + if (INDEXED.IsMatch(sreplacement)) + { + pattern.replacement = int.Parse(sreplacement.Substring(1)) - 1; + } + } + + patterns.Add(pattern); + } + + /// <summary> + /// builds the patterns into a single regular expression + /// </summary> + /// <returns></returns> + private Regex getPatterns() + { + StringBuilder rtrn = new StringBuilder(string.Empty); + foreach (object pattern in patterns) + { + rtrn.Append(((Pattern) pattern).ToString() + "|"); + } + rtrn.Remove(rtrn.Length - 1, 1); + return new Regex(rtrn.ToString(), ignoreCase ? RegexOptions.IgnoreCase : RegexOptions.None ); + } + + /// <summary> + /// Global replacement function. Called once for each match found + /// </summary> + /// <param name="match">Match found</param> + private string replacement(Match match) + { + int i = 1, j = 0; + Pattern pattern; + //loop through the patterns + while (!((pattern = (Pattern) patterns[j++]) == null)) + { + //do we have a result? + if (match.Groups[i].Value != string.Empty) + { + object replacement = pattern.replacement; + if (replacement is MatchGroupEvaluator) + { + return ((MatchGroupEvaluator) replacement)(match, i); + } + else if (replacement is int) + { + return match.Groups[(int) replacement + i].Value; + } + else + { + //string, send to interpreter + return replacementString(match, i, (string) replacement, pattern.length); + } + } + else //skip over references to sub-expressions + i += pattern.length; + } + return match.Value; //should never be hit, but you never know + } + + /// <summary> + /// Replacement function for complicated lookups (e.g. Hello $3 $2) + /// </summary> + private string replacementString(Match match, int offset, string replacement, int length) + { + while (length > 0) + { + replacement = replacement.Replace("$" + length--, match.Groups[offset + length].Value); + } + return replacement; + } + + private StringCollection escaped = new StringCollection(); + + //encode escaped characters + private string escape(string str) + { + if (escapeChar == '\0') + return str; + Regex escaping = new Regex("\\\\(.)"); + return escaping.Replace(str, new MatchEvaluator(escapeMatch)); + } + + private string escapeMatch(Match match) + { + escaped.Add(match.Groups[1].Value); + return "\\"; + } + + //decode escaped characters + private int unescapeIndex = 0; + private string unescape(string str) + { + if (escapeChar == '\0') + return str; + Regex unescaping = new Regex("\\" + escapeChar); + return unescaping.Replace(str, new MatchEvaluator(unescapeMatch)); + } + + private string unescapeMatch(Match match) + { + return "\\" + escaped[unescapeIndex++]; + } + + private string internalEscape(string str) + { + return ESCAPE.Replace(str, ""); + } + + //subclass for each pattern + private class Pattern + { + public string expression; + public object replacement; + public int length; + + public override string ToString() + { + return "(" + expression + ")"; + } + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.BuildTasks/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..6fdcc21 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("CustomMsBuildTasks")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("CustomMsBuildTasks")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("952e3aaa-5dc6-4b71-8c9c-6b485263be19")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/DotNetOpenAuth.BuildTasks/Purge.cs b/src/DotNetOpenAuth.BuildTasks/Purge.cs new file mode 100644 index 0000000..f23a6d9 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/Purge.cs @@ -0,0 +1,88 @@ +//----------------------------------------------------------------------- +// <copyright file="Purge.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using Microsoft.Build.Utilities; + using Microsoft.Build.Framework; + using System.IO; + using System.Text.RegularExpressions; + + /// <summary> + /// Purges directory trees of all directories and files that are not on a whitelist. + /// </summary> + /// <remarks> + /// This task performs a function similar to robocopy's /MIR switch, except that + /// this task does not require that an entire directory tree be used as the source + /// in order to purge old files from the destination. + /// </remarks> + public class Purge : Task { + /// <summary> + /// Initializes a new instance of the <see cref="Purge"/> class. + /// </summary> + public Purge() { + this.PurgeEmptyDirectories = true; + } + + /// <summary> + /// Gets or sets the root directories to purge. + /// </summary> + /// <value>The directories.</value> + [Required] + public string[] Directories { get; set; } + + /// <summary> + /// Gets or sets the files that should be NOT be purged. + /// </summary> + public ITaskItem[] IntendedFiles { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether empty directories will be deleted. + /// </summary> + /// <value> + /// The default value is <c>true</c>. + /// </value> + public bool PurgeEmptyDirectories { get; set; } + + /// <summary> + /// Executes this instance. + /// </summary> + public override bool Execute() { + HashSet<string> intendedFiles = new HashSet<string>(this.IntendedFiles.Select(file => file.GetMetadata("FullPath")), StringComparer.OrdinalIgnoreCase); + + foreach (string directory in this.Directories.Select(dir => NormalizePath(dir))) { + foreach (string existingFile in Directory.GetFiles(directory, "*", SearchOption.AllDirectories)) { + if (!intendedFiles.Contains(existingFile)) { + this.Log.LogWarning("Purging file \"{0}\".", existingFile); + File.Delete(existingFile); + } + } + + if (this.PurgeEmptyDirectories) { + foreach (string subdirectory in Directory.GetDirectories(directory, "*", SearchOption.AllDirectories)) { + // We have to check for the existance of the directory because it MAY be + // a descendent of a directory we already deleted in this loop. + if (Directory.Exists(subdirectory)) { + if (Directory.GetDirectories(subdirectory).Length == 0 && Directory.GetFiles(subdirectory).Length == 0) { + this.Log.LogWarning("Purging empty directory \"{0}\".", subdirectory); + Directory.Delete(subdirectory); + } + } + } + } + } + + return !this.Log.HasLoggedErrors; + } + + private static string NormalizePath(string path) { + return Regex.Replace(path, @"\\+", @"\"); + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/ReSignDelaySignedAssemblies.cs b/src/DotNetOpenAuth.BuildTasks/ReSignDelaySignedAssemblies.cs new file mode 100644 index 0000000..a0ba386 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/ReSignDelaySignedAssemblies.cs @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------- +// <copyright file="ReSignDelaySignedAssemblies.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using System; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + public class ReSignDelaySignedAssemblies : SnToolTask { + /// <summary> + /// Gets or sets the key file to use for signing. + /// </summary> + public ITaskItem KeyFile { get; set; } + + /// <summary> + /// Gets or sets the key container. + /// </summary> + public ITaskItem KeyContainer { get; set; } + + /// <summary> + /// Gets or sets the assemblies to re-sign. + /// </summary> + public ITaskItem[] Assemblies { get; set; } + + /// <summary> + /// Generates the command line commands. + /// </summary> + protected override string GenerateCommandLineCommands() { + ////if (this.Assemblies.Length != 1) { + //// throw new NotSupportedException("Exactly 1 assembly for signing is supported."); + ////} + CommandLineBuilder args = new CommandLineBuilder(); + args.AppendSwitch("-q"); + + if (this.KeyFile != null) { + args.AppendSwitch("-R"); + } else if (this.KeyContainer != null) { + args.AppendSwitch("-Rc"); + } else { + throw new InvalidOperationException("Either KeyFile or KeyContainer must be set."); + } + + args.AppendFileNameIfNotNull(this.Assemblies[0]); + if (this.KeyFile != null) { + args.AppendFileNameIfNotNull(this.KeyFile); + } else { + args.AppendFileNameIfNotNull(this.KeyContainer); + } + + return args.ToString(); + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/SetEnvironmentVariable.cs b/src/DotNetOpenAuth.BuildTasks/SetEnvironmentVariable.cs new file mode 100644 index 0000000..2de5976 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/SetEnvironmentVariable.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.Build.Utilities; +using Microsoft.Build.Framework; + +namespace DotNetOpenAuth.BuildTasks { + public class SetEnvironmentVariable : Task { + public SetEnvironmentVariable() { + Scope = EnvironmentVariableTarget.Process; + } + + /// <summary> + /// The name of the environment variable to set or clear. + /// </summary> + [Required] + public string Name { get; set; } + /// <summary> + /// The value of the environment variable, or the empty string to clear it. + /// </summary> + [Required] + public string Value { get; set; } + /// <summary> + /// The target environment for the variable. Machine, User, or Process. + /// </summary> + public EnvironmentVariableTarget Scope { get; set; } + + public override bool Execute() { + Environment.SetEnvironmentVariable(Name, Value, Scope); + return true; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/SignatureVerification.cs b/src/DotNetOpenAuth.BuildTasks/SignatureVerification.cs new file mode 100644 index 0000000..2e69926 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/SignatureVerification.cs @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------- +// <copyright file="SignatureVerification.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using Microsoft.Build.Utilities; + + public class SignatureVerification : SnToolTask { + /// <summary> + /// Gets or sets a value indicating whether to register the given assembly and public key token + /// for skip verification or clear any pre-existing skip verification entry. + /// </summary> + public bool SkipVerification { get; set; } + + /// <summary> + /// Gets or sets the name of the assembly. + /// </summary> + /// <value>The name of the assembly.</value> + public string AssemblyName { get; set; } + + /// <summary> + /// Gets or sets the public key token. + /// </summary> + /// <value>The public key token.</value> + public string PublicKeyToken { get; set; } + + /// <summary> + /// Generates the command line commands. + /// </summary> + protected override string GenerateCommandLineCommands() { + CommandLineBuilder builder = new CommandLineBuilder(); + builder.AppendSwitch("-q"); + if (this.SkipVerification) { + builder.AppendSwitch("-Vr"); + } else { + builder.AppendSwitch("-Vu"); + } + + builder.AppendFileNameIfNotNull(this.AssemblyName + "," + this.PublicKeyToken); + return builder.ToString(); + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/SnToolTask.cs b/src/DotNetOpenAuth.BuildTasks/SnToolTask.cs new file mode 100644 index 0000000..29896fe --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/SnToolTask.cs @@ -0,0 +1,37 @@ +//----------------------------------------------------------------------- +// <copyright file="SnToolTask.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using System; + using System.IO; + using Microsoft.Build.Utilities; + + public abstract class SnToolTask : ToolTask { + /// <summary> + /// Gets the name of the tool. + /// </summary> + /// <value>The name of the tool.</value> + protected override string ToolName { + get { return "sn.exe"; } + } + + /// <summary> + /// Generates the full path to tool. + /// </summary> + protected override string GenerateFullPathToTool() { + string[] versions = new[] { "v6.0A", "v6.1", "v7.0a" }; + string fullPath = null; + foreach (string version in versions) { + fullPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Microsoft SDKs\Windows\" + version + @"\bin\" + this.ToolName); + if (File.Exists(fullPath)) { + return fullPath; + } + } + + throw new FileNotFoundException("Unable to find sn.exe tool.", fullPath); + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/TaskStrings.Designer.cs b/src/DotNetOpenAuth.BuildTasks/TaskStrings.Designer.cs new file mode 100644 index 0000000..17647fd --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/TaskStrings.Designer.cs @@ -0,0 +1,117 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:2.0.50727.4927 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace DotNetOpenAuth.BuildTasks { + using System; + + + /// <summary> + /// A strongly-typed resource class, for looking up localized strings, etc. + /// </summary> + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class TaskStrings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal TaskStrings() { + } + + /// <summary> + /// Returns the cached ResourceManager instance used by this class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DotNetOpenAuth.BuildTasks.TaskStrings", typeof(TaskStrings).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// <summary> + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// <summary> + /// Looks up a localized string similar to Web application '{0}' deleted.. + /// </summary> + internal static string DeletedWebApplication { + get { + return ResourceManager.GetString("DeletedWebApplication", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The {0} and {1} properties must be set to arrays of equal length.. + /// </summary> + internal static string MismatchingArrayLengths { + get { + return ResourceManager.GetString("MismatchingArrayLengths", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to No web site with the name '{0}' found.. + /// </summary> + internal static string NoMatchingWebSiteFound { + get { + return ResourceManager.GetString("NoMatchingWebSiteFound", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Packing javascript resource "{0}" into "{1}".. + /// </summary> + internal static string PackingJsFile { + get { + return ResourceManager.GetString("PackingJsFile", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Skip packing "{0}" because its packed version is up to date.. + /// </summary> + internal static string SkipPackingJsFile { + get { + return ResourceManager.GetString("SkipPackingJsFile", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Web application '{0}' was not found, so it was not deleted.. + /// </summary> + internal static string WebApplicationNotFoundSoNotDeleted { + get { + return ResourceManager.GetString("WebApplicationNotFoundSoNotDeleted", resourceCulture); + } + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/TaskStrings.resx b/src/DotNetOpenAuth.BuildTasks/TaskStrings.resx new file mode 100644 index 0000000..50e1592 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/TaskStrings.resx @@ -0,0 +1,138 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="DeletedWebApplication" xml:space="preserve"> + <value>Web application '{0}' deleted.</value> + </data> + <data name="MismatchingArrayLengths" xml:space="preserve"> + <value>The {0} and {1} properties must be set to arrays of equal length.</value> + </data> + <data name="NoMatchingWebSiteFound" xml:space="preserve"> + <value>No web site with the name '{0}' found.</value> + </data> + <data name="PackingJsFile" xml:space="preserve"> + <value>Packing javascript resource "{0}" into "{1}".</value> + </data> + <data name="SkipPackingJsFile" xml:space="preserve"> + <value>Skip packing "{0}" because its packed version is up to date.</value> + </data> + <data name="WebApplicationNotFoundSoNotDeleted" xml:space="preserve"> + <value>Web application '{0}' was not found, so it was not deleted.</value> + </data> +</root>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.BuildTasks/Trim.cs b/src/DotNetOpenAuth.BuildTasks/Trim.cs new file mode 100644 index 0000000..972b87d --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/Trim.cs @@ -0,0 +1,79 @@ +//----------------------------------------------------------------------- +// <copyright file="Trim.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + /// <summary> + /// Trims item identities or metadata. + /// </summary> + public class Trim : Task { + /// <summary> + /// Gets or sets the name of the metadata to trim. Leave empty or null to operate on itemspec. + /// </summary> + /// <value>The name of the metadata.</value> + public string MetadataName { get; set; } + + /// <summary> + /// Gets or sets the characters that should be trimmed off if found at the start of items' ItemSpecs. + /// </summary> + public string StartCharacters { get; set; } + + /// <summary> + /// Gets or sets the characters that should be trimmed off if found at the end of items' ItemSpecs. + /// </summary> + public string EndCharacters { get; set; } + + /// <summary> + /// Gets or sets the substring that should be trimmed along with everything that appears after it. + /// </summary> + public string AllAfter { get; set; } + + /// <summary> + /// Gets or sets the items with ItemSpec's to be trimmed. + /// </summary> + [Required] + public ITaskItem[] Inputs { get; set; } + + /// <summary> + /// Gets or sets the items with trimmed ItemSpec strings. + /// </summary> + [Output] + public ITaskItem[] Outputs { get; set; } + + /// <summary> + /// Executes this instance. + /// </summary> + /// <returns>A value indicating whether the task completed successfully.</returns> + public override bool Execute() { + this.Outputs = new ITaskItem[this.Inputs.Length]; + for (int i = 0; i < this.Inputs.Length; i++) { + this.Outputs[i] = new TaskItem(this.Inputs[i]); + string value = string.IsNullOrEmpty(this.MetadataName) ? this.Outputs[i].ItemSpec : this.Outputs[i].GetMetadata(this.MetadataName); + if (!string.IsNullOrEmpty(this.StartCharacters)) { + value = value.TrimStart(this.StartCharacters.ToCharArray()); + } + if (!string.IsNullOrEmpty(this.EndCharacters)) { + value = value.TrimEnd(this.EndCharacters.ToCharArray()); + } + if (!string.IsNullOrEmpty(this.AllAfter)) { + int index = value.IndexOf(this.AllAfter); + if (index >= 0) { + value = value.Substring(0, index); + } + } + if (string.IsNullOrEmpty(this.MetadataName)) { + this.Outputs[i].ItemSpec = value; + } else { + this.Outputs[i].SetMetadata(this.MetadataName, value); + } + } + + return true; + } + } +} diff --git a/src/DotNetOpenAuth.Test/App.config b/src/DotNetOpenAuth.Test/App.config index 359e25f..dafb99d 100644 --- a/src/DotNetOpenAuth.Test/App.config +++ b/src/DotNetOpenAuth.Test/App.config @@ -50,4 +50,8 @@ </provider> </openid> </dotNetOpenAuth> + + <system.diagnostics> + <assert assertuienabled="false"/> + </system.diagnostics> </configuration>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.Test/AssemblyTesting.cs b/src/DotNetOpenAuth.Test/AssemblyTesting.cs index d0868d2..7659a82 100644 --- a/src/DotNetOpenAuth.Test/AssemblyTesting.cs +++ b/src/DotNetOpenAuth.Test/AssemblyTesting.cs @@ -14,10 +14,9 @@ namespace DotNetOpenAuth.Test { public static void AssemblyInitialize(TestContext tc) { // Make contract failures become test failures. Contract.ContractFailed += (sender, e) => { - if (e.FailureKind == ContractFailureKind.Precondition) { - // Currently we ignore these so that the regular ErrorUtilities can kick in. - e.SetHandled(); - } else { + // For now, we have tests that verify that preconditions throw exceptions. + // So we don't want to fail a test just because a precondition check failed. + if (e.FailureKind != ContractFailureKind.Precondition) { e.SetHandled(); Assert.Fail(e.FailureKind.ToString() + ": " + e.Message); } diff --git a/src/DotNetOpenAuth.Test/CoordinatorBase.cs b/src/DotNetOpenAuth.Test/CoordinatorBase.cs index d1bf27c..df331f3 100644 --- a/src/DotNetOpenAuth.Test/CoordinatorBase.cs +++ b/src/DotNetOpenAuth.Test/CoordinatorBase.cs @@ -6,6 +6,7 @@ namespace DotNetOpenAuth.Test { using System; + using System.Diagnostics.Contracts; using System.Threading; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.RelyingParty; @@ -17,8 +18,8 @@ namespace DotNetOpenAuth.Test { private Action<T2> party2Action; protected CoordinatorBase(Action<T1> party1Action, Action<T2> party2Action) { - ErrorUtilities.VerifyArgumentNotNull(party1Action, "party1Action"); - ErrorUtilities.VerifyArgumentNotNull(party2Action, "party2Action"); + Contract.Requires<ArgumentNullException>(party1Action != null); + Contract.Requires<ArgumentNullException>(party2Action != null); this.party1Action = party1Action; this.party2Action = party2Action; @@ -38,6 +39,7 @@ namespace DotNetOpenAuth.Test { // terminate the other thread and inform the test host that the test failed. Action<Action> safeWrapper = (action) => { try { + TestBase.SetMockHttpContext(); action(); } catch (Exception ex) { // We may be the second thread in an ThreadAbortException, so check the "flag" diff --git a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj index 7212008..d4997ae 100644 --- a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj +++ b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj @@ -21,12 +21,12 @@ <DefineConstants>DEBUG;TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> - <CodeContractsEnableRuntimeChecking>False</CodeContractsEnableRuntimeChecking> + <CodeContractsEnableRuntimeChecking>True</CodeContractsEnableRuntimeChecking> <CodeContractsCustomRewriterAssembly> </CodeContractsCustomRewriterAssembly> <CodeContractsCustomRewriterClass> </CodeContractsCustomRewriterClass> - <CodeContractsRuntimeCheckingLevel>Full</CodeContractsRuntimeCheckingLevel> + <CodeContractsRuntimeCheckingLevel>None</CodeContractsRuntimeCheckingLevel> <CodeContractsRunCodeAnalysis>False</CodeContractsRunCodeAnalysis> <CodeContractsBuildReferenceAssembly>False</CodeContractsBuildReferenceAssembly> <CodeContractsNonNullObligations>False</CodeContractsNonNullObligations> @@ -44,6 +44,8 @@ <CodeContractsRunInBackground>True</CodeContractsRunInBackground> <CodeContractsShowSquigglies>False</CodeContractsShowSquigglies> <CodeContractsRuntimeOnlyPublicSurface>False</CodeContractsRuntimeOnlyPublicSurface> + <CodeContractsRuntimeThrowOnFailure>True</CodeContractsRuntimeThrowOnFailure> + <CodeContractsRuntimeCallSiteRequires>False</CodeContractsRuntimeCallSiteRequires> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> @@ -52,10 +54,34 @@ <DefineConstants>TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> + <CodeContractsEnableRuntimeChecking>True</CodeContractsEnableRuntimeChecking> + <CodeContractsCustomRewriterAssembly> + </CodeContractsCustomRewriterAssembly> + <CodeContractsCustomRewriterClass> + </CodeContractsCustomRewriterClass> + <CodeContractsRuntimeCheckingLevel>None</CodeContractsRuntimeCheckingLevel> + <CodeContractsRuntimeOnlyPublicSurface>False</CodeContractsRuntimeOnlyPublicSurface> + <CodeContractsRuntimeThrowOnFailure>True</CodeContractsRuntimeThrowOnFailure> + <CodeContractsRuntimeCallSiteRequires>False</CodeContractsRuntimeCallSiteRequires> + <CodeContractsRunCodeAnalysis>False</CodeContractsRunCodeAnalysis> + <CodeContractsBuildReferenceAssembly>False</CodeContractsBuildReferenceAssembly> + <CodeContractsNonNullObligations>False</CodeContractsNonNullObligations> + <CodeContractsBoundsObligations>False</CodeContractsBoundsObligations> + <CodeContractsArithmeticObligations>False</CodeContractsArithmeticObligations> + <CodeContractsLibPaths> + </CodeContractsLibPaths> + <CodeContractsPlatformPath> + </CodeContractsPlatformPath> + <CodeContractsExtraAnalysisOptions> + </CodeContractsExtraAnalysisOptions> + <CodeContractsBaseLineFile> + </CodeContractsBaseLineFile> + <CodeContractsUseBaseLine>False</CodeContractsUseBaseLine> + <CodeContractsRunInBackground>True</CodeContractsRunInBackground> + <CodeContractsShowSquigglies>False</CodeContractsShowSquigglies> </PropertyGroup> - <PropertyGroup Condition=" '$(Sign)' == 'true' "> + <PropertyGroup> <SignAssembly>true</SignAssembly> - <AssemblyOriginatorKeyFile>..\official-build-key.pfx</AssemblyOriginatorKeyFile> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'CodeAnalysis|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -71,7 +97,7 @@ </CodeContractsCustomRewriterAssembly> <CodeContractsCustomRewriterClass> </CodeContractsCustomRewriterClass> - <CodeContractsRuntimeCheckingLevel>Full</CodeContractsRuntimeCheckingLevel> + <CodeContractsRuntimeCheckingLevel>None</CodeContractsRuntimeCheckingLevel> <CodeContractsRunCodeAnalysis>True</CodeContractsRunCodeAnalysis> <CodeContractsBuildReferenceAssembly>False</CodeContractsBuildReferenceAssembly> <CodeContractsNonNullObligations>False</CodeContractsNonNullObligations> @@ -87,6 +113,10 @@ <CodeContractsUseBaseLine>False</CodeContractsUseBaseLine> <CodeContractsRunInBackground>True</CodeContractsRunInBackground> <CodeContractsShowSquigglies>True</CodeContractsShowSquigglies> + <CodeContractsRuntimeOnlyPublicSurface>False</CodeContractsRuntimeOnlyPublicSurface> + <CodeContractsRuntimeThrowOnFailure>True</CodeContractsRuntimeThrowOnFailure> + <CodeContractsRuntimeCallSiteRequires>False</CodeContractsRuntimeCallSiteRequires> + <CodeContractsArithmeticObligations>False</CodeContractsArithmeticObligations> </PropertyGroup> <ItemGroup> <Reference Include="log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=1b44e1d426115821, processorArchitecture=MSIL"> @@ -131,10 +161,12 @@ <Compile Include="Hosting\HostingTests.cs" /> <Compile Include="Hosting\HttpHost.cs" /> <Compile Include="Hosting\TestingWorkerRequest.cs" /> + <Compile Include="LocalizationTests.cs" /> <Compile Include="Messaging\CollectionAssert.cs" /> <Compile Include="Messaging\EnumerableCacheTests.cs" /> <Compile Include="Messaging\ErrorUtilitiesTests.cs" /> <Compile Include="Messaging\MessageSerializerTests.cs" /> + <Compile Include="Messaging\MultipartPostPartTests.cs" /> <Compile Include="Messaging\OutgoingWebResponseTests.cs" /> <Compile Include="Messaging\Reflection\MessageDescriptionTests.cs" /> <Compile Include="Messaging\Reflection\MessageDictionaryTests.cs" /> @@ -233,6 +265,7 @@ <Compile Include="OpenId\RelyingParty\AuthenticationRequestTests.cs" /> <Compile Include="OpenId\RelyingParty\FailedAuthenticationResponseTests.cs" /> <Compile Include="OpenId\RelyingParty\NegativeAuthenticationResponseTests.cs" /> + <Compile Include="OpenId\RelyingParty\OpenIdTextBoxTests.cs" /> <Compile Include="OpenId\RelyingParty\PositiveAnonymousResponseTests.cs" /> <Compile Include="OpenId\RelyingParty\PositiveAuthenticationResponseTests.cs" /> <Compile Include="OpenId\RelyingParty\OpenIdRelyingPartyTests.cs" /> @@ -296,4 +329,4 @@ </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="..\..\tools\DotNetOpenAuth.Versioning.targets" /> -</Project>
\ No newline at end of file +</Project> diff --git a/src/DotNetOpenAuth.Test/LocalizationTests.cs b/src/DotNetOpenAuth.Test/LocalizationTests.cs new file mode 100644 index 0000000..50e9a34 --- /dev/null +++ b/src/DotNetOpenAuth.Test/LocalizationTests.cs @@ -0,0 +1,28 @@ +//----------------------------------------------------------------------- +// <copyright file="LocalizationTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test { + using System; + using System.Globalization; + using System.Threading; + using DotNetOpenAuth.Messaging; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + /// <summary> + /// Tests various localized resources work as expected. + /// </summary> + [TestClass] + public class LocalizationTests { + /// <summary> + /// Tests that Serbian localized strings are correctly installed. + /// </summary> + [TestMethod, ExpectedException(typeof(InvalidOperationException), "Ovaj metod zahteva tekući HttpContext. Kao alternativa, koristite preklopljeni metod koji dozvoljava da se prosledi informacija bez HttpContext-a.")] + public void Serbian() { + Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("sr"); + ErrorUtilities.VerifyHttpContext(); + } + } +} diff --git a/src/DotNetOpenAuth.Test/Messaging/Bindings/StandardExpirationBindingElementTests.cs b/src/DotNetOpenAuth.Test/Messaging/Bindings/StandardExpirationBindingElementTests.cs index cbaded1..3cc792b 100644 --- a/src/DotNetOpenAuth.Test/Messaging/Bindings/StandardExpirationBindingElementTests.cs +++ b/src/DotNetOpenAuth.Test/Messaging/Bindings/StandardExpirationBindingElementTests.cs @@ -33,7 +33,7 @@ namespace DotNetOpenAuth.Test.Messaging.Bindings { [TestMethod, ExpectedException(typeof(ExpiredMessageException))] public void VerifyBadTimestampIsRejected() { this.Channel = CreateChannel(MessageProtections.Expiration); - this.ParameterizedReceiveProtectedTest(DateTime.UtcNow - StandardExpirationBindingElement.DefaultMaximumMessageAge - TimeSpan.FromSeconds(1), false); + this.ParameterizedReceiveProtectedTest(DateTime.UtcNow - StandardExpirationBindingElement.MaximumMessageAge - TimeSpan.FromSeconds(1), false); } } } diff --git a/src/DotNetOpenAuth.Test/Messaging/Bindings/StandardReplayProtectionBindingElementTests.cs b/src/DotNetOpenAuth.Test/Messaging/Bindings/StandardReplayProtectionBindingElementTests.cs index 26ce01c..14651bc 100644 --- a/src/DotNetOpenAuth.Test/Messaging/Bindings/StandardReplayProtectionBindingElementTests.cs +++ b/src/DotNetOpenAuth.Test/Messaging/Bindings/StandardReplayProtectionBindingElementTests.cs @@ -31,6 +31,7 @@ namespace DotNetOpenAuth.Test.Messaging.Bindings { this.protocol = Protocol.Default; this.nonceStore = new NonceMemoryStore(TimeSpan.FromHours(3)); this.nonceElement = new StandardReplayProtectionBindingElement(this.nonceStore); + this.nonceElement.Channel = new Mocks.TestChannel(); this.message = new TestReplayProtectedMessage(); this.message.UtcCreationDate = DateTime.UtcNow; } diff --git a/src/DotNetOpenAuth.Test/Messaging/ChannelTests.cs b/src/DotNetOpenAuth.Test/Messaging/ChannelTests.cs index 669abbc..7846411 100644 --- a/src/DotNetOpenAuth.Test/Messaging/ChannelTests.cs +++ b/src/DotNetOpenAuth.Test/Messaging/ChannelTests.cs @@ -201,6 +201,7 @@ namespace DotNetOpenAuth.Test.Messaging { [TestMethod, ExpectedException(typeof(InvalidOperationException))] public void ReadFromRequestNoContext() { + HttpContext.Current = null; TestBadChannel badChannel = new TestBadChannel(false); badChannel.ReadFromRequest(); } diff --git a/src/DotNetOpenAuth.Test/Messaging/CollectionAssert.cs b/src/DotNetOpenAuth.Test/Messaging/CollectionAssert.cs index f9e569a..db136f5 100644 --- a/src/DotNetOpenAuth.Test/Messaging/CollectionAssert.cs +++ b/src/DotNetOpenAuth.Test/Messaging/CollectionAssert.cs @@ -5,16 +5,18 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.Test.Messaging { + using System; using System.Collections; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.Linq; using DotNetOpenAuth.Messaging; using Microsoft.VisualStudio.TestTools.UnitTesting; internal class CollectionAssert<T> { internal static void AreEquivalent(ICollection<T> expected, ICollection<T> actual) { - ErrorUtilities.VerifyArgumentNotNull(expected, "expected"); - ErrorUtilities.VerifyArgumentNotNull(actual, "actual"); + Contract.Requires<ArgumentNullException>(expected != null); + Contract.Requires<ArgumentNullException>(actual != null); ICollection expectedNonGeneric = new List<T>(expected); ICollection actualNonGeneric = new List<T>(actual); @@ -22,8 +24,8 @@ namespace DotNetOpenAuth.Test.Messaging { } internal static void AreEquivalentByEquality(ICollection<T> expected, ICollection<T> actual) { - ErrorUtilities.VerifyArgumentNotNull(expected, "expected"); - ErrorUtilities.VerifyArgumentNotNull(actual, "actual"); + Contract.Requires<ArgumentNullException>(expected != null); + Contract.Requires<ArgumentNullException>(actual != null); Assert.AreEqual(expected.Count, actual.Count); foreach (T value in expected) { @@ -32,7 +34,7 @@ namespace DotNetOpenAuth.Test.Messaging { } internal static void Contains(IEnumerable<T> sequence, T element) { - ErrorUtilities.VerifyArgumentNotNull(sequence, "sequence"); + Contract.Requires<ArgumentNullException>(sequence != null); if (!sequence.Contains(element)) { Assert.Fail("Sequence did not include expected element '{0}'.", element); diff --git a/src/DotNetOpenAuth.Test/Messaging/MultiPartPostPartTests.cs b/src/DotNetOpenAuth.Test/Messaging/MultiPartPostPartTests.cs new file mode 100644 index 0000000..f87ae59 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Messaging/MultiPartPostPartTests.cs @@ -0,0 +1,99 @@ +//----------------------------------------------------------------------- +// <copyright file="MultipartPostPartTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Messaging { + using System.CodeDom.Compiler; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.IO; + using System.Net; + using DotNetOpenAuth.Messaging; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class MultipartPostPartTests : TestBase { + /// <summary> + /// Verifies that the Length property matches the length actually serialized. + /// </summary> + [TestMethod] + public void FormDataSerializeMatchesLength() { + var part = MultipartPostPart.CreateFormPart("a", "b"); + VerifyLength(part); + } + + /// <summary> + /// Verifies that the length property matches the length actually serialized. + /// </summary> + [TestMethod] + public void FileSerializeMatchesLength() { + using (TempFileCollection tfc = new TempFileCollection()) { + string file = tfc.AddExtension(".txt"); + File.WriteAllText(file, "sometext"); + var part = MultipartPostPart.CreateFormFilePart("someformname", file, "text/plain"); + VerifyLength(part); + } + } + + /// <summary> + /// Verifies MultiPartPost sends the right number of bytes. + /// </summary> + [TestMethod] + public void MultiPartPostAscii() { + using (TempFileCollection tfc = new TempFileCollection()) { + string file = tfc.AddExtension("txt"); + File.WriteAllText(file, "sometext"); + this.VerifyFullPost(new List<MultipartPostPart> { + MultipartPostPart.CreateFormPart("a", "b"), + MultipartPostPart.CreateFormFilePart("SomeFormField", file, "text/plain"), + }); + } + } + + /// <summary> + /// Verifies MultiPartPost sends the right number of bytes. + /// </summary> + [TestMethod] + public void MultiPartPostMultiByteCharacters() { + using (TempFileCollection tfc = new TempFileCollection()) { + string file = tfc.AddExtension("txt"); + File.WriteAllText(file, "\x1020\x818"); + this.VerifyFullPost(new List<MultipartPostPart> { + MultipartPostPart.CreateFormPart("a", "\x987"), + MultipartPostPart.CreateFormFilePart("SomeFormField", file, "text/plain"), + }); + } + } + + private static void VerifyLength(MultipartPostPart part) { + Contract.Requires(part != null); + + var expectedLength = part.Length; + var ms = new MemoryStream(); + var sw = new StreamWriter(ms); + part.Serialize(sw); + sw.Flush(); + var actualLength = ms.Length; + Assert.AreEqual(expectedLength, actualLength); + } + + private void VerifyFullPost(List<MultipartPostPart> parts) { + var request = (HttpWebRequest)WebRequest.Create("http://localhost"); + var handler = new Mocks.TestWebRequestHandler(); + bool posted = false; + handler.Callback = req => { + foreach (string header in req.Headers) { + TestContext.WriteLine("{0}: {1}", header, req.Headers[header]); + } + TestContext.WriteLine(handler.RequestEntityAsString); + Assert.AreEqual(req.ContentLength, handler.RequestEntityStream.Length); + posted = true; + return null; + }; + request.PostMultipart(handler, parts); + Assert.IsTrue(posted, "HTTP POST never sent."); + } + } +} diff --git a/src/DotNetOpenAuth.Test/Messaging/MultipartPostPartTests.cs b/src/DotNetOpenAuth.Test/Messaging/MultipartPostPartTests.cs new file mode 100644 index 0000000..f87ae59 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Messaging/MultipartPostPartTests.cs @@ -0,0 +1,99 @@ +//----------------------------------------------------------------------- +// <copyright file="MultipartPostPartTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Messaging { + using System.CodeDom.Compiler; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.IO; + using System.Net; + using DotNetOpenAuth.Messaging; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class MultipartPostPartTests : TestBase { + /// <summary> + /// Verifies that the Length property matches the length actually serialized. + /// </summary> + [TestMethod] + public void FormDataSerializeMatchesLength() { + var part = MultipartPostPart.CreateFormPart("a", "b"); + VerifyLength(part); + } + + /// <summary> + /// Verifies that the length property matches the length actually serialized. + /// </summary> + [TestMethod] + public void FileSerializeMatchesLength() { + using (TempFileCollection tfc = new TempFileCollection()) { + string file = tfc.AddExtension(".txt"); + File.WriteAllText(file, "sometext"); + var part = MultipartPostPart.CreateFormFilePart("someformname", file, "text/plain"); + VerifyLength(part); + } + } + + /// <summary> + /// Verifies MultiPartPost sends the right number of bytes. + /// </summary> + [TestMethod] + public void MultiPartPostAscii() { + using (TempFileCollection tfc = new TempFileCollection()) { + string file = tfc.AddExtension("txt"); + File.WriteAllText(file, "sometext"); + this.VerifyFullPost(new List<MultipartPostPart> { + MultipartPostPart.CreateFormPart("a", "b"), + MultipartPostPart.CreateFormFilePart("SomeFormField", file, "text/plain"), + }); + } + } + + /// <summary> + /// Verifies MultiPartPost sends the right number of bytes. + /// </summary> + [TestMethod] + public void MultiPartPostMultiByteCharacters() { + using (TempFileCollection tfc = new TempFileCollection()) { + string file = tfc.AddExtension("txt"); + File.WriteAllText(file, "\x1020\x818"); + this.VerifyFullPost(new List<MultipartPostPart> { + MultipartPostPart.CreateFormPart("a", "\x987"), + MultipartPostPart.CreateFormFilePart("SomeFormField", file, "text/plain"), + }); + } + } + + private static void VerifyLength(MultipartPostPart part) { + Contract.Requires(part != null); + + var expectedLength = part.Length; + var ms = new MemoryStream(); + var sw = new StreamWriter(ms); + part.Serialize(sw); + sw.Flush(); + var actualLength = ms.Length; + Assert.AreEqual(expectedLength, actualLength); + } + + private void VerifyFullPost(List<MultipartPostPart> parts) { + var request = (HttpWebRequest)WebRequest.Create("http://localhost"); + var handler = new Mocks.TestWebRequestHandler(); + bool posted = false; + handler.Callback = req => { + foreach (string header in req.Headers) { + TestContext.WriteLine("{0}: {1}", header, req.Headers[header]); + } + TestContext.WriteLine(handler.RequestEntityAsString); + Assert.AreEqual(req.ContentLength, handler.RequestEntityStream.Length); + posted = true; + return null; + }; + request.PostMultipart(handler, parts); + Assert.IsTrue(posted, "HTTP POST never sent."); + } + } +} diff --git a/src/DotNetOpenAuth.Test/Messaging/ResponseTests.cs b/src/DotNetOpenAuth.Test/Messaging/ResponseTests.cs index a0e7c3f..89d165a 100644 --- a/src/DotNetOpenAuth.Test/Messaging/ResponseTests.cs +++ b/src/DotNetOpenAuth.Test/Messaging/ResponseTests.cs @@ -15,6 +15,7 @@ namespace DotNetOpenAuth.Test.Messaging { public class ResponseTests : TestBase { [TestMethod, ExpectedException(typeof(InvalidOperationException))] public void SendWithoutAspNetContext() { + HttpContext.Current = null; new OutgoingWebResponse().Send(); } diff --git a/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs b/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs index 3bbe6e3..16386de 100644 --- a/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs +++ b/src/DotNetOpenAuth.Test/Mocks/CoordinatingChannel.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.Test.Mocks { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.Linq; using System.Text; using System.Threading; @@ -84,7 +85,7 @@ namespace DotNetOpenAuth.Test.Mocks { /// <param name="outgoingMessageFilter">The outgoing message filter. May be null.</param> internal CoordinatingChannel(Channel wrappedChannel, Action<IProtocolMessage> incomingMessageFilter, Action<IProtocolMessage> outgoingMessageFilter) : base(GetMessageFactory(wrappedChannel), wrappedChannel.BindingElements.ToArray()) { - ErrorUtilities.VerifyArgumentNotNull(wrappedChannel, "wrappedChannel"); + Contract.Requires<ArgumentNullException>(wrappedChannel != null); this.wrappedChannel = wrappedChannel; this.incomingMessageFilter = incomingMessageFilter; @@ -220,7 +221,7 @@ namespace DotNetOpenAuth.Test.Mocks { /// channel since their message factory is not used. /// </remarks> protected virtual T CloneSerializedParts<T>(T message) where T : class, IProtocolMessage { - ErrorUtilities.VerifyArgumentNotNull(message, "message"); + Contract.Requires<ArgumentNullException>(message != null); IProtocolMessage clonedMessage; var messageAccessor = this.MessageDescriptions.GetAccessor(message); @@ -251,7 +252,7 @@ namespace DotNetOpenAuth.Test.Mocks { } private static IMessageFactory GetMessageFactory(Channel channel) { - ErrorUtilities.VerifyArgumentNotNull(channel, "channel"); + Contract.Requires<ArgumentNullException>(channel != null); Channel_Accessor accessor = Channel_Accessor.AttachShadow(channel); return accessor.MessageFactory; diff --git a/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuthChannel.cs b/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuthChannel.cs index 10b0261..e862ca6 100644 --- a/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuthChannel.cs +++ b/src/DotNetOpenAuth.Test/Mocks/CoordinatingOAuthChannel.cs @@ -6,6 +6,7 @@ namespace DotNetOpenAuth.Test.Mocks { using System; + using System.Diagnostics.Contracts; using System.Threading; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Bindings; @@ -30,7 +31,7 @@ namespace DotNetOpenAuth.Test.Mocks { internal CoordinatingOAuthChannel(ITamperProtectionChannelBindingElement signingBindingElement, IConsumerTokenManager tokenManager) : base( signingBindingElement, - new NonceMemoryStore(StandardExpirationBindingElement.DefaultMaximumMessageAge), + new NonceMemoryStore(StandardExpirationBindingElement.MaximumMessageAge), tokenManager) { } @@ -44,7 +45,7 @@ namespace DotNetOpenAuth.Test.Mocks { internal CoordinatingOAuthChannel(ITamperProtectionChannelBindingElement signingBindingElement, IServiceProviderTokenManager tokenManager) : base( signingBindingElement, - new NonceMemoryStore(StandardExpirationBindingElement.DefaultMaximumMessageAge), + new NonceMemoryStore(StandardExpirationBindingElement.MaximumMessageAge), tokenManager) { } @@ -134,7 +135,7 @@ namespace DotNetOpenAuth.Test.Mocks { } private T CloneSerializedParts<T>(T message, HttpRequestInfo requestInfo) where T : class, IProtocolMessage { - ErrorUtilities.VerifyArgumentNotNull(message, "message"); + Contract.Requires<ArgumentNullException>(message != null); IProtocolMessage clonedMessage; var messageAccessor = this.MessageDescriptions.GetAccessor(message); diff --git a/src/DotNetOpenAuth.Test/Mocks/CoordinatingOutgoingWebResponse.cs b/src/DotNetOpenAuth.Test/Mocks/CoordinatingOutgoingWebResponse.cs index 07f9bc9..62ea871 100644 --- a/src/DotNetOpenAuth.Test/Mocks/CoordinatingOutgoingWebResponse.cs +++ b/src/DotNetOpenAuth.Test/Mocks/CoordinatingOutgoingWebResponse.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.Test.Mocks { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.Linq; using System.Text; using DotNetOpenAuth.Messaging; @@ -20,8 +21,8 @@ namespace DotNetOpenAuth.Test.Mocks { /// <param name="message">The direct response message to send to the remote channel. This message will be cloned.</param> /// <param name="receivingChannel">The receiving channel.</param> internal CoordinatingOutgoingWebResponse(IProtocolMessage message, CoordinatingChannel receivingChannel) { - ErrorUtilities.VerifyArgumentNotNull(message, "message"); - ErrorUtilities.VerifyArgumentNotNull(receivingChannel, "receivingChannel"); + Contract.Requires<ArgumentNullException>(message != null); + Contract.Requires<ArgumentNullException>(receivingChannel != null); this.receivingChannel = receivingChannel; this.OriginalMessage = message; diff --git a/src/DotNetOpenAuth.Test/Mocks/InMemoryTokenManager.cs b/src/DotNetOpenAuth.Test/Mocks/InMemoryTokenManager.cs index 48547b7..35672d7 100644 --- a/src/DotNetOpenAuth.Test/Mocks/InMemoryTokenManager.cs +++ b/src/DotNetOpenAuth.Test/Mocks/InMemoryTokenManager.cs @@ -106,6 +106,10 @@ namespace DotNetOpenAuth.Test.Mocks { return this.tokens[token]; } + public void UpdateToken(IServiceProviderRequestToken token) { + // Nothing to do here, since we're using Linq To SQL. + } + #endregion /// <summary> diff --git a/src/DotNetOpenAuth.Test/Mocks/MockHttpRequest.cs b/src/DotNetOpenAuth.Test/Mocks/MockHttpRequest.cs index 66f258d..0213a33 100644 --- a/src/DotNetOpenAuth.Test/Mocks/MockHttpRequest.cs +++ b/src/DotNetOpenAuth.Test/Mocks/MockHttpRequest.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.Test.Mocks { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.Globalization; using System.IO; using System.Net; @@ -22,7 +23,7 @@ namespace DotNetOpenAuth.Test.Mocks { private readonly Dictionary<Uri, IncomingWebResponse> registeredMockResponses = new Dictionary<Uri, IncomingWebResponse>(); private MockHttpRequest(IDirectWebRequestHandler mockHandler) { - ErrorUtilities.VerifyArgumentNotNull(mockHandler, "mockHandler"); + Contract.Requires<ArgumentNullException>(mockHandler != null); this.MockWebRequestHandler = mockHandler; } @@ -41,7 +42,7 @@ namespace DotNetOpenAuth.Test.Mocks { } internal void RegisterMockResponse(IncomingWebResponse response) { - ErrorUtilities.VerifyArgumentNotNull(response, "response"); + Contract.Requires<ArgumentNullException>(response != null); if (this.registeredMockResponses.ContainsKey(response.RequestUri)) { Logger.Http.WarnFormat("Mock HTTP response already registered for {0}.", response.RequestUri); } else { @@ -58,9 +59,9 @@ namespace DotNetOpenAuth.Test.Mocks { } internal void RegisterMockResponse(Uri requestUri, Uri responseUri, string contentType, WebHeaderCollection headers, string responseBody) { - ErrorUtilities.VerifyArgumentNotNull(requestUri, "requestUri"); - ErrorUtilities.VerifyArgumentNotNull(responseUri, "responseUri"); - ErrorUtilities.VerifyNonZeroLength(contentType, "contentType"); + Contract.Requires<ArgumentNullException>(requestUri != null); + Contract.Requires<ArgumentNullException>(responseUri != null); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(contentType)); // Set up the redirect if appropriate if (requestUri != responseUri) { @@ -83,7 +84,7 @@ namespace DotNetOpenAuth.Test.Mocks { } internal void RegisterMockXrdsResponse(ServiceEndpoint endpoint) { - ErrorUtilities.VerifyArgumentNotNull(endpoint, "endpoint"); + Contract.Requires<ArgumentNullException>(endpoint != null); string identityUri; if (endpoint.ClaimedIdentifier == endpoint.Protocol.ClaimedIdentifierForOPIdentifier) { @@ -95,7 +96,7 @@ namespace DotNetOpenAuth.Test.Mocks { } internal void RegisterMockXrdsResponse(Uri respondingUri, IEnumerable<ServiceEndpoint> endpoints) { - ErrorUtilities.VerifyArgumentNotNull(endpoints, "endpoints"); + Contract.Requires<ArgumentNullException>(endpoints != null); StringBuilder xrds = new StringBuilder(); xrds.AppendLine(@"<xrds:XRDS xmlns:xrds='xri://$xrds' xmlns:openid='http://openid.net/xmlns/1.0' xmlns='xri://$xrd*($v*2.0)'> diff --git a/src/DotNetOpenAuth.Test/Mocks/MockIdentifier.cs b/src/DotNetOpenAuth.Test/Mocks/MockIdentifier.cs index 669e4d3..346dde9 100644 --- a/src/DotNetOpenAuth.Test/Mocks/MockIdentifier.cs +++ b/src/DotNetOpenAuth.Test/Mocks/MockIdentifier.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.Test.Mocks { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId; using DotNetOpenAuth.OpenId.RelyingParty; @@ -24,10 +25,10 @@ namespace DotNetOpenAuth.Test.Mocks { private Identifier wrappedIdentifier; public MockIdentifier(Identifier wrappedIdentifier, MockHttpRequest mockHttpRequest, IEnumerable<ServiceEndpoint> endpoints) - : base(false) { - ErrorUtilities.VerifyArgumentNotNull(wrappedIdentifier, "wrappedIdentifier"); - ErrorUtilities.VerifyArgumentNotNull(mockHttpRequest, "mockHttpRequest"); - ErrorUtilities.VerifyArgumentNotNull(endpoints, "endpoints"); + : base(wrappedIdentifier.OriginalString, false) { + Contract.Requires<ArgumentNullException>(wrappedIdentifier != null); + Contract.Requires<ArgumentNullException>(mockHttpRequest != null); + Contract.Requires<ArgumentNullException>(endpoints != null); this.wrappedIdentifier = wrappedIdentifier; this.endpoints = endpoints; diff --git a/src/DotNetOpenAuth.Test/Mocks/MockRealm.cs b/src/DotNetOpenAuth.Test/Mocks/MockRealm.cs index ae39ebb..dd17735 100644 --- a/src/DotNetOpenAuth.Test/Mocks/MockRealm.cs +++ b/src/DotNetOpenAuth.Test/Mocks/MockRealm.cs @@ -5,7 +5,9 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.Test.Mocks { + using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId; @@ -19,7 +21,7 @@ namespace DotNetOpenAuth.Test.Mocks { /// <param name="relyingPartyDescriptions">The relying party descriptions.</param> internal MockRealm(Realm wrappedRealm, params RelyingPartyEndpointDescription[] relyingPartyDescriptions) : base(wrappedRealm) { - ErrorUtilities.VerifyArgumentNotNull(relyingPartyDescriptions, "relyingPartyDescriptions"); + Contract.Requires<ArgumentNullException>(relyingPartyDescriptions != null); this.relyingPartyDescriptions = relyingPartyDescriptions; } diff --git a/src/DotNetOpenAuth.Test/Mocks/TestMessageFactory.cs b/src/DotNetOpenAuth.Test/Mocks/TestMessageFactory.cs index ffb117c..7b13175 100644 --- a/src/DotNetOpenAuth.Test/Mocks/TestMessageFactory.cs +++ b/src/DotNetOpenAuth.Test/Mocks/TestMessageFactory.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.Test.Mocks { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.Linq; using System.Text; using DotNetOpenAuth.Messaging; @@ -32,8 +33,6 @@ namespace DotNetOpenAuth.Test.Mocks { #region IMessageFactory Members public IDirectedProtocolMessage GetNewRequestMessage(MessageReceivingEndpoint recipient, IDictionary<string, string> fields) { - ErrorUtilities.VerifyArgumentNotNull(fields, "fields"); - if (fields.ContainsKey("age")) { if (this.signedMessages) { if (this.expiringMessages) { diff --git a/src/DotNetOpenAuth.Test/Mocks/TestWebRequestHandler.cs b/src/DotNetOpenAuth.Test/Mocks/TestWebRequestHandler.cs index b74c0f0..03dbd6b 100644 --- a/src/DotNetOpenAuth.Test/Mocks/TestWebRequestHandler.cs +++ b/src/DotNetOpenAuth.Test/Mocks/TestWebRequestHandler.cs @@ -56,7 +56,7 @@ namespace DotNetOpenAuth.Test.Mocks { #region IWebRequestHandler Members public bool CanSupport(DirectWebRequestOptions options) { - return options == DirectWebRequestOptions.None; + return true; } /// <summary> diff --git a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs index 856f164..e0cc92a 100644 --- a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs +++ b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.Test.ChannelElements { using System; using System.Collections.Generic; using System.Collections.Specialized; + using System.Diagnostics.Contracts; using System.IO; using System.Net; using System.Text; @@ -34,13 +35,13 @@ namespace DotNetOpenAuth.Test.ChannelElements { this.webRequestHandler = new TestWebRequestHandler(); this.signingElement = new RsaSha1SigningBindingElement(new InMemoryTokenManager()); - this.nonceStore = new NonceMemoryStore(StandardExpirationBindingElement.DefaultMaximumMessageAge); + this.nonceStore = new NonceMemoryStore(StandardExpirationBindingElement.MaximumMessageAge); this.channel = new OAuthChannel(this.signingElement, this.nonceStore, new InMemoryTokenManager(), new TestMessageFactory()); this.accessor = OAuthChannel_Accessor.AttachShadow(this.channel); this.channel.WebRequestHandler = this.webRequestHandler; } - [TestMethod, ExpectedException(typeof(ArgumentException))] + [TestMethod, ExpectedException(typeof(ArgumentNullException))] public void CtorNullSigner() { new OAuthChannel(null, this.nonceStore, new InMemoryTokenManager(), new TestMessageFactory()); } @@ -243,7 +244,7 @@ namespace DotNetOpenAuth.Test.ChannelElements { } private static string CreateAuthorizationHeader(IDictionary<string, string> fields) { - ErrorUtilities.VerifyArgumentNotNull(fields, "fields"); + Contract.Requires<ArgumentNullException>(fields != null); StringBuilder authorization = new StringBuilder(); authorization.Append("OAuth "); diff --git a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/PlaintextSigningBindingElementTest.cs b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/PlaintextSigningBindingElementTest.cs index 627db8f..01d51a3 100644 --- a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/PlaintextSigningBindingElementTest.cs +++ b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/PlaintextSigningBindingElementTest.cs @@ -9,6 +9,7 @@ namespace DotNetOpenAuth.Test.ChannelElements { using DotNetOpenAuth.OAuth; using DotNetOpenAuth.OAuth.ChannelElements; using DotNetOpenAuth.OAuth.Messages; + using DotNetOpenAuth.Test.Mocks; using Microsoft.VisualStudio.TestTools.UnitTesting; [TestClass] @@ -16,6 +17,7 @@ namespace DotNetOpenAuth.Test.ChannelElements { [TestMethod] public void HttpsSignatureGeneration() { SigningBindingElementBase target = new PlaintextSigningBindingElement(); + target.Channel = new TestChannel(); MessageReceivingEndpoint endpoint = new MessageReceivingEndpoint("https://localtest", HttpDeliveryMethods.GetRequest); ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint, Protocol.Default.Version); message.ConsumerSecret = "cs"; @@ -29,6 +31,7 @@ namespace DotNetOpenAuth.Test.ChannelElements { public void HttpsSignatureVerification() { MessageReceivingEndpoint endpoint = new MessageReceivingEndpoint("https://localtest", HttpDeliveryMethods.GetRequest); ITamperProtectionChannelBindingElement target = new PlaintextSigningBindingElement(); + target.Channel = new TestChannel(); ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint, Protocol.Default.Version); message.ConsumerSecret = "cs"; message.TokenSecret = "ts"; @@ -40,6 +43,7 @@ namespace DotNetOpenAuth.Test.ChannelElements { [TestMethod] public void HttpsSignatureVerificationNotApplicable() { SigningBindingElementBase target = new PlaintextSigningBindingElement(); + target.Channel = new TestChannel(); MessageReceivingEndpoint endpoint = new MessageReceivingEndpoint("https://localtest", HttpDeliveryMethods.GetRequest); ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint, Protocol.Default.Version); message.ConsumerSecret = "cs"; @@ -52,6 +56,7 @@ namespace DotNetOpenAuth.Test.ChannelElements { [TestMethod] public void HttpSignatureGeneration() { SigningBindingElementBase target = new PlaintextSigningBindingElement(); + target.Channel = new TestChannel(); MessageReceivingEndpoint endpoint = new MessageReceivingEndpoint("http://localtest", HttpDeliveryMethods.GetRequest); ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint, Protocol.Default.Version); message.ConsumerSecret = "cs"; @@ -66,6 +71,7 @@ namespace DotNetOpenAuth.Test.ChannelElements { [TestMethod] public void HttpSignatureVerification() { SigningBindingElementBase target = new PlaintextSigningBindingElement(); + target.Channel = new TestChannel(); MessageReceivingEndpoint endpoint = new MessageReceivingEndpoint("http://localtest", HttpDeliveryMethods.GetRequest); ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint, Protocol.Default.Version); message.ConsumerSecret = "cs"; diff --git a/src/DotNetOpenAuth.Test/OAuth/OAuthCoordinator.cs b/src/DotNetOpenAuth.Test/OAuth/OAuthCoordinator.cs index ce548a9..5967a03 100644 --- a/src/DotNetOpenAuth.Test/OAuth/OAuthCoordinator.cs +++ b/src/DotNetOpenAuth.Test/OAuth/OAuthCoordinator.cs @@ -6,6 +6,7 @@ namespace DotNetOpenAuth.Test { using System; + using System.Diagnostics.Contracts; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.OAuth; @@ -26,8 +27,8 @@ namespace DotNetOpenAuth.Test { /// <param name="serviceProviderAction">The code path of the Service Provider.</param> internal OAuthCoordinator(ConsumerDescription consumerDescription, ServiceProviderDescription serviceDescription, Action<WebConsumer> consumerAction, Action<ServiceProvider> serviceProviderAction) : base(consumerAction, serviceProviderAction) { - ErrorUtilities.VerifyArgumentNotNull(consumerDescription, "consumerDescription"); - ErrorUtilities.VerifyArgumentNotNull(serviceDescription, "serviceDescription"); + Contract.Requires<ArgumentNullException>(consumerDescription != null); + Contract.Requires<ArgumentNullException>(serviceDescription != null); this.consumerDescription = consumerDescription; this.serviceDescription = serviceDescription; diff --git a/src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs b/src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs index 4ebdf74..7de2a8b 100644 --- a/src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs @@ -6,6 +6,7 @@ namespace DotNetOpenAuth.Test.OpenId { using System; + using System.Diagnostics.Contracts; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.OpenId; @@ -135,8 +136,8 @@ namespace DotNetOpenAuth.Test.OpenId { } private void ParameterizedAuthenticationTest(Protocol protocol, bool statelessRP, bool sharedAssociation, bool positive, bool immediate, bool tamper) { - ErrorUtilities.VerifyArgument(!statelessRP || !sharedAssociation, "The RP cannot be stateless while sharing an association with the OP."); - ErrorUtilities.VerifyArgument(positive || !tamper, "Cannot tamper with a negative response."); + Contract.Requires<ArgumentException>(!statelessRP || !sharedAssociation, "The RP cannot be stateless while sharing an association with the OP."); + Contract.Requires<ArgumentException>(positive || !tamper, "Cannot tamper with a negative response."); ProviderSecuritySettings securitySettings = new ProviderSecuritySettings(); Association association = sharedAssociation ? HmacShaAssociation.Create(protocol, protocol.Args.SignatureAlgorithm.Best, AssociationRelyingPartyType.Smart, securitySettings) : null; var coordinator = new OpenIdCoordinator( diff --git a/src/DotNetOpenAuth.Test/OpenId/ChannelElements/ExtensionsBindingElementTests.cs b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/ExtensionsBindingElementTests.cs index 5af1caf..29797dc 100644 --- a/src/DotNetOpenAuth.Test/OpenId/ChannelElements/ExtensionsBindingElementTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/ExtensionsBindingElementTests.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.Test.OpenId.ChannelElements { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.Linq; using System.Text.RegularExpressions; using DotNetOpenAuth.Messaging; @@ -167,7 +168,7 @@ namespace DotNetOpenAuth.Test.OpenId.ChannelElements { } private static void RegisterMockExtension(Channel channel) { - ErrorUtilities.VerifyArgumentNotNull(channel, "channel"); + Contract.Requires<ArgumentNullException>(channel != null); ExtensionTestUtilities.RegisterExtension(channel, MockOpenIdExtension.Factory); } @@ -178,7 +179,7 @@ namespace DotNetOpenAuth.Test.OpenId.ChannelElements { /// <param name="protocol">The protocol to construct the message with.</param> /// <returns>The message ready to send from OP to RP.</returns> private IndirectSignedResponse CreateResponseWithExtensions(Protocol protocol) { - ErrorUtilities.VerifyArgumentNotNull(protocol, "protocol"); + Contract.Requires<ArgumentNullException>(protocol != null); IndirectSignedResponse response = new IndirectSignedResponse(protocol.Version, RPUri); response.ProviderEndpoint = OPUri; diff --git a/src/DotNetOpenAuth.Test/OpenId/Extensions/AttributeExchange/AttributeRequestTests.cs b/src/DotNetOpenAuth.Test/OpenId/Extensions/AttributeExchange/AttributeRequestTests.cs index 228bda3..48b5727 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Extensions/AttributeExchange/AttributeRequestTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Extensions/AttributeExchange/AttributeRequestTests.cs @@ -25,7 +25,7 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions { new AttributeRequest(string.Empty); } - [TestMethod, ExpectedException(typeof(ArgumentNullException))] + [TestMethod, ExpectedException(typeof(ArgumentException))] public void CtorNullTypeUri() { new AttributeRequest(null); } diff --git a/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionTestUtilities.cs b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionTestUtilities.cs index 47c8ec4..334fc93 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionTestUtilities.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionTestUtilities.cs @@ -5,7 +5,9 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.Test.OpenId.Extensions { + using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.Linq; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId; @@ -71,7 +73,7 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions { } internal static void RegisterExtension(Channel channel, StandardOpenIdExtensionFactory.CreateDelegate extensionFactory) { - ErrorUtilities.VerifyArgumentNotNull(channel, "channel"); + Contract.Requires<ArgumentNullException>(channel != null); var factory = (OpenIdExtensionFactoryAggregator)channel.BindingElements.OfType<ExtensionsBindingElement>().Single().ExtensionFactory; factory.Factories.OfType<StandardOpenIdExtensionFactory>().Single().RegisterExtension(extensionFactory); diff --git a/src/DotNetOpenAuth.Test/OpenId/IdentifierTests.cs b/src/DotNetOpenAuth.Test/OpenId/IdentifierTests.cs index 3e599e9..cc02265 100644 --- a/src/DotNetOpenAuth.Test/OpenId/IdentifierTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/IdentifierTests.cs @@ -75,7 +75,7 @@ namespace DotNetOpenAuth.Test.OpenId { Assert.AreEqual(this.uri, ((UriIdentifier)id).Uri.AbsoluteUri); } - [TestMethod, ExpectedException(typeof(ArgumentNullException))] + [TestMethod, ExpectedException(typeof(ArgumentException))] public void ParseNull() { Identifier.Parse(null); } diff --git a/src/DotNetOpenAuth.Test/OpenId/Messages/IndirectSignedResponseTests.cs b/src/DotNetOpenAuth.Test/OpenId/Messages/IndirectSignedResponseTests.cs index e140b24..8b0937a 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Messages/IndirectSignedResponseTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Messages/IndirectSignedResponseTests.cs @@ -127,7 +127,7 @@ namespace DotNetOpenAuth.Test.OpenId.Messages { } } - [TestMethod, ExpectedException(typeof(ArgumentNullException))] + [TestMethod, ExpectedException(typeof(ArgumentException))] public void GetReturnToArgumentNullKey() { this.response.GetReturnToArgument(null); } diff --git a/src/DotNetOpenAuth.Test/OpenId/OpenIdCoordinator.cs b/src/DotNetOpenAuth.Test/OpenId/OpenIdCoordinator.cs index af9c5db..0f9d472 100644 --- a/src/DotNetOpenAuth.Test/OpenId/OpenIdCoordinator.cs +++ b/src/DotNetOpenAuth.Test/OpenId/OpenIdCoordinator.cs @@ -6,6 +6,7 @@ namespace DotNetOpenAuth.Test.OpenId { using System; + using System.Diagnostics.Contracts; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.OpenId; @@ -36,7 +37,7 @@ namespace DotNetOpenAuth.Test.OpenId { } private static Action<OpenIdRelyingParty> WrapAction(Action<OpenIdRelyingParty> action) { - ErrorUtilities.VerifyArgumentNotNull(action, "action"); + Contract.Requires<ArgumentNullException>(action != null); return rp => { action(rp); @@ -45,7 +46,7 @@ namespace DotNetOpenAuth.Test.OpenId { } private static Action<OpenIdProvider> WrapAction(Action<OpenIdProvider> action) { - ErrorUtilities.VerifyArgumentNotNull(action, "action"); + Contract.Requires<ArgumentNullException>(action != null); return op => { action(op); diff --git a/src/DotNetOpenAuth.Test/OpenId/Provider/OpenIdProviderTests.cs b/src/DotNetOpenAuth.Test/OpenId/Provider/OpenIdProviderTests.cs index 0a6cdcc..8528aa7 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Provider/OpenIdProviderTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Provider/OpenIdProviderTests.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.Test.OpenId.Provider { using System; using System.IO; + using System.Web; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId; using DotNetOpenAuth.OpenId.Extensions; @@ -74,6 +75,7 @@ namespace DotNetOpenAuth.Test.OpenId.Provider { /// </summary> [TestMethod, ExpectedException(typeof(InvalidOperationException))] public void GetRequestNoContext() { + HttpContext.Current = null; this.provider.GetRequest(); } diff --git a/src/DotNetOpenAuth.Test/OpenId/ProviderEndpointDescriptionTests.cs b/src/DotNetOpenAuth.Test/OpenId/ProviderEndpointDescriptionTests.cs index 005b8a0..089265f 100644 --- a/src/DotNetOpenAuth.Test/OpenId/ProviderEndpointDescriptionTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/ProviderEndpointDescriptionTests.cs @@ -29,7 +29,7 @@ namespace DotNetOpenAuth.Test.OpenId { this.se.IsExtensionSupported((Type)null); } - [TestMethod, ExpectedException(typeof(ArgumentNullException))] + [TestMethod, ExpectedException(typeof(ArgumentException))] public void IsExtensionSupportedNullString() { this.se.IsExtensionSupported((string)null); } diff --git a/src/DotNetOpenAuth.Test/OpenId/RelyingParty/OpenIdTextBoxTests.cs b/src/DotNetOpenAuth.Test/OpenId/RelyingParty/OpenIdTextBoxTests.cs new file mode 100644 index 0000000..67255e3 --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/RelyingParty/OpenIdTextBoxTests.cs @@ -0,0 +1,49 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdTextBoxTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.OpenId.RelyingParty { + using DotNetOpenAuth.OpenId.RelyingParty; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class OpenIdTextBoxTests : OpenIdTestBase { + /// <summary> + /// Verifies that the Text and Identifier properties interact correctly. + /// </summary> + [TestMethod] + public void IdentifierTextInteraction() { + var box = new OpenIdTextBox(); + Assert.AreEqual(string.Empty, box.Text); + Assert.IsNull(box.Identifier); + + box.Text = "=arnott"; + Assert.AreEqual("=arnott", box.Text); + Assert.AreEqual("=arnott", box.Identifier.ToString()); + + box.Identifier = "=bob"; + Assert.AreEqual("=bob", box.Text); + Assert.AreEqual("=bob", box.Identifier.ToString()); + + box.Text = string.Empty; + Assert.AreEqual(string.Empty, box.Text); + Assert.IsNull(box.Identifier); + + box.Text = null; + Assert.AreEqual(string.Empty, box.Text); + Assert.IsNull(box.Identifier); + + // Invalid identifier case + box.Text = "/"; + Assert.AreEqual("/", box.Text); + Assert.IsNull(box.Identifier); + + // blank out the invalid case + box.Identifier = null; + Assert.AreEqual(string.Empty, box.Text); + Assert.IsNull(box.Identifier); + } + } +} diff --git a/src/DotNetOpenAuth.Test/OpenId/RelyingParty/PositiveAuthenticationResponseTests.cs b/src/DotNetOpenAuth.Test/OpenId/RelyingParty/PositiveAuthenticationResponseTests.cs index 083b988..701bcae 100644 --- a/src/DotNetOpenAuth.Test/OpenId/RelyingParty/PositiveAuthenticationResponseTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/RelyingParty/PositiveAuthenticationResponseTests.cs @@ -38,7 +38,7 @@ namespace DotNetOpenAuth.Test.OpenId.RelyingParty { Assert.AreEqual(AuthenticationStatus.Authenticated, authResponse.Status); Assert.IsNull(authResponse.Exception); Assert.AreEqual<string>(assertion.ClaimedIdentifier, authResponse.ClaimedIdentifier); - Assert.AreEqual<string>(authResponseAccessor.endpoint.FriendlyIdentifierForDisplay, authResponse.FriendlyIdentifierForDisplay); + Assert.AreEqual<string>(authResponse.Endpoint.FriendlyIdentifierForDisplay, authResponse.FriendlyIdentifierForDisplay); Assert.AreSame(extension, authResponse.GetUntrustedExtension(typeof(ClaimsResponse))); Assert.AreSame(extension, authResponse.GetUntrustedExtension<ClaimsResponse>()); Assert.IsNull(authResponse.GetCallbackArgument("a")); diff --git a/src/DotNetOpenAuth.Test/OpenId/RelyingParty/ServiceEndpointTests.cs b/src/DotNetOpenAuth.Test/OpenId/RelyingParty/ServiceEndpointTests.cs index bd09bc9..ff15aa3 100644 --- a/src/DotNetOpenAuth.Test/OpenId/RelyingParty/ServiceEndpointTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/RelyingParty/ServiceEndpointTests.cs @@ -138,14 +138,14 @@ namespace DotNetOpenAuth.Test.OpenId.RelyingParty { }; ServiceEndpoint se; - // strip of protocol and fragment + // strip of protocol, port, query and fragment se = ServiceEndpoint.CreateForClaimedIdentifier( - "http://someprovider.somedomain.com:79/someuser#frag", + "http://someprovider.somedomain.com:79/someuser?query#frag", localId, new ProviderEndpointDescription(providerEndpoint, serviceTypeUris), null, null); - Assert.AreEqual("someprovider.somedomain.com:79/someuser", se.FriendlyIdentifierForDisplay); + Assert.AreEqual("someprovider.somedomain.com/someuser", se.FriendlyIdentifierForDisplay); // unescape characters Uri foreignUri = new Uri("http://server崎/村"); @@ -180,7 +180,7 @@ namespace DotNetOpenAuth.Test.OpenId.RelyingParty { Assert.IsFalse(se.IsTypeUriPresent("http://someother")); } - [TestMethod, ExpectedException(typeof(ArgumentNullException))] + [TestMethod, ExpectedException(typeof(ArgumentException))] public void IsTypeUriPresentNull() { ServiceEndpoint se = ServiceEndpoint.CreateForClaimedIdentifier(this.claimedXri, this.userSuppliedXri, this.localId, new ProviderEndpointDescription(this.providerEndpoint, this.v20TypeUris), this.servicePriority, this.uriPriority); se.IsTypeUriPresent(null); diff --git a/src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs b/src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs index f1823e6..5a5182f 100644 --- a/src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs @@ -31,7 +31,7 @@ namespace DotNetOpenAuth.Test.OpenId { new UriIdentifier((Uri)null); } - [TestMethod, ExpectedException(typeof(ArgumentNullException))] + [TestMethod, ExpectedException(typeof(ArgumentException))] public void CtorNullString() { new UriIdentifier((string)null); } diff --git a/src/DotNetOpenAuth.Test/OpenId/XriIdentifierTests.cs b/src/DotNetOpenAuth.Test/OpenId/XriIdentifierTests.cs index 87ecd4b..46427bb 100644 --- a/src/DotNetOpenAuth.Test/OpenId/XriIdentifierTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/XriIdentifierTests.cs @@ -22,7 +22,7 @@ namespace DotNetOpenAuth.Test.OpenId { base.SetUp(); } - [TestMethod, ExpectedException(typeof(ArgumentNullException))] + [TestMethod, ExpectedException(typeof(ArgumentException))] public void CtorNull() { new XriIdentifier(null); } diff --git a/src/DotNetOpenAuth.Test/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.Test/Properties/AssemblyInfo.cs index d6fd020..4744bfe 100644 --- a/src/DotNetOpenAuth.Test/Properties/AssemblyInfo.cs +++ b/src/DotNetOpenAuth.Test/Properties/AssemblyInfo.cs @@ -29,4 +29,4 @@ using System.Runtime.InteropServices; // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("aef0bb13-b79c-4854-a69a-de58b8feb5d1")] -[assembly: ContractVerification(false)]
\ No newline at end of file +[assembly: ContractVerification(true)]
\ No newline at end of file diff --git a/src/DotNetOpenAuth.Test/TestBase.cs b/src/DotNetOpenAuth.Test/TestBase.cs index f9db40c..c6508f6 100644 --- a/src/DotNetOpenAuth.Test/TestBase.cs +++ b/src/DotNetOpenAuth.Test/TestBase.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.Test { using System.IO; using System.Reflection; + using System.Web; using DotNetOpenAuth.Messaging.Reflection; using DotNetOpenAuth.OAuth.Messages; using log4net; @@ -48,6 +49,7 @@ namespace DotNetOpenAuth.Test { log4net.Config.XmlConfigurator.Configure(Assembly.GetExecutingAssembly().GetManifestResourceStream("DotNetOpenAuth.Test.Logging.config")); MessageBase.LowSecurityMode = true; this.messageDescriptions = new MessageDescriptionCollection(); + SetMockHttpContext(); } /// <summary> @@ -58,6 +60,16 @@ namespace DotNetOpenAuth.Test { log4net.LogManager.Shutdown(); } + /// <summary> + /// Sets HttpContext.Current to some empty (but non-null!) value. + /// </summary> + protected internal static void SetMockHttpContext() { + HttpContext.Current = new HttpContext( + new HttpRequest("mock", "http://mock", "mock"), + new HttpResponse(new StringWriter())); + } + +#pragma warning disable 0618 protected internal static void SuspendLogging() { LogManager.GetLoggerRepository().Threshold = LogManager.GetLoggerRepository().LevelMap["OFF"]; } @@ -65,5 +77,6 @@ namespace DotNetOpenAuth.Test { protected internal static void ResumeLogging() { LogManager.GetLoggerRepository().Threshold = LogManager.GetLoggerRepository().LevelMap["ALL"]; } +#pragma warning restore 0618 } } diff --git a/src/DotNetOpenAuth.sln b/src/DotNetOpenAuth.sln index e6aff81..569a7bc 100644 --- a/src/DotNetOpenAuth.sln +++ b/src/DotNetOpenAuth.sln @@ -7,6 +7,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.Test", "DotN EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{20B5E173-C3C4-49F8-BD25-E69044075B4D}" ProjectSection(SolutionItems) = preProject + ..\build.proj = ..\build.proj DotNetOpenAuth.vsmdi = DotNetOpenAuth.vsmdi ..\LICENSE.txt = ..\LICENSE.txt LocalTestRun.testrunconfig = LocalTestRun.testrunconfig @@ -78,7 +79,7 @@ Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "InfoCardRelyingParty", "..\ Release.AspNetCompiler.ForceOverwrite = "true" Release.AspNetCompiler.FixedNames = "false" Release.AspNetCompiler.Debug = "False" - VWDPort = "4490" + VWDPort = "59719" DefaultWebSiteLanguage = "Visual Basic" EndProjectSection EndProject @@ -135,7 +136,7 @@ Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "OAuthConsumer", "..\samples Release.AspNetCompiler.ForceOverwrite = "true" Release.AspNetCompiler.FixedNames = "false" Release.AspNetCompiler.Debug = "False" - VWDPort = "10335" + VWDPort = "59721" EndProjectSection EndProject Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "OAuthServiceProvider", "..\samples\OAuthServiceProvider", "{7ADCCD5C-AC2B-4340-9410-FE3A31A48191}" @@ -164,6 +165,17 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenIdOfflineProvider", ".. EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{E9ED920D-1F83-48C0-9A4B-09CCE505FE6D}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Project Templates", "Project Templates", "{B9EB8729-4B54-4453-B089-FE6761BA3057}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebFormsRelyingParty", "..\projecttemplates\WebFormsRelyingParty\WebFormsRelyingParty.csproj", "{A78F8FC6-7B03-4230-BE41-761E400D6810}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RelyingPartyLogic", "..\projecttemplates\RelyingPartyLogic\RelyingPartyLogic.csproj", "{17932639-1F50-48AF-B0A5-E2BF832F82CC}" + ProjectSection(ProjectDependencies) = postProject + {2B4261AC-25AC-4B8D-B459-1C42B6B1401D} = {2B4261AC-25AC-4B8D-B459-1C42B6B1401D} + EndProjectSection +EndProject +Project("{C8D11400-126E-41CD-887F-60BD40844F9E}") = "RelyingPartyDatabase", "..\projecttemplates\RelyingPartyDatabase\RelyingPartyDatabase.dbproj", "{2B4261AC-25AC-4B8D-B459-1C42B6B1401D}" +EndProject Global GlobalSection(TestCaseManagementSettings) = postSolution CategoryFile = DotNetOpenAuth.vsmdi @@ -258,6 +270,27 @@ Global {5C65603B-235F-47E6-B536-06385C60DE7F}.Debug|Any CPU.Build.0 = Debug|Any CPU {5C65603B-235F-47E6-B536-06385C60DE7F}.Release|Any CPU.ActiveCfg = Release|Any CPU {5C65603B-235F-47E6-B536-06385C60DE7F}.Release|Any CPU.Build.0 = Release|Any CPU + {A78F8FC6-7B03-4230-BE41-761E400D6810}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {A78F8FC6-7B03-4230-BE41-761E400D6810}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {A78F8FC6-7B03-4230-BE41-761E400D6810}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A78F8FC6-7B03-4230-BE41-761E400D6810}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A78F8FC6-7B03-4230-BE41-761E400D6810}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A78F8FC6-7B03-4230-BE41-761E400D6810}.Release|Any CPU.Build.0 = Release|Any CPU + {17932639-1F50-48AF-B0A5-E2BF832F82CC}.CodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU + {17932639-1F50-48AF-B0A5-E2BF832F82CC}.CodeAnalysis|Any CPU.Build.0 = Release|Any CPU + {17932639-1F50-48AF-B0A5-E2BF832F82CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17932639-1F50-48AF-B0A5-E2BF832F82CC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17932639-1F50-48AF-B0A5-E2BF832F82CC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17932639-1F50-48AF-B0A5-E2BF832F82CC}.Release|Any CPU.Build.0 = Release|Any CPU + {2B4261AC-25AC-4B8D-B459-1C42B6B1401D}.CodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU + {2B4261AC-25AC-4B8D-B459-1C42B6B1401D}.CodeAnalysis|Any CPU.Build.0 = Debug|Any CPU + {2B4261AC-25AC-4B8D-B459-1C42B6B1401D}.CodeAnalysis|Any CPU.Deploy.0 = Debug|Any CPU + {2B4261AC-25AC-4B8D-B459-1C42B6B1401D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2B4261AC-25AC-4B8D-B459-1C42B6B1401D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2B4261AC-25AC-4B8D-B459-1C42B6B1401D}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {2B4261AC-25AC-4B8D-B459-1C42B6B1401D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2B4261AC-25AC-4B8D-B459-1C42B6B1401D}.Release|Any CPU.Build.0 = Release|Any CPU + {2B4261AC-25AC-4B8D-B459-1C42B6B1401D}.Release|Any CPU.Deploy.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -278,5 +311,8 @@ Global {9ADBE36D-9960-48F6-82E9-B4AC559E9AC3} = {1E2CBAA5-60A3-4AED-912E-541F5753CDC6} {7ADCCD5C-AC2B-4340-9410-FE3A31A48191} = {1E2CBAA5-60A3-4AED-912E-541F5753CDC6} {5C65603B-235F-47E6-B536-06385C60DE7F} = {E9ED920D-1F83-48C0-9A4B-09CCE505FE6D} + {A78F8FC6-7B03-4230-BE41-761E400D6810} = {B9EB8729-4B54-4453-B089-FE6761BA3057} + {17932639-1F50-48AF-B0A5-E2BF832F82CC} = {B9EB8729-4B54-4453-B089-FE6761BA3057} + {2B4261AC-25AC-4B8D-B459-1C42B6B1401D} = {B9EB8729-4B54-4453-B089-FE6761BA3057} EndGlobalSection EndGlobal diff --git a/src/DotNetOpenAuth.vsmdi b/src/DotNetOpenAuth.vsmdi index a404b8e..14dd27e 100644 --- a/src/DotNetOpenAuth.vsmdi +++ b/src/DotNetOpenAuth.vsmdi @@ -78,9 +78,9 @@ <TestLink id="054484ce-12c5-83ad-49a4-b241cd81557d" name="ClaimedIdentifier" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="e8337858-a320-8aad-51aa-402e65a90b75" name="ReplayDetectionTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="507cd1b6-1010-0bca-cf7f-f96e3f4f6c6c" name="QueryBeforeSettingUrl" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="ee7a04ba-0419-e08f-b838-01ec0f2a838e" name="UnsolicitedAssertion" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="1dcbaac6-0b11-8d8f-50d7-237574abbab1" name="ToDictionaryWithSkippedNullKey" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="715dcbdd-28f5-3c33-7d88-e0a1b648d89a" name="CreateRequestDumbMode" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="a1ff4ada-fe5d-d2f3-b7fb-8e72db02b3c3" name="Full" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="85a71d28-5f2f-75ce-9008-94982438bb5f" name="EqualityTests" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f334cc44-b2d0-2d67-358a-532def3bee80" name="ContainsKeyValuePair" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="fa05cc5f-2aaf-da22-ff52-caf1c3c6bb08" name="InsecureIdentifiersRejectedWithRequireSsl" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -91,7 +91,7 @@ <TestLink id="a260d196-066f-b0ae-a40e-fb9d962b28a4" name="XrdsDirectDiscovery_20" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="13acd546-c82e-324c-220d-34f42a6d705e" name="DeserializeSimple" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="b56cdf04-0d29-8b13-468c-fb4b4258c619" name="CtorNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="2c2b48d0-8009-e7e0-9ff4-34f9973f59da" name="EqualsTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="d474830d-3636-522c-1564-1b83e7a844d3" name="EmptyLine" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f44fb549-fc8a-7469-6eed-09d9f86cebff" name="SendDirectMessageResponse" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="8fc08a6d-6dcf-6256-42ff-073d4e4b6859" name="RequireDirectedIdentity" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f20bd439-e277-dc27-4ec4-5d5949d1c6bf" name="RequestUsingAuthorizationHeaderScattered" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -118,22 +118,23 @@ <TestLink id="db8d66cc-8206-57cc-0ce5-c8117813d77c" name="UnifyExtensionsasSregFromSchemaOpenIdNet" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="ef8a2274-4e58-0dde-4c5c-7f286865fc3a" name="SendReplayProtectedMessageSetsNonce" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="63e5025b-5ccf-5f13-6e05-d1e44502a6e9" name="RequestBadPreferredScheme" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="decb3fef-ef61-6794-5bc6-f7ff722a146e" name="EqualsTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="2eb7c3dd-08aa-bfc0-edc8-e14bd82f7507" name="IdentifierTextInteraction" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="0d99e0a9-295e-08a6-bc31-2abb79c00ff8" name="IsReturnUrlDiscoverableRequireSsl" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f41ce7ab-5500-7eea-ab4d-8c646bffff23" name="HttpSchemePrepended" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="55b078e4-3933-d4e0-1151-a0a61321638e" name="ReadFromRequestAuthorization" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="38239ff0-1dfd-1116-55df-2790243dc768" name="IsValid" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="04be6602-31a2-f4ae-8fdb-b9ad2ac370be" name="PrepareMessageForReceiving" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="2c2b48d0-8009-e7e0-9ff4-34f9973f59da" name="EqualsTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="5dd2e6c9-ff0f-48de-3a1a-cbab61370843" name="SetCountNegative" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="182203f3-5a16-b736-ea8c-b59f6bf7df66" name="InvalidRealmTwoWildcards2" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="4fd5baa2-8f39-8bf6-db8f-aa92592bfc06" name="CtorRequest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="d474830d-3636-522c-1564-1b83e7a844d3" name="EmptyLine" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="385c302d-b546-c164-2a59-2e35f75d7d60" name="RemoveStructDeclaredProperty" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="870cce9d-5b17-953c-028f-827ec8b56da2" name="GetInvalidMessageType" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="97f0277a-86e6-5b5a-8419-c5253cabf2e0" name="UserAuthorizationUriTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="643add47-e9f3-20b8-d8e0-69e3f8926d33" name="CreateRequestsWithEndpointFilter" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="d66a3b7a-1738-f6b3-aed1-e9bc80734ae9" name="CtorNullString" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f3f84a10-317f-817a-1988-fddc10b75c20" name="AddTwoAttributes" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="5e2555a0-c07a-6488-c0e9-40ececd5021f" name="Serbian" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="e03f0038-5bb7-92f2-87a7-00a7d2c31a77" name="MessageExpirationWithoutTamperResistance" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="1f3ea08b-9880-635f-368f-9fcd3e25f3cd" name="ReadFromRequestNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="9104f36f-6652-dcbb-a8ae-0d6fc34d76ed" name="AddCallbackArgumentClearsPreviousArgument" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -228,18 +229,19 @@ <TestLink id="32604ca2-2577-9c33-f778-ff7e4c985ce5" name="RequestTokenUriWithOAuthParametersTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="fdf439d0-3b74-4d32-d395-d5a2559ed88b" name="Ctor" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="3df1f62b-4fb4-d399-cf7f-40b72001d9d6" name="CtorUnsolicited" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="90f06a50-7b81-05ec-3dc0-7b3e8ade8cfa" name="NormalizeCase" storage="..\bin\debug\dotnetopenauth.test.dll" enabled="false" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="7c688207-e58a-86ed-90d0-687e28ee7e38" name="MultiPartPostAscii" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="8b11aa63-4c0f-41ff-f70c-882aacf939fe" name="CtorCountNegative" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="83271647-7da8-70b1-48a3-f1253a636088" name="IsExtensionSupportedEmptyString" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="b671ea40-3b8c-58d5-7788-f776810c49be" name="UnicodeTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="5435ab79-de25-e2fc-0b2d-b05d5686d27d" name="IsUrlWithinRealmTests" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="501fa941-c1ac-d4ef-56e7-46827788b571" name="GetRequestNoContext" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="01e33554-07cc-ff90-46f8-7d0ca036c9f6" name="ToDictionaryNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="3aa4e498-fd14-8274-22da-895436c1659e" name="AssociateUnencrypted" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="33dd2f79-cd69-f1c0-0e47-f7a17b5a8a8b" name="MultiPartPostMultiByteCharacters" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="0215f125-3936-484e-a8d0-d940d85bbc27" name="AppendQueryArgsNullDictionary" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="7c048c58-c456-3406-995f-adb742cc2501" name="DeserializeInvalidMessage" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="704a32d0-3f50-d462-f767-fd9cf1981b7f" name="ProviderVersion" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f362baf3-da5b-1b8c-39ae-7c9b2051270a" name="AuthenticationTimeUtcSetUtc" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="decb3fef-ef61-6794-5bc6-f7ff722a146e" name="EqualsTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="3bb818b4-5423-ad91-8cd9-8606ec85d2cb" name="ReadFromRequestAuthorizationScattered" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="30f3c12b-e510-de63-5acd-ae8e32866592" name="CreateQueryString" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="9302541c-9c30-9ce7-66db-348ee4e9f6ee" name="UnifyExtensionsAsSregWithSreg" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -271,7 +273,7 @@ <TestLink id="ee052b02-681f-2303-3cc6-37f7b2319579" name="RequireAssociation" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="fb0e478e-0f55-b257-75fe-2ab60b57292e" name="SendInvalidMessageTransport" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="a4aa113a-57b5-a52c-c4e3-f70d6702badb" name="Default" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="1da7c479-bf01-2d12-8645-b4f7007dcfec" name="LanguagePreferenceEncoding" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="ee7a04ba-0419-e08f-b838-01ec0f2a838e" name="UnsolicitedAssertion" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="547cfee6-bbb4-6138-a114-fc0eb6cc94f6" name="PrivateAssociationTampered" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="5803e93d-e256-86ba-e10e-499d2f813c6d" name="Trivial" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="beb086e9-5eb7-fb8f-480a-70ede9efd70d" name="CreateRequests" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -309,6 +311,7 @@ <TestLink id="47e8fae9-542d-1ebb-e17c-568cf9594539" name="RelativeUriDecodeFails" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f3af5fd8-f661-dc4f-4539-947b081a8b54" name="ReceivedReplayProtectedMessageJustOnce" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="a14ddf08-796b-6cf1-a9bf-856dd50520fa" name="RequiredProtection" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="3aa4e498-fd14-8274-22da-895436c1659e" name="AssociateUnencrypted" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="5b4fee50-7c15-8c6b-3398-c82279646e5f" name="RequiredOptionalLists" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="59295023-d248-e9c4-68b9-65f6ea38490c" name="VerifyArgumentNotNullDoesNotThrow" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="e137b84a-d2a7-9af6-d15d-a92417668ccf" name="Transport" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -357,6 +360,7 @@ <TestLink id="809afd59-8f10-ce37-6630-06b59351a05a" name="CommonProperties" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="02333934-cfea-2fb6-5e08-7a24be050f44" name="CreateRequestsOnNonOpenID" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="5298ecb0-bcad-9022-8b93-87793eb2c669" name="UnsolicitedDelegatingIdentifierRejection" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="b97c4a66-a832-401c-a98b-6342ad7bcdcf" name="LanguagePreferenceEncodingDecoding" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="068dcefa-8f2b-52c3-fe79-576c84c5648b" name="CtorBlank" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="315b5857-7697-8222-f94c-f6f10d539491" name="BaseSignatureStringTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="cd219db4-4f6e-8ff4-f957-c8428d38c118" name="HttpSignatureGeneration" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -365,6 +369,8 @@ <TestLink id="ab653060-5bec-3dc6-78ee-a5ef7d393b1d" name="AddPolicyMultipleTimes" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="2599d559-d036-5dd2-0b5b-fb229c3bf486" name="InvalidRealmBadWildcard2" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="3744e1f1-6be1-f27f-78e9-5410d356ccf4" name="Ctor" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="f341c47f-ac23-2fc0-fcf5-c999fe8d2611" name="SetBodyToByteStream" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="643d722c-2c2b-fbd8-a499-5a852ef14dc7" name="PrepareMessageForSending" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="79c0d33a-f7f2-fd69-1b4d-57ee3ece2cca" name="EqualityTests" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="937b85f4-1ef3-84d1-a567-8bba079a33a9" name="Properties" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="53bc1962-e7c2-04b6-cafa-0f6fde7592a9" name="ReadFromRequestNoContext" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -383,7 +389,7 @@ <TestLink id="5271f941-565f-5977-6884-82cef09161db" name="ParseEndUserSuppliedXriIdentifer" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="7cf52613-a212-8a0f-843f-37f08740c125" name="SpreadSregToAxNoOpIfOPSupportsSreg" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="7c8eac5a-0455-e038-0e9a-10e59d459452" name="CtorUriHttpSchemeSecure" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="f69f1c0c-e258-95fb-4fcb-ad14bfc40e3c" name="Discover" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="bdba0004-be80-f5c1-1aae-487db09bdf04" name="GetReturnToArgumentDoesNotReturnExtraArgs" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="ccfda025-cb1a-a2ff-78bd-5e9af885ae0b" name="ToDictionary" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="64142858-d52e-be06-d11f-6be326c6176b" name="RespondTwoValues" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="03b47440-3d09-ab28-97f1-39809f5703b6" name="NormalizeCase" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -405,12 +411,12 @@ <TestLink id="e2b1ae2a-8f30-b6b3-bca6-ef28fc5a0175" name="ClaimedIdAndLocalIdSpecifiedIsValid" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="58d69d1e-3bd2-3379-0af1-188f9cff2dd0" name="IsTypeUriPresentEmpty" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="495dd486-08dd-d365-7a84-67d96fef8460" name="SendIndirectedUndirectedMessage" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="24fb403f-be35-278e-9beb-e11177f2cd1e" name="FormDataSerializeMatchesLength" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="7cdabb8a-aefa-e90e-c32e-047404b64c2d" name="SerializeTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="b4b00582-dcc9-7672-0c02-52432b074a92" name="GetNullType" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f6979feb-7016-4e2b-14e2-e6c2c392419f" name="RemoveByKeyValuePair" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="58df167c-cf19-351c-cb09-5c52ae9f97be" name="DeserializeNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="a778f331-f14e-9d6e-f942-a023423540f6" name="Ctor" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="a1ff4ada-fe5d-d2f3-b7fb-8e72db02b3c3" name="Full" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="90f06a50-7b81-05ec-3dc0-7b3e8ade8cfa" name="NormalizeCase" storage="..\bin\debug\dotnetopenauth.test.dll" enabled="false" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="be00d3ef-f24d-eb8a-d251-4d691736ee6f" name="AddAttributeRequestNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="593e1d86-a6c2-c937-a1b4-6d25a595a1f1" name="EnumerableCacheCurrentThrowsBefore" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="030ac3cf-cfb6-ca47-f822-ec1d2979f0b3" name="Defaults" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -427,6 +433,7 @@ <TestLink id="671ddaf5-238d-a517-b0f3-d79bd591a396" name="EmptyMailAddress" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="d8118997-ecf7-7130-f068-5e2bc867786d" name="SerializeNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="e9c2087b-1c52-5bb9-bf4e-9046cf281e36" name="DiscoverRequireSslWithInsecureRedirect" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="f69f1c0c-e258-95fb-4fcb-ad14bfc40e3c" name="Discover" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="25e2c593-2e69-6215-90c0-67f269939865" name="CtorEmptyTypeUri" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="e1e9dde8-30e6-6ce0-d5a6-4e22e0347ac4" name="UnifyExtensionsAsSregWithAX" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f50a0bdb-380e-30f6-492a-a6dd9664d0f0" name="ExtensionOnlyChannelLevel" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -460,7 +467,7 @@ <TestLink id="b2b54c72-1d26-8c28-ebf5-7a5a4beeec43" name="VerifyNonZeroLengthOnNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="9684f7bf-cdda-a2c5-0822-29cb0add3835" name="ResponseNonceGetter" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="c4001e1c-75ad-236b-284f-318905d2bc3a" name="CreateRequestOnNonOpenID" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="b58e4791-68c0-1bc0-2e48-e1351459ee46" name="UserSetupUrlSetForV1Immediate" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="a66588b5-989d-8f8e-4994-4a066220516b" name="FileSerializeMatchesLength" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="c0d7cfcc-4f7e-e7df-3de2-b578c4c3d6ee" name="SpreadSregToAxMultipleSchemas" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="32e95494-d0bb-cfc7-a8d6-652f8816c6b4" name="ReadFromResponse" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f967c0af-c04c-d156-4faf-8978bfcab5d7" name="RequiredNullableStruct" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -474,7 +481,7 @@ <TestLink id="439c8c16-2ba5-eb3b-b631-ce50ec48eba0" name="CtorNullMember" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="a6295302-c78f-4122-ce88-94fc30980262" name="CtorStringNoSchemeSecure" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f12bf351-584c-bc51-c315-a67f1076927c" name="ReturnToDoesNotMatchRecipient" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="bdba0004-be80-f5c1-1aae-487db09bdf04" name="GetReturnToArgumentDoesNotReturnExtraArgs" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="b4b00582-dcc9-7672-0c02-52432b074a92" name="GetNullType" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f1e1aa37-c712-6096-22fa-394008f0820a" name="CtorNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="cb48421f-f4ff-3994-3abc-4be35f8bfd99" name="AssociateQuietlyFailsAfterHttpError" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="660ad25a-b02b-1b17-7d6e-3af3303fa7bc" name="ModeEncoding" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -487,7 +494,7 @@ <TestLink id="90d3c411-8895-a07f-7a21-258b9d43c5b2" name="InvalidMessageNoNonceReceivedTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="121983e3-1336-70cb-8d2a-498629e92bec" name="GetReturnToArgumentNullKey" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="5e0c892d-7ad8-6d56-1f1d-2fb6236670d6" name="CtorDefault" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="643d722c-2c2b-fbd8-a499-5a852ef14dc7" name="PrepareMessageForSending" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="b58e4791-68c0-1bc0-2e48-e1351459ee46" name="UserSetupUrlSetForV1Immediate" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="c23e762d-4162-cb9e-47b3-455a568b5072" name="SendIndirectMessageFormPostEmptyRecipient" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f70b368e-da33-bc64-6096-1b467d49a9d4" name="NonIdentityRequest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="46877579-ba4c-c30c-38c4-9c6ad3922390" name="InsufficientlyProtectedMessageReceived" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> diff --git a/src/DotNetOpenAuth/ComponentModel/SuggestedStringsConverter.cs b/src/DotNetOpenAuth/ComponentModel/SuggestedStringsConverter.cs index 1c8c555..864d001 100644 --- a/src/DotNetOpenAuth/ComponentModel/SuggestedStringsConverter.cs +++ b/src/DotNetOpenAuth/ComponentModel/SuggestedStringsConverter.cs @@ -16,6 +16,7 @@ namespace DotNetOpenAuth.ComponentModel { /// A type that generates suggested strings for Intellisense, /// but doesn't actually convert between strings and other types. /// </summary> + [ContractClass(typeof(SuggestedStringsConverterContract))] public abstract class SuggestedStringsConverter : ConverterBase<string> { /// <summary> /// Initializes a new instance of the <see cref="SuggestedStringsConverter"/> class. @@ -35,6 +36,9 @@ namespace DotNetOpenAuth.ComponentModel { /// <param name="type">The type to reflect over.</param> /// <returns>A collection of values.</returns> internal static ICollection GetStandardValuesForCacheShared(Type type) { + Contract.Requires<ArgumentNullException>(type != null); + Contract.Ensures(Contract.Result<ICollection>() != null); + var fields = from field in type.GetFields(BindingFlags.Static | BindingFlags.Public) select field.GetValue(null); var properties = from prop in type.GetProperties(BindingFlags.Static | BindingFlags.Public) diff --git a/src/DotNetOpenAuth/ComponentModel/SuggestedStringsConverterContract.cs b/src/DotNetOpenAuth/ComponentModel/SuggestedStringsConverterContract.cs new file mode 100644 index 0000000..1573208 --- /dev/null +++ b/src/DotNetOpenAuth/ComponentModel/SuggestedStringsConverterContract.cs @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------- +// <copyright file="SuggestedStringsConverterContract.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.ComponentModel { + using System; + using System.Collections; + using System.ComponentModel.Design.Serialization; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Reflection; + + /// <summary> + /// Contract class for the <see cref="SuggestedStringsConverter"/> class. + /// </summary> + [ContractClassFor(typeof(SuggestedStringsConverter))] + internal abstract class SuggestedStringsConverterContract : SuggestedStringsConverter { + /// <summary> + /// Gets the type to reflect over for the well known values. + /// </summary> + protected override Type WellKnownValuesType { + get { + Contract.Ensures(Contract.Result<Type>() != null); + throw new NotImplementedException(); + } + } + } +} diff --git a/src/DotNetOpenAuth/ComponentModel/UriConverter.cs b/src/DotNetOpenAuth/ComponentModel/UriConverter.cs index cf8dde3..5e7c22b 100644 --- a/src/DotNetOpenAuth/ComponentModel/UriConverter.cs +++ b/src/DotNetOpenAuth/ComponentModel/UriConverter.cs @@ -101,9 +101,13 @@ namespace DotNetOpenAuth.ComponentModel { protected override ICollection GetStandardValuesForCache() { if (this.WellKnownValuesType != null) { var fields = from field in this.WellKnownValuesType.GetFields(BindingFlags.Static | BindingFlags.Public) - select new Uri((string)field.GetValue(null)); + let value = (string)field.GetValue(null) + where value != null + select new Uri(value); var properties = from prop in this.WellKnownValuesType.GetProperties(BindingFlags.Static | BindingFlags.Public) - select new Uri((string)prop.GetValue(null, null)); + let value = (string)prop.GetValue(null, null) + where value != null + select new Uri(value); return (fields.Concat(properties)).ToArray(); } else { return new Uri[0]; diff --git a/src/DotNetOpenAuth/Configuration/AssociationTypeCollection.cs b/src/DotNetOpenAuth/Configuration/AssociationTypeCollection.cs index 56c7389..881fcdb 100644 --- a/src/DotNetOpenAuth/Configuration/AssociationTypeCollection.cs +++ b/src/DotNetOpenAuth/Configuration/AssociationTypeCollection.cs @@ -54,8 +54,7 @@ namespace DotNetOpenAuth.Configuration { /// An <see cref="T:System.Object"/> that acts as the key for the specified <see cref="T:System.Configuration.ConfigurationElement"/>. /// </returns> protected override object GetElementKey(ConfigurationElement element) { - Contract.Assume(element != null); // this should be Contract.Requires in base class. - return ((AssociationTypeElement)element).AssociationType; + return ((AssociationTypeElement)element).AssociationType ?? string.Empty; } } } diff --git a/src/DotNetOpenAuth/Configuration/HostNameOrRegexCollection.cs b/src/DotNetOpenAuth/Configuration/HostNameOrRegexCollection.cs index 88a1615..c7d963b 100644 --- a/src/DotNetOpenAuth/Configuration/HostNameOrRegexCollection.cs +++ b/src/DotNetOpenAuth/Configuration/HostNameOrRegexCollection.cs @@ -64,7 +64,7 @@ namespace DotNetOpenAuth.Configuration { /// </returns> protected override object GetElementKey(ConfigurationElement element) { Contract.Assume(element != null); // this should be Contract.Requires in base class. - return ((HostNameElement)element).Name; + return ((HostNameElement)element).Name ?? string.Empty; } } } diff --git a/src/DotNetOpenAuth/Configuration/OpenIdElement.cs b/src/DotNetOpenAuth/Configuration/OpenIdElement.cs index 58a8276..404b2f6 100644 --- a/src/DotNetOpenAuth/Configuration/OpenIdElement.cs +++ b/src/DotNetOpenAuth/Configuration/OpenIdElement.cs @@ -63,8 +63,17 @@ namespace DotNetOpenAuth.Configuration { [ConfigurationProperty(MaxAuthenticationTimePropertyName, DefaultValue = "0:05")] // 5 minutes [PositiveTimeSpanValidator] internal TimeSpan MaxAuthenticationTime { - get { return (TimeSpan)this[MaxAuthenticationTimePropertyName]; } - set { this[MaxAuthenticationTimePropertyName] = value; } + get { + Contract.Ensures(Contract.Result<TimeSpan>() > TimeSpan.Zero); + TimeSpan result = (TimeSpan)this[MaxAuthenticationTimePropertyName]; + Contract.Assume(result > TimeSpan.Zero); // our PositiveTimeSpanValidator should take care of this + return result; + } + + set { + Contract.Requires<ArgumentOutOfRangeException>(value > TimeSpan.Zero); + this[MaxAuthenticationTimePropertyName] = value; + } } /// <summary> diff --git a/src/DotNetOpenAuth/Configuration/TypeConfigurationCollection.cs b/src/DotNetOpenAuth/Configuration/TypeConfigurationCollection.cs index d928c87..000cb6a 100644 --- a/src/DotNetOpenAuth/Configuration/TypeConfigurationCollection.cs +++ b/src/DotNetOpenAuth/Configuration/TypeConfigurationCollection.cs @@ -18,7 +18,8 @@ namespace DotNetOpenAuth.Configuration { /// </summary> /// <typeparam name="T">The type that all types specified in the elements must derive from.</typeparam> [ContractVerification(true)] - internal class TypeConfigurationCollection<T> : ConfigurationElementCollection { + internal class TypeConfigurationCollection<T> : ConfigurationElementCollection + where T : class { /// <summary> /// Initializes a new instance of the TypeConfigurationCollection class. /// </summary> @@ -30,8 +31,7 @@ namespace DotNetOpenAuth.Configuration { /// </summary> /// <param name="elements">The elements that should be added to the collection initially.</param> internal TypeConfigurationCollection(IEnumerable<Type> elements) { - Contract.Requires(elements != null); - ErrorUtilities.VerifyArgumentNotNull(elements, "elements"); + Contract.Requires<ArgumentNullException>(elements != null); foreach (Type element in elements) { this.BaseAdd(new TypeConfigurationElement<T> { TypeName = element.FullName }); @@ -70,7 +70,7 @@ namespace DotNetOpenAuth.Configuration { protected override object GetElementKey(ConfigurationElement element) { Contract.Assume(element != null); // this should be Contract.Requires in base class. TypeConfigurationElement<T> typedElement = (TypeConfigurationElement<T>)element; - return !string.IsNullOrEmpty(typedElement.TypeName) ? typedElement.TypeName : typedElement.XamlSource; + return (!string.IsNullOrEmpty(typedElement.TypeName) ? typedElement.TypeName : typedElement.XamlSource) ?? string.Empty; } } } diff --git a/src/DotNetOpenAuth/Configuration/TypeConfigurationElement.cs b/src/DotNetOpenAuth/Configuration/TypeConfigurationElement.cs index c8cf2aa..5ac88da 100644 --- a/src/DotNetOpenAuth/Configuration/TypeConfigurationElement.cs +++ b/src/DotNetOpenAuth/Configuration/TypeConfigurationElement.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.Configuration { using System; using System.Configuration; + using System.Diagnostics.Contracts; using System.IO; using System.Reflection; using System.Web; @@ -18,7 +19,8 @@ namespace DotNetOpenAuth.Configuration { /// the full type that provides some service used by this library. /// </summary> /// <typeparam name="T">A constraint on the type the user may provide.</typeparam> - internal class TypeConfigurationElement<T> : ConfigurationElement { + internal class TypeConfigurationElement<T> : ConfigurationElement + where T : class { /// <summary> /// The name of the attribute whose value is the full name of the type the user is specifying. /// </summary> @@ -75,6 +77,8 @@ namespace DotNetOpenAuth.Configuration { /// <param name="defaultValue">The value to return if no type is given in the .config file.</param> /// <returns>The newly instantiated type.</returns> public T CreateInstance(T defaultValue) { + Contract.Ensures(Contract.Result<T>() != null || Contract.Result<T>() == defaultValue); + return this.CreateInstance(defaultValue, false); } @@ -85,6 +89,8 @@ namespace DotNetOpenAuth.Configuration { /// <param name="allowInternals">if set to <c>true</c> then internal types may be instantiated.</param> /// <returns>The newly instantiated type.</returns> public T CreateInstance(T defaultValue, bool allowInternals) { + Contract.Ensures(Contract.Result<T>() != null || Contract.Result<T>() == defaultValue); + if (this.CustomType != null) { if (!allowInternals) { // Although .NET will usually prevent our instantiating non-public types, diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj index ac2e516..42b99fd 100644 --- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj +++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj @@ -12,6 +12,11 @@ <AssemblyName>DotNetOpenAuth</AssemblyName> <TargetFrameworkVersion>v3.5</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> + <StandardCopyright> +Copyright (c) 2009, Andrew Arnott. All rights reserved. +Code licensed under the Ms-PL License: +http://opensource.org/licenses/ms-pl.html +</StandardCopyright> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -25,7 +30,7 @@ <DocumentationFile>..\..\bin\Debug\DotNetOpenAuth.xml</DocumentationFile> <RunCodeAnalysis>false</RunCodeAnalysis> <CodeAnalysisRules>-Microsoft.Design#CA1054;-Microsoft.Design#CA1056;-Microsoft.Design#CA1055</CodeAnalysisRules> - <CodeContractsEnableRuntimeChecking>False</CodeContractsEnableRuntimeChecking> + <CodeContractsEnableRuntimeChecking>True</CodeContractsEnableRuntimeChecking> <CodeContractsCustomRewriterAssembly> </CodeContractsCustomRewriterAssembly> <CodeContractsCustomRewriterClass> @@ -48,6 +53,11 @@ <CodeContractsShowSquigglies>True</CodeContractsShowSquigglies> <CodeContractsArithmeticObligations>False</CodeContractsArithmeticObligations> <CodeContractsRuntimeOnlyPublicSurface>False</CodeContractsRuntimeOnlyPublicSurface> + <CodeContractsRuntimeThrowOnFailure>True</CodeContractsRuntimeThrowOnFailure> + <CodeContractsRuntimeCallSiteRequires>False</CodeContractsRuntimeCallSiteRequires> + <CodeContractsEmitXMLDocs>True</CodeContractsEmitXMLDocs> + <CodeContractsRedundantAssumptions>False</CodeContractsRedundantAssumptions> + <CodeContractsReferenceAssembly>Build</CodeContractsReferenceAssembly> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> @@ -60,12 +70,12 @@ <DocumentationFile>..\..\bin\Release\DotNetOpenAuth.xml</DocumentationFile> <RunCodeAnalysis>true</RunCodeAnalysis> <CodeAnalysisRules>-Microsoft.Design#CA1054;-Microsoft.Design#CA1056;-Microsoft.Design#CA1055</CodeAnalysisRules> - <CodeContractsEnableRuntimeChecking>False</CodeContractsEnableRuntimeChecking> + <CodeContractsEnableRuntimeChecking>True</CodeContractsEnableRuntimeChecking> <CodeContractsCustomRewriterAssembly> </CodeContractsCustomRewriterAssembly> <CodeContractsCustomRewriterClass> </CodeContractsCustomRewriterClass> - <CodeContractsRuntimeCheckingLevel>Full</CodeContractsRuntimeCheckingLevel> + <CodeContractsRuntimeCheckingLevel>ReleaseRequires</CodeContractsRuntimeCheckingLevel> <CodeContractsRunCodeAnalysis>False</CodeContractsRunCodeAnalysis> <CodeContractsBuildReferenceAssembly>True</CodeContractsBuildReferenceAssembly> <CodeContractsNonNullObligations>False</CodeContractsNonNullObligations> @@ -81,16 +91,21 @@ <CodeContractsUseBaseLine>False</CodeContractsUseBaseLine> <CodeContractsRunInBackground>True</CodeContractsRunInBackground> <CodeContractsShowSquigglies>False</CodeContractsShowSquigglies> + <CodeContractsArithmeticObligations>False</CodeContractsArithmeticObligations> + <CodeContractsRuntimeOnlyPublicSurface>False</CodeContractsRuntimeOnlyPublicSurface> + <CodeContractsRuntimeThrowOnFailure>True</CodeContractsRuntimeThrowOnFailure> + <CodeContractsRuntimeCallSiteRequires>False</CodeContractsRuntimeCallSiteRequires> + <CodeContractsEmitXMLDocs>True</CodeContractsEmitXMLDocs> + <CodeContractsRedundantAssumptions>False</CodeContractsRedundantAssumptions> + <CodeContractsReferenceAssembly>Build</CodeContractsReferenceAssembly> </PropertyGroup> - <PropertyGroup Condition=" '$(Sign)' == 'true' "> + <PropertyGroup> <SignAssembly>true</SignAssembly> - <AssemblyOriginatorKeyFile>..\official-build-key.pfx</AssemblyOriginatorKeyFile> - <DefineConstants>$(DefineConstants);StrongNameSigned</DefineConstants> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'CodeAnalysis|AnyCPU' "> <DebugSymbols>true</DebugSymbols> <OutputPath>..\..\bin\CodeAnalysis\</OutputPath> - <DefineConstants>CONTRACTS_FULL;DEBUG;TRACE</DefineConstants> + <DefineConstants>$(DefineConstants);CONTRACTS_FULL;DEBUG;TRACE</DefineConstants> <DocumentationFile>..\..\bin\CodeAnalysis\DotNetOpenAuth.xml</DocumentationFile> <DebugType>full</DebugType> <PlatformTarget>AnyCPU</PlatformTarget> @@ -105,7 +120,7 @@ </CodeContractsCustomRewriterClass> <CodeContractsRuntimeCheckingLevel>Full</CodeContractsRuntimeCheckingLevel> <CodeContractsRunCodeAnalysis>True</CodeContractsRunCodeAnalysis> - <CodeContractsBuildReferenceAssembly>False</CodeContractsBuildReferenceAssembly> + <CodeContractsBuildReferenceAssembly>True</CodeContractsBuildReferenceAssembly> <CodeContractsNonNullObligations>False</CodeContractsNonNullObligations> <CodeContractsBoundsObligations>False</CodeContractsBoundsObligations> <CodeContractsLibPaths> @@ -120,6 +135,13 @@ <CodeContractsRunInBackground>True</CodeContractsRunInBackground> <CodeContractsShowSquigglies>True</CodeContractsShowSquigglies> <RunCodeAnalysis>true</RunCodeAnalysis> + <CodeContractsArithmeticObligations>False</CodeContractsArithmeticObligations> + <CodeContractsRuntimeOnlyPublicSurface>False</CodeContractsRuntimeOnlyPublicSurface> + <CodeContractsRuntimeThrowOnFailure>True</CodeContractsRuntimeThrowOnFailure> + <CodeContractsRuntimeCallSiteRequires>False</CodeContractsRuntimeCallSiteRequires> + <CodeContractsEmitXMLDocs>True</CodeContractsEmitXMLDocs> + <CodeContractsRedundantAssumptions>False</CodeContractsRedundantAssumptions> + <CodeContractsReferenceAssembly>Build</CodeContractsReferenceAssembly> </PropertyGroup> <ItemGroup> <Reference Include="log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=1b44e1d426115821, processorArchitecture=MSIL"> @@ -146,15 +168,27 @@ <Reference Include="System.IdentityModel.Selectors"> <RequiredTargetFramework>3.0</RequiredTargetFramework> </Reference> + <Reference Include="System.Runtime.Serialization"> + <RequiredTargetFramework>3.0</RequiredTargetFramework> + </Reference> <Reference Include="System.ServiceModel"> <RequiredTargetFramework>3.0</RequiredTargetFramework> </Reference> + <Reference Include="System.ServiceModel.Web"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> <Reference Include="System.Web" /> <Reference Include="System.Web.Abstractions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> <HintPath>..\..\lib\System.Web.Abstractions.dll</HintPath> <RequiredTargetFramework>3.5</RequiredTargetFramework> </Reference> + <Reference Include="System.Web.Extensions"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> + <Reference Include="System.Web.Extensions.Design"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> <Reference Include="System.Web.Mobile" /> <Reference Include="System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> @@ -176,6 +210,7 @@ <ItemGroup> <Compile Include="ComponentModel\ClaimTypeSuggestions.cs" /> <Compile Include="ComponentModel\ConverterBase.cs" /> + <Compile Include="ComponentModel\SuggestedStringsConverterContract.cs" /> <Compile Include="ComponentModel\IssuersSuggestions.cs" /> <Compile Include="ComponentModel\IdentifierConverter.cs" /> <Compile Include="ComponentModel\SuggestedStringsConverter.cs" /> @@ -231,9 +266,11 @@ <Compile Include="Messaging\EmptyList.cs" /> <Compile Include="Messaging\ErrorUtilities.cs" /> <Compile Include="Messaging\IMessageWithEvents.cs" /> + <Compile Include="Messaging\IncomingWebResponseContract.cs" /> <Compile Include="Messaging\IProtocolMessageWithExtensions.cs" /> <Compile Include="Messaging\InternalErrorException.cs" /> <Compile Include="Messaging\KeyedCollectionDelegate.cs" /> + <Compile Include="Messaging\MultipartPostPart.cs" /> <Compile Include="Messaging\NetworkDirectWebResponse.cs" /> <Compile Include="Messaging\OutgoingWebResponseActionResult.cs" /> <Compile Include="Messaging\Reflection\IMessagePartEncoder.cs" /> @@ -353,6 +390,7 @@ <Compile Include="OpenId\ChannelElements\OpenIdMessageFactory.cs" /> <Compile Include="OpenId\ChannelElements\ReturnToSignatureBindingElement.cs" /> <Compile Include="OpenId\ChannelElements\SkipSecurityBindingElement.cs" /> + <Compile Include="OpenId\AssociationContract.cs" /> <Compile Include="OpenId\Extensions\AliasManager.cs" /> <Compile Include="OpenId\Extensions\AttributeExchange\AttributeRequest.cs" /> <Compile Include="OpenId\Extensions\AttributeExchange\AttributeValues.cs" /> @@ -399,6 +437,7 @@ <Compile Include="OpenId\Messages\CheckAuthenticationRequest.cs" /> <Compile Include="OpenId\Messages\CheckAuthenticationResponse.cs" /> <Compile Include="OpenId\Messages\CheckIdRequest.cs" /> + <Compile Include="OpenId\Messages\AssociateSuccessfulResponseContract.cs" /> <Compile Include="OpenId\Messages\IErrorMessage.cs" /> <Compile Include="OpenId\Messages\IndirectResponseBase.cs" /> <Compile Include="OpenId\Messages\IndirectSignedResponse.cs" /> @@ -461,8 +500,15 @@ <Compile Include="OpenId\RelyingParty\AssociationPreference.cs" /> <Compile Include="OpenId\RelyingParty\AuthenticationRequest.cs" /> <Compile Include="OpenId\RelyingParty\AuthenticationRequestMode.cs" /> + <Compile Include="OpenId\RelyingParty\SelectorButtonContract.cs" /> + <Compile Include="OpenId\RelyingParty\SelectorProviderButton.cs" /> + <Compile Include="OpenId\RelyingParty\SelectorOpenIdButton.cs" /> + <Compile Include="OpenId\RelyingParty\SelectorInfoCardButton.cs" /> <Compile Include="OpenId\RelyingParty\IRelyingPartyBehavior.cs" /> + <Compile Include="OpenId\RelyingParty\OpenIdSelector.cs" /> <Compile Include="OpenId\RelyingParty\OpenIdRelyingPartyAjaxControlBase.cs" /> + <Compile Include="OpenId\RelyingParty\IXrdsProviderEndpointContract.cs" /> + <Compile Include="OpenId\RelyingParty\IAuthenticationRequestContract.cs" /> <Compile Include="OpenId\RelyingParty\NegativeAuthenticationResponse.cs" /> <Compile Include="OpenId\RelyingParty\OpenIdAjaxTextBox.cs" /> <Compile Include="OpenId\RelyingParty\OpenIdButton.cs" /> @@ -491,9 +537,10 @@ <Compile Include="OpenId\ProviderEndpointDescription.cs" /> <Compile Include="OpenId\Provider\ProviderSecuritySettings.cs" /> <Compile Include="OpenId\RelyingParty\IRelyingPartyApplicationStore.cs" /> - <Compile Include="OpenId\RelyingParty\AuthenticationResponseSnapshot.cs" /> + <Compile Include="OpenId\RelyingParty\PositiveAuthenticationResponseSnapshot.cs" /> <Compile Include="OpenId\RelyingParty\PrivateSecretManager.cs" /> <Compile Include="OpenId\RelyingParty\RelyingPartySecuritySettings.cs" /> + <Compile Include="OpenId\RelyingParty\SelectorButton.cs" /> <Compile Include="OpenId\RelyingParty\ServiceEndpoint.cs" /> <Compile Include="OpenId\OpenIdXrdsHelper.cs" /> <Compile Include="OpenId\RelyingParty\SimpleXrdsProviderEndpoint.cs" /> @@ -542,6 +589,8 @@ <None Include="Messaging\Bindings\Bindings.cd" /> <None Include="Messaging\Exceptions.cd" /> <None Include="Messaging\Messaging.cd" /> + <None Include="OpenId\RelyingParty\Controls.cd" /> + <None Include="OpenId\RelyingParty\OpenIdRelyingParty.cd" /> </ItemGroup> <ItemGroup> <EmbeddedResource Include="Messaging\MessagingStrings.resx"> @@ -573,7 +622,9 @@ <EmbeddedResource Include="OpenId\RelyingParty\login_failure.png" /> <EmbeddedResource Include="OpenId\RelyingParty\login_success %28lock%29.png" /> <EmbeddedResource Include="OpenId\RelyingParty\login_success.png" /> - <EmbeddedResource Include="OpenId\RelyingParty\OpenIdAjaxTextBox.js" /> + <EmbeddedResource Include="OpenId\RelyingParty\OpenIdAjaxTextBox.js"> + <Copyright>$(StandardCopyright)</Copyright> + </EmbeddedResource> <EmbeddedResource Include="OpenId\RelyingParty\spinner.gif" /> </ItemGroup> <ItemGroup> @@ -594,18 +645,41 @@ <EmbeddedResource Include="InfoCard\infocard_71x50.png" /> <EmbeddedResource Include="InfoCard\infocard_81x57.png" /> <EmbeddedResource Include="InfoCard\infocard_92x64.png" /> - <EmbeddedResource Include="InfoCard\SupportingScript.js" /> + <EmbeddedResource Include="InfoCard\SupportingScript.js"> + <Copyright>$(StandardCopyright)</Copyright> + </EmbeddedResource> </ItemGroup> <ItemGroup> - <EmbeddedResource Include="OpenId\RelyingParty\OpenIdRelyingPartyControlBase.js" /> + <EmbeddedResource Include="OpenId\RelyingParty\OpenIdRelyingPartyControlBase.js"> + <Copyright>$(StandardCopyright)</Copyright> + </EmbeddedResource> </ItemGroup> <ItemGroup> + <EmbeddedResource Include="InfoCard\InfoCardStrings.sr.resx" /> + <EmbeddedResource Include="Messaging\MessagingStrings.sr.resx" /> + <EmbeddedResource Include="OAuth\OAuthStrings.sr.resx" /> <EmbeddedResource Include="OpenId\Behaviors\BehaviorStrings.resx"> <Generator>ResXFileCodeGenerator</Generator> <LastGenOutput>BehaviorStrings.Designer.cs</LastGenOutput> </EmbeddedResource> - <EmbeddedResource Include="OpenId\RelyingParty\OpenIdRelyingPartyAjaxControlBase.js" /> + <EmbeddedResource Include="OpenId\Behaviors\BehaviorStrings.sr.resx" /> + <EmbeddedResource Include="OpenId\OpenIdStrings.sr.resx" /> + <EmbeddedResource Include="OpenId\RelyingParty\OpenIdRelyingPartyAjaxControlBase.js"> + <Copyright>$(StandardCopyright)</Copyright> + </EmbeddedResource> + <EmbeddedResource Include="Strings.sr.resx" /> + <EmbeddedResource Include="Xrds\XrdsStrings.sr.resx" /> + </ItemGroup> + <ItemGroup> + <EmbeddedResource Include="OpenId\RelyingParty\OpenIdAjaxTextBox.css" /> + </ItemGroup> + <ItemGroup> + <EmbeddedResource Include="OpenId\RelyingParty\OpenIdSelector.js" /> + </ItemGroup> + <ItemGroup> + <EmbeddedResource Include="OpenId\RelyingParty\OpenIdSelector.css" /> </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="..\..\tools\DotNetOpenAuth.Versioning.targets" /> -</Project> + <Import Project="..\..\tools\JavascriptPacker.targets" /> +</Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/GlobalSuppressions.cs b/src/DotNetOpenAuth/GlobalSuppressions.cs index d0e0d05..a3343ba 100644 --- a/src/DotNetOpenAuth/GlobalSuppressions.cs +++ b/src/DotNetOpenAuth/GlobalSuppressions.cs @@ -48,3 +48,11 @@ [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "icam", Scope = "resource", Target = "DotNetOpenAuth.OpenId.Behaviors.BehaviorStrings.resources")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "idmanagement", Scope = "resource", Target = "DotNetOpenAuth.OpenId.Behaviors.BehaviorStrings.resources")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "no-pii", Scope = "resource", Target = "DotNetOpenAuth.OpenId.Behaviors.BehaviorStrings.resources")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "req", Scope = "member", Target = "DotNetOpenAuth.OpenId.Provider.IAuthenticationRequestContract.#DotNetOpenAuth.OpenId.Provider.IAuthenticationRequest.ClaimedIdentifier")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "runat", Scope = "resource", Target = "DotNetOpenAuth.OpenId.OpenIdStrings.resources")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "DotNetOpenAuth.OpenId.Behaviors.AXFetchAsSregTransform.#DotNetOpenAuth.OpenId.RelyingParty.IRelyingPartyBehavior.OnOutgoingAuthenticationRequest(DotNetOpenAuth.OpenId.RelyingParty.IAuthenticationRequest)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "DotNetOpenAuth.OpenId.Behaviors.AXFetchAsSregTransform.#DotNetOpenAuth.OpenId.RelyingParty.IRelyingPartyBehavior.OnIncomingPositiveAssertion(DotNetOpenAuth.OpenId.RelyingParty.IAuthenticationResponse)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "DotNetOpenAuth.OpenId.Behaviors.AXFetchAsSregTransform.#DotNetOpenAuth.OpenId.RelyingParty.IRelyingPartyBehavior.ApplySecuritySettings(DotNetOpenAuth.OpenId.RelyingParty.RelyingPartySecuritySettings)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "DotNetOpenAuth.OpenId.Behaviors.AXFetchAsSregTransform.#DotNetOpenAuth.OpenId.Provider.IProviderBehavior.OnOutgoingResponse(DotNetOpenAuth.OpenId.Provider.IAuthenticationRequest)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "DotNetOpenAuth.OpenId.Behaviors.AXFetchAsSregTransform.#DotNetOpenAuth.OpenId.Provider.IProviderBehavior.OnIncomingRequest(DotNetOpenAuth.OpenId.Provider.IRequest)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Scope = "member", Target = "DotNetOpenAuth.OpenId.Behaviors.AXFetchAsSregTransform.#DotNetOpenAuth.OpenId.Provider.IProviderBehavior.ApplySecuritySettings(DotNetOpenAuth.OpenId.Provider.ProviderSecuritySettings)")] diff --git a/src/DotNetOpenAuth/InfoCard/ClaimType.cs b/src/DotNetOpenAuth/InfoCard/ClaimType.cs index 5a25ef8..9d3056a 100644 --- a/src/DotNetOpenAuth/InfoCard/ClaimType.cs +++ b/src/DotNetOpenAuth/InfoCard/ClaimType.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.InfoCard { using System; using System.ComponentModel; + using System.Diagnostics.Contracts; using System.IdentityModel.Claims; using System.Web.UI; @@ -15,6 +16,7 @@ namespace DotNetOpenAuth.InfoCard { /// </summary> [PersistChildren(false)] [Serializable] + [ContractVerification(true)] public class ClaimType { /// <summary> /// Initializes a new instance of the <see cref="ClaimType"/> class. @@ -47,7 +49,7 @@ namespace DotNetOpenAuth.InfoCard { /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. /// </returns> public override string ToString() { - return this.Name != null ? this.Name : null; + return this.Name ?? "<no name>"; } } } diff --git a/src/DotNetOpenAuth/InfoCard/InfoCardImage.cs b/src/DotNetOpenAuth/InfoCard/InfoCardImage.cs index 2b7b25f..247f461 100644 --- a/src/DotNetOpenAuth/InfoCard/InfoCardImage.cs +++ b/src/DotNetOpenAuth/InfoCard/InfoCardImage.cs @@ -23,6 +23,7 @@ namespace DotNetOpenAuth.InfoCard { using System; using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; using System.Globalization; /// <summary> @@ -129,6 +130,7 @@ namespace DotNetOpenAuth.InfoCard { /// <returns>The manifest resource stream name.</returns> internal static string GetImageManifestResourceStreamName(InfoCardImageSize size) { string imageSize = size.ToString(); + Contract.Assume(imageSize.Length >= 6); imageSize = imageSize.Substring(4); return String.Format(CultureInfo.InvariantCulture, UrlFormatString, imageSize); } diff --git a/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs b/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs index fe2c2a2..de4d023 100644 --- a/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs +++ b/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs @@ -16,6 +16,7 @@ namespace DotNetOpenAuth.InfoCard { using System.Drawing.Design; using System.Globalization; using System.Linq; + using System.Text; using System.Text.RegularExpressions; using System.Web; using System.Web.UI; @@ -221,7 +222,7 @@ namespace DotNetOpenAuth.InfoCard { #region Properties /// <summary> - /// Gets the set of claims that are requested from the Information Card.. + /// Gets the set of claims that are requested from the Information Card. /// </summary> [Description("Specifies the required and optional claims.")] [PersistenceMode(PersistenceMode.InnerProperty), Category(InfoCardCategory)] @@ -272,17 +273,20 @@ namespace DotNetOpenAuth.InfoCard { } set { - if (this.Page != null && !this.DesignMode) { - // Validate new value by trying to construct a Uri based on it. - new Uri(new HttpRequestInfo(HttpContext.Current.Request).UrlBeforeRewriting, this.Page.ResolveUrl(value)); // throws an exception on failure. - } else { - // We can't fully test it, but it should start with either ~/ or a protocol. - if (Regex.IsMatch(value, @"^https?://")) { - new Uri(value); // make sure it's fully-qualified, but ignore wildcards - } else if (value.StartsWith("~/", StringComparison.Ordinal)) { - // this is valid too + ErrorUtilities.VerifyOperation(string.IsNullOrEmpty(value) || this.Page == null || this.DesignMode || (HttpContext.Current != null && HttpContext.Current.Request != null), MessagingStrings.HttpContextRequired); + if (!string.IsNullOrEmpty(value)) { + if (this.Page != null && !this.DesignMode) { + // Validate new value by trying to construct a Uri based on it. + new Uri(new HttpRequestInfo(HttpContext.Current.Request).UrlBeforeRewriting, this.Page.ResolveUrl(value)); // throws an exception on failure. } else { - throw new UriFormatException(); + // We can't fully test it, but it should start with either ~/ or a protocol. + if (Regex.IsMatch(value, @"^https?://")) { + new Uri(value); // make sure it's fully-qualified, but ignore wildcards + } else if (value.StartsWith("~/", StringComparison.Ordinal)) { + // this is valid too + } else { + throw new UriFormatException(); + } } } @@ -448,8 +452,7 @@ namespace DotNetOpenAuth.InfoCard { /// <returns>The event arguments sent to the event handlers.</returns> [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "decryptor", Justification = "By design")] protected virtual ReceivingTokenEventArgs OnReceivingToken(string tokenXml) { - Contract.Requires(tokenXml != null); - ErrorUtilities.VerifyArgumentNotNull(tokenXml, "tokenXml"); + Contract.Requires<ArgumentNullException>(tokenXml != null); var args = new ReceivingTokenEventArgs(tokenXml); var receivingToken = this.ReceivingToken; @@ -465,8 +468,7 @@ namespace DotNetOpenAuth.InfoCard { /// </summary> /// <param name="token">The token, if it was decrypted.</param> protected virtual void OnReceivedToken(Token token) { - Contract.Requires(token != null); - ErrorUtilities.VerifyArgumentNotNull(token, "token"); + Contract.Requires<ArgumentNullException>(token != null); var receivedInfoCard = this.ReceivedToken; if (receivedInfoCard != null) { @@ -480,8 +482,8 @@ namespace DotNetOpenAuth.InfoCard { /// <param name="unprocessedToken">The unprocessed token.</param> /// <param name="ex">The exception generated while processing the token.</param> protected virtual void OnTokenProcessingError(string unprocessedToken, Exception ex) { - Contract.Requires(unprocessedToken != null); - Contract.Requires(ex != null); + Contract.Requires<ArgumentNullException>(unprocessedToken != null); + Contract.Requires<ArgumentNullException>(ex != null); var tokenProcessingError = this.TokenProcessingError; if (tokenProcessingError != null) { @@ -532,6 +534,8 @@ namespace DotNetOpenAuth.InfoCard { // the privacy URL is present but the privacy version is not. ErrorUtilities.VerifyOperation(string.IsNullOrEmpty(this.PrivacyUrl) || !string.IsNullOrEmpty(this.PrivacyVersion), InfoCardStrings.PrivacyVersionRequiredWithPrivacyUrl); } + + this.RegisterInfoCardSelectorObjectScript(); } /// <summary> @@ -540,12 +544,18 @@ namespace DotNetOpenAuth.InfoCard { /// <param name="name">The parameter name.</param> /// <param name="value">The parameter value.</param> /// <returns>The control that renders to the Param tag.</returns> - private static Control CreateParam(string name, string value) { - Contract.Ensures(Contract.Result<Control>() != null); - HtmlGenericControl control = new HtmlGenericControl(HtmlTextWriterTag.Param.ToString()); - control.Attributes.Add(HtmlTextWriterAttribute.Name.ToString(), name); - control.Attributes.Add(HtmlTextWriterAttribute.Value.ToString(), value); - return control; + private static string CreateParamJs(string name, string value) { + Contract.Ensures(Contract.Result<string>() != null); + string scriptFormat = @" objp = document.createElement('param'); + objp.name = {0}; + objp.value = {1}; + obj.appendChild(objp); +"; + return string.Format( + CultureInfo.InvariantCulture, + scriptFormat, + MessagingUtilities.GetSafeJavascriptValue(name), + MessagingUtilities.GetSafeJavascriptValue(value)); } /// <summary> @@ -565,17 +575,7 @@ namespace DotNetOpenAuth.InfoCard { supportedPanel.Style[HtmlTextWriterStyle.Display] = "none"; } - supportedPanel.Controls.Add(this.CreateInfoCardSelectorObject()); - - // add clickable image - Image image = new Image(); - image.ImageUrl = this.Page.ClientScript.GetWebResourceUrl(typeof(InfoCardSelector), InfoCardImage.GetImageManifestResourceStreamName(this.ImageSize)); - image.AlternateText = InfoCardStrings.SelectorClickPrompt; - image.ToolTip = this.ToolTip; - image.Style[HtmlTextWriterStyle.Cursor] = "hand"; - - image.Attributes["onclick"] = this.GetInfoCardSelectorActivationScript(false); - supportedPanel.Controls.Add(image); + supportedPanel.Controls.Add(this.CreateInfoCardImage()); // trigger the selector at page load? if (this.AutoPopup && !this.Page.IsPostBack) { @@ -630,47 +630,74 @@ namespace DotNetOpenAuth.InfoCard { } /// <summary> - /// Creates the info card selector <object> HTML tag. + /// Adds the javascript that adds the info card selector <object> HTML tag to the page. /// </summary> - /// <returns>A control that renders to the <object> tag.</returns> [Pure] - private Control CreateInfoCardSelectorObject() { - HtmlGenericControl cardSpaceControl = new HtmlGenericControl(HtmlTextWriterTag.Object.ToString()); - cardSpaceControl.Attributes.Add(HtmlTextWriterAttribute.Type.ToString(), "application/x-informationcard"); - cardSpaceControl.Attributes.Add(HtmlTextWriterAttribute.Id.ToString(), this.ClientID + "_cs"); + private void RegisterInfoCardSelectorObjectScript() { + string scriptFormat = @"{{ + var obj = document.createElement('object'); + obj.type = 'application/x-informationcard'; + obj.id = {0}; + obj.style.display = 'none'; +"; + StringBuilder script = new StringBuilder(); + script.AppendFormat( + CultureInfo.InvariantCulture, + scriptFormat, + MessagingUtilities.GetSafeJavascriptValue(this.ClientID + "_cs")); if (!string.IsNullOrEmpty(this.Issuer)) { - cardSpaceControl.Controls.Add(CreateParam("issuer", this.Issuer)); + script.AppendLine(CreateParamJs("issuer", this.Issuer)); } if (!string.IsNullOrEmpty(this.IssuerPolicy)) { - cardSpaceControl.Controls.Add(CreateParam("issuerPolicy", this.IssuerPolicy)); + script.AppendLine(CreateParamJs("issuerPolicy", this.IssuerPolicy)); } if (!string.IsNullOrEmpty(this.TokenType)) { - cardSpaceControl.Controls.Add(CreateParam("tokenType", this.TokenType)); + script.AppendLine(CreateParamJs("tokenType", this.TokenType)); } string requiredClaims, optionalClaims; this.GetRequestedClaims(out requiredClaims, out optionalClaims); ErrorUtilities.VerifyArgument(!string.IsNullOrEmpty(requiredClaims) || !string.IsNullOrEmpty(optionalClaims), InfoCardStrings.EmptyClaimListNotAllowed); if (!string.IsNullOrEmpty(requiredClaims)) { - cardSpaceControl.Controls.Add(CreateParam("requiredClaims", requiredClaims)); + script.AppendLine(CreateParamJs("requiredClaims", requiredClaims)); } if (!string.IsNullOrEmpty(optionalClaims)) { - cardSpaceControl.Controls.Add(CreateParam("optionalClaims", optionalClaims)); + script.AppendLine(CreateParamJs("optionalClaims", optionalClaims)); } if (!string.IsNullOrEmpty(this.PrivacyUrl)) { string privacyUrl = this.DesignMode ? this.PrivacyUrl : new Uri(Page.Request.Url, Page.ResolveUrl(this.PrivacyUrl)).AbsoluteUri; - cardSpaceControl.Controls.Add(CreateParam("privacyUrl", privacyUrl)); + script.AppendLine(CreateParamJs("privacyUrl", privacyUrl)); } if (!string.IsNullOrEmpty(this.PrivacyVersion)) { - cardSpaceControl.Controls.Add(CreateParam("privacyVersion", this.PrivacyVersion)); + script.AppendLine(CreateParamJs("privacyVersion", this.PrivacyVersion)); } - return cardSpaceControl; + script.AppendLine(@"if (document.infoCard.isSupported()) { document.write(obj.outerHTML); } +}"); + + this.Page.ClientScript.RegisterClientScriptBlock(typeof(InfoCardSelector), this.ClientID + "tag", script.ToString(), true); + } + + /// <summary> + /// Creates the info card clickable image. + /// </summary> + /// <returns>An Image object.</returns> + [Pure] + private Image CreateInfoCardImage() { + // add clickable image + Image image = new Image(); + image.ImageUrl = this.Page.ClientScript.GetWebResourceUrl(typeof(InfoCardSelector), InfoCardImage.GetImageManifestResourceStreamName(this.ImageSize)); + image.AlternateText = InfoCardStrings.SelectorClickPrompt; + image.ToolTip = this.ToolTip; + image.Style[HtmlTextWriterStyle.Cursor] = "hand"; + + image.Attributes["onclick"] = this.GetInfoCardSelectorActivationScript(false); + return image; } /// <summary> @@ -681,7 +708,7 @@ namespace DotNetOpenAuth.InfoCard { /// <param name="optional">A space-delimited list of claim type URIs for claims that may optionally be included in a submitted Information Card.</param> [Pure] private void GetRequestedClaims(out string required, out string optional) { - Contract.Requires(this.ClaimsRequested != null); + Contract.Requires<InvalidOperationException>(this.ClaimsRequested != null); Contract.Ensures(Contract.ValueAtReturn<string>(out required) != null); Contract.Ensures(Contract.ValueAtReturn<string>(out optional) != null); @@ -696,10 +723,10 @@ namespace DotNetOpenAuth.InfoCard { string[] requiredClaimsArray = requiredClaims.ToArray(); string[] optionalClaimsArray = optionalClaims.ToArray(); - Contract.Assume(requiredClaimsArray != null); - Contract.Assume(optionalClaimsArray != null); required = string.Join(" ", requiredClaimsArray); optional = string.Join(" ", optionalClaimsArray); + Contract.Assume(required != null); + Contract.Assume(optional != null); } /// <summary> @@ -707,7 +734,7 @@ namespace DotNetOpenAuth.InfoCard { /// or to downgrade gracefully if the user agent lacks an Information Card selector. /// </summary> private void RenderSupportingScript() { - Contract.Requires(this.infoCardSupportedPanel != null); + Contract.Requires<InvalidOperationException>(this.infoCardSupportedPanel != null); this.Page.ClientScript.RegisterClientScriptResource(typeof(InfoCardSelector), ScriptResourceName); @@ -726,4 +753,4 @@ namespace DotNetOpenAuth.InfoCard { } } } -}
\ No newline at end of file +} diff --git a/src/DotNetOpenAuth/InfoCard/InfoCardStrings.sr.resx b/src/DotNetOpenAuth/InfoCard/InfoCardStrings.sr.resx new file mode 100644 index 0000000..9df0429 --- /dev/null +++ b/src/DotNetOpenAuth/InfoCard/InfoCardStrings.sr.resx @@ -0,0 +1,135 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="AudienceMismatch" xml:space="preserve"> + <value>Token je neispravan: restrikcije u prijemu se ne slažu sa Relying Party.</value> + </data> + <data name="EmptyClaimListNotAllowed" xml:space="preserve"> + <value>Tražena lista zahteva za uključivanje u InfoCard ne sme biti prazna.</value> + </data> + <data name="EncryptionAlgorithmNotFound" xml:space="preserve"> + <value>encryptionAlgorithm nije pronađen.</value> + </data> + <data name="PpidClaimRequired" xml:space="preserve"> + <value>Ova operacija zahteva da PPID zahtev bude uključen u InfoCard token.</value> + </data> + <data name="SelectorClickPrompt" xml:space="preserve"> + <value>Kliknite ovde da odaberete vaš Information Card.</value> + </data> +</root>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/InfoCard/ReceivedTokenEventArgs.cs b/src/DotNetOpenAuth/InfoCard/ReceivedTokenEventArgs.cs index 1511e2d..6c6a5af 100644 --- a/src/DotNetOpenAuth/InfoCard/ReceivedTokenEventArgs.cs +++ b/src/DotNetOpenAuth/InfoCard/ReceivedTokenEventArgs.cs @@ -31,6 +31,7 @@ namespace DotNetOpenAuth.InfoCard { /// <summary> /// Verifies conditions that should be true for any valid state of this object. /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] [ContractInvariantMethod] protected void ObjectInvariant() { diff --git a/src/DotNetOpenAuth/InfoCard/ReceivingTokenEventArgs.cs b/src/DotNetOpenAuth/InfoCard/ReceivingTokenEventArgs.cs index f3722d7..aaf734b 100644 --- a/src/DotNetOpenAuth/InfoCard/ReceivingTokenEventArgs.cs +++ b/src/DotNetOpenAuth/InfoCard/ReceivingTokenEventArgs.cs @@ -21,7 +21,7 @@ namespace DotNetOpenAuth.InfoCard { /// </summary> /// <param name="tokenXml">The raw token XML, prior to any decryption.</param> internal ReceivingTokenEventArgs(string tokenXml) { - Contract.Requires(tokenXml != null); + Contract.Requires<ArgumentNullException>(tokenXml != null); this.TokenXml = tokenXml; this.IsEncrypted = Token.IsEncrypted(this.TokenXml); @@ -63,7 +63,7 @@ namespace DotNetOpenAuth.InfoCard { /// </summary> /// <param name="securityToken">The security token.</param> public void AddDecryptingToken(SecurityToken securityToken) { - Contract.Requires(securityToken != null); + Contract.Requires<ArgumentNullException>(securityToken != null); this.DecryptingTokens.Add(securityToken); } @@ -72,8 +72,8 @@ namespace DotNetOpenAuth.InfoCard { /// </summary> /// <param name="certificate">The certificate.</param> public void AddDecryptingToken(X509Certificate2 certificate) { - Contract.Requires(certificate != null); - Contract.Requires(certificate.HasPrivateKey); + Contract.Requires<ArgumentNullException>(certificate != null); + Contract.Requires<ArgumentException>(certificate.HasPrivateKey); this.AddDecryptingToken(new X509SecurityToken(certificate)); } @@ -81,6 +81,7 @@ namespace DotNetOpenAuth.InfoCard { /// <summary> /// Verifies conditions that should be true for any valid state of this object. /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] [ContractInvariantMethod] protected void ObjectInvariant() { diff --git a/src/DotNetOpenAuth/InfoCard/SupportingScript.js b/src/DotNetOpenAuth/InfoCard/SupportingScript.js index ce4b02d..a883cd7 100644 --- a/src/DotNetOpenAuth/InfoCard/SupportingScript.js +++ b/src/DotNetOpenAuth/InfoCard/SupportingScript.js @@ -1,5 +1,7 @@ +/*jslint white: true, onevar: true, browser: true, undef: true, nomen: true, plusplus: true, bitwise: true, regexp: true, strict: true, newcap: true, immed: true */ +"use strict"; document.infoCard = { - isSupported: function() { + isSupported: function () { /// <summary> /// Determines if information cards are supported by the /// browser. @@ -7,19 +9,20 @@ document.infoCard = { /// <returns> /// true-if the browser supports information cards. ///</returns> + var IEVer, embed, x, event; - var IEVer = -1; - if (navigator.appName == 'Microsoft Internet Explorer') { - if (new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})").exec(navigator.userAgent) != null) { + IEVer = -1; + if (navigator.appName === 'Microsoft Internet Explorer') { + if (new RegExp("MSIE ([0-9]{1,}[\\.0-9]{0,})").exec(navigator.userAgent) !== null) { IEVer = parseFloat(RegExp.$1); } } // Look for IE 7+. if (IEVer >= 7) { - var embed = document.createElement("object"); + embed = document.createElement("object"); embed.type = "application/x-informationcard"; - return !(embed.issuerPolicy === undefined) && embed.isInstalled; + return embed.issuerPolicy !== undefined && embed.isInstalled; } // not IE (any version) @@ -32,22 +35,23 @@ document.infoCard = { // check for the IdentitySelector event handler is there. if (document.addEventListener) { - var event = document.createEvent("Events"); + event = document.createEvent("Events"); event.initEvent("IdentitySelectorAvailable", true, true); top.dispatchEvent(event); - if (top.IdentitySelectorAvailable == true) { + if (top.IdentitySelectorAvailable === true) { return true; } } } - + return false; }, - activate: function(selectorId, hiddenFieldName) { - var selector = document.getElementById(selectorId); - var hiddenField = document.getElementsByName(hiddenFieldName)[0]; + activate: function (selectorId, hiddenFieldName) { + var selector, hiddenField; + selector = document.getElementById(selectorId); + hiddenField = document.getElementsByName(hiddenFieldName)[0]; try { hiddenField.value = selector.value; } catch (e) { @@ -71,7 +75,7 @@ document.infoCard = { } }, - showStatic: function(divName) { + showStatic: function (divName) { var div = document.getElementById(divName); if (div) { div.style.visibility = 'visible'; @@ -95,26 +99,26 @@ document.infoCard = { checkDynamic: function (controlDiv, unsupportedDiv) { if (this.isSupported()) { this.showDynamic(controlDiv); - if (unsupportedDiv != '') { + if (unsupportedDiv) { this.hideDynamic(unsupportedDiv); } } else { this.hideDynamic(controlDiv); - if (unsupportedDiv != '') { + if (unsupportedDiv) { this.showDynamic(unsupportedDiv); } } }, - checkStatic: function(controlDiv, unsupportedDiv) { + checkStatic: function (controlDiv, unsupportedDiv) { if (this.isSupported()) { this.showStatic(controlDiv); - if (unsupportedDiv != '') { + if (unsupportedDiv) { this.hideStatic(unsupportedDiv); } } else { this.hideStatic(controlDiv); - if (unsupportedDiv != '') { + if (unsupportedDiv) { this.showDynamic(unsupportedDiv); } } diff --git a/src/DotNetOpenAuth/InfoCard/Token/Token.cs b/src/DotNetOpenAuth/InfoCard/Token/Token.cs index 5c955ed..4656e3f 100644 --- a/src/DotNetOpenAuth/InfoCard/Token/Token.cs +++ b/src/DotNetOpenAuth/InfoCard/Token/Token.cs @@ -42,19 +42,20 @@ namespace DotNetOpenAuth.InfoCard { /// <param name="decryptor">The decryptor to use to decrypt the token, if necessary..</param> /// <exception cref="InformationCardException">Thrown for any problem decoding or decrypting the token.</exception> private Token(string tokenXml, Uri audience, TokenDecryptor decryptor) { - Contract.Requires(tokenXml != null && tokenXml.Length > 0); - Contract.Requires(decryptor != null || !IsEncrypted(tokenXml)); - ErrorUtilities.VerifyNonZeroLength(tokenXml, "tokenXml"); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(tokenXml)); + Contract.Requires<ArgumentException>(decryptor != null || !IsEncrypted(tokenXml)); + Contract.Ensures(this.AuthorizationContext != null); byte[] decryptedBytes; string decryptedString; using (XmlReader tokenReader = XmlReader.Create(new StringReader(tokenXml))) { + Contract.Assume(tokenReader != null); // BCL contract should say XmlReader.Create result != null if (IsEncrypted(tokenReader)) { Logger.InfoCard.DebugFormat("Incoming SAML token, before decryption: {0}", tokenXml); - ErrorUtilities.VerifyArgumentNotNull(decryptor, "decryptor"); decryptedBytes = decryptor.DecryptToken(tokenReader); decryptedString = Encoding.UTF8.GetString(decryptedBytes); + Contract.Assume(decryptedString != null); // BCL contracts should be enhanced here } else { decryptedBytes = Encoding.UTF8.GetBytes(tokenXml); decryptedString = tokenXml; @@ -106,7 +107,7 @@ namespace DotNetOpenAuth.InfoCard { /// </summary> public string SiteSpecificId { get { - Contract.Requires(this.Claims.ContainsKey(ClaimTypes.PPID)); + Contract.Requires<InvalidOperationException>(this.Claims.ContainsKey(ClaimTypes.PPID) && !string.IsNullOrEmpty(this.Claims[ClaimTypes.PPID])); string ppidValue; ErrorUtilities.VerifyOperation(this.Claims.TryGetValue(ClaimTypes.PPID, out ppidValue) && ppidValue != null, InfoCardStrings.PpidClaimRequired); return TokenUtility.CalculateSiteSpecificID(ppidValue); @@ -132,7 +133,7 @@ namespace DotNetOpenAuth.InfoCard { /// <param name="tokenXml">The token XML.</param> /// <returns>The deserialized token.</returns> public static Token Read(string tokenXml) { - Contract.Requires(!String.IsNullOrEmpty(tokenXml)); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(tokenXml)); return Read(tokenXml, (Uri)null); } @@ -143,7 +144,7 @@ namespace DotNetOpenAuth.InfoCard { /// <param name="audience">The URI that this token must have been crafted to be sent to. Use <c>null</c> to accept any intended audience.</param> /// <returns>The deserialized token.</returns> public static Token Read(string tokenXml, Uri audience) { - Contract.Requires(!String.IsNullOrEmpty(tokenXml)); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(tokenXml)); return Read(tokenXml, audience, Enumerable.Empty<SecurityToken>()); } @@ -154,8 +155,8 @@ namespace DotNetOpenAuth.InfoCard { /// <param name="decryptionTokens">Any X.509 certificates that may be used to decrypt the token, if necessary.</param> /// <returns>The deserialized token.</returns> public static Token Read(string tokenXml, IEnumerable<SecurityToken> decryptionTokens) { - Contract.Requires(!String.IsNullOrEmpty(tokenXml)); - Contract.Requires(decryptionTokens != null); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(tokenXml)); + Contract.Requires<ArgumentNullException>(decryptionTokens != null); return Read(tokenXml, null, decryptionTokens); } @@ -167,8 +168,8 @@ namespace DotNetOpenAuth.InfoCard { /// <param name="decryptionTokens">Any X.509 certificates that may be used to decrypt the token, if necessary.</param> /// <returns>The deserialized token.</returns> public static Token Read(string tokenXml, Uri audience, IEnumerable<SecurityToken> decryptionTokens) { - Contract.Requires(!String.IsNullOrEmpty(tokenXml)); - Contract.Requires(decryptionTokens != null); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(tokenXml)); + Contract.Requires<ArgumentNullException>(decryptionTokens != null); Contract.Ensures(Contract.Result<Token>() != null); TokenDecryptor decryptor = null; @@ -190,14 +191,27 @@ namespace DotNetOpenAuth.InfoCard { /// </returns> [Pure] internal static bool IsEncrypted(string tokenXml) { - Contract.Requires(tokenXml != null); - ErrorUtilities.VerifyArgumentNotNull(tokenXml, "tokenXml"); + Contract.Requires<ArgumentNullException>(tokenXml != null); using (XmlReader tokenReader = XmlReader.Create(new StringReader(tokenXml))) { + Contract.Assume(tokenReader != null); // CC missing for XmlReader.Create return IsEncrypted(tokenReader); } } +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + protected void ObjectInvariant() + { + Contract.Invariant(this.AuthorizationContext != null); + } +#endif + /// <summary> /// Determines whether the specified token XML is encrypted. /// </summary> @@ -206,8 +220,7 @@ namespace DotNetOpenAuth.InfoCard { /// <c>true</c> if the specified token XML is encrypted; otherwise, <c>false</c>. /// </returns> private static bool IsEncrypted(XmlReader tokenXmlReader) { - Contract.Requires(tokenXmlReader != null); - ErrorUtilities.VerifyArgumentNotNull(tokenXmlReader, "tokenXmlReader"); + Contract.Requires<ArgumentNullException>(tokenXmlReader != null); return tokenXmlReader.IsStartElement(TokenDecryptor.XmlEncryptionStrings.EncryptedData, TokenDecryptor.XmlEncryptionStrings.Namespace); } diff --git a/src/DotNetOpenAuth/InfoCard/Token/TokenDecryptor.cs b/src/DotNetOpenAuth/InfoCard/Token/TokenDecryptor.cs index 1038ad7..2257f15 100644 --- a/src/DotNetOpenAuth/InfoCard/Token/TokenDecryptor.cs +++ b/src/DotNetOpenAuth/InfoCard/Token/TokenDecryptor.cs @@ -89,9 +89,8 @@ namespace DotNetOpenAuth.InfoCard { /// <param name="reader">The encrypted token XML reader.</param> /// <returns>A byte array of the contents of the encrypted token</returns> internal byte[] DecryptToken(XmlReader reader) { - Contract.Requires(reader != null); + Contract.Requires<ArgumentNullException>(reader != null); Contract.Ensures(Contract.Result<byte[]>() != null); - ErrorUtilities.VerifyArgumentNotNull(reader, "reader"); byte[] securityTokenData; string encryptionAlgorithm; @@ -148,6 +147,7 @@ namespace DotNetOpenAuth.InfoCard { /// <summary> /// Verifies conditions that should be true for any valid state of this object. /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] [ContractInvariantMethod] protected void ObjectInvariant() { diff --git a/src/DotNetOpenAuth/InfoCard/Token/TokenUtility.cs b/src/DotNetOpenAuth/InfoCard/Token/TokenUtility.cs index 85b951d..48b7794 100644 --- a/src/DotNetOpenAuth/InfoCard/Token/TokenUtility.cs +++ b/src/DotNetOpenAuth/InfoCard/Token/TokenUtility.cs @@ -48,6 +48,8 @@ namespace DotNetOpenAuth.InfoCard { /// The authorization context carried by the token. /// </returns> internal static AuthorizationContext AuthenticateToken(XmlReader reader, Uri audience) { + Contract.Ensures(Contract.Result<AuthorizationContext>() != null); + // Extensibility Point: // in order to accept different token types, you would need to add additional // code to create an authenticationcontext from the security token. @@ -160,8 +162,7 @@ namespace DotNetOpenAuth.InfoCard { /// <param name="authzContext">The Authorization Context</param> /// <returns>A unique ID for this user at this web site.</returns> internal static string GetUniqueName(AuthorizationContext authzContext) { - Contract.Requires(authzContext != null); - ErrorUtilities.VerifyArgumentNotNull(authzContext, "authzContext"); + Contract.Requires<ArgumentNullException>(authzContext != null); Claim uniqueIssuerClaim = null; Claim uniqueUserClaim = null; @@ -217,9 +218,8 @@ namespace DotNetOpenAuth.InfoCard { /// <param name="ppid">The personal private identifier.</param> /// <returns>A string containing the XXX-XXXX-XXX cosmetic value.</returns> internal static string CalculateSiteSpecificID(string ppid) { - Contract.Requires(ppid != null); - ErrorUtilities.VerifyArgumentNotNull(ppid, "ppid"); - Contract.Ensures(Contract.Result<string>() != null && Contract.Result<string>().Length > 0); + Contract.Requires<ArgumentNullException>(ppid != null); + Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>())); int callSignChars = 10; char[] charMap = "QL23456789ABCDEFGHJKMNPRSTUVWXYZ".ToCharArray(); @@ -246,8 +246,7 @@ namespace DotNetOpenAuth.InfoCard { /// <param name="cs">the claimset which contains the claim</param> /// <returns>a RSA claim</returns> private static Claim GetUniqueRsaClaim(ClaimSet cs) { - Contract.Requires(cs != null); - ErrorUtilities.VerifyArgumentNotNull(cs, "cs"); + Contract.Requires<ArgumentNullException>(cs != null); Claim rsa = null; @@ -269,11 +268,9 @@ namespace DotNetOpenAuth.InfoCard { /// <param name="claimValue">the claim value to hash with.</param> /// <returns>A base64 representation of the combined ID.</returns> private static string ComputeCombinedId(RSA issuerKey, string claimValue) { - Contract.Requires(issuerKey != null); - Contract.Requires(claimValue != null); + Contract.Requires<ArgumentNullException>(issuerKey != null); + Contract.Requires<ArgumentNullException>(claimValue != null); Contract.Ensures(Contract.Result<string>() != null); - ErrorUtilities.VerifyArgumentNotNull(issuerKey, "issuerKey"); - ErrorUtilities.VerifyArgumentNotNull(claimValue, "claimValue"); int nameLength = Encoding.UTF8.GetByteCount(claimValue); RSAParameters rsaParams = issuerKey.ExportParameters(false); diff --git a/src/DotNetOpenAuth/InfoCard/TokenProcessingErrorEventArgs.cs b/src/DotNetOpenAuth/InfoCard/TokenProcessingErrorEventArgs.cs index 1132ac0..1b4a62d 100644 --- a/src/DotNetOpenAuth/InfoCard/TokenProcessingErrorEventArgs.cs +++ b/src/DotNetOpenAuth/InfoCard/TokenProcessingErrorEventArgs.cs @@ -18,8 +18,8 @@ namespace DotNetOpenAuth.InfoCard { /// <param name="tokenXml">The token XML.</param> /// <param name="exception">The exception.</param> internal TokenProcessingErrorEventArgs(string tokenXml, Exception exception) { - Contract.Requires(tokenXml != null); - Contract.Requires(exception != null); + Contract.Requires<ArgumentNullException>(tokenXml != null); + Contract.Requires<ArgumentNullException>(exception != null); this.TokenXml = tokenXml; this.Exception = exception; } @@ -38,6 +38,7 @@ namespace DotNetOpenAuth.InfoCard { /// <summary> /// Verifies conditions that should be true for any valid state of this object. /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] [ContractInvariantMethod] protected void ObjectInvariant() { diff --git a/src/DotNetOpenAuth/Logger.cs b/src/DotNetOpenAuth/Logger.cs index 6015df5..a9dbef2 100644 --- a/src/DotNetOpenAuth/Logger.cs +++ b/src/DotNetOpenAuth/Logger.cs @@ -6,6 +6,7 @@ namespace DotNetOpenAuth { using System; + using System.Diagnostics.Contracts; using System.Globalization; using DotNetOpenAuth.Loggers; using DotNetOpenAuth.Messaging; @@ -131,7 +132,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 Create(string name) { - ErrorUtilities.VerifyNonZeroLength(name, "name"); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(name)); return InitializeFacade(name); } @@ -142,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; @@ -153,9 +155,7 @@ namespace DotNetOpenAuth { /// <param name="type">A type whose full name that will be included in the log file.</param> /// <returns>The <see cref="ILog"/> instance created with the given type name.</returns> internal static ILog Create(Type type) { - if (type == null) { - throw new ArgumentNullException("type"); - } + Contract.Requires<ArgumentNullException>(type != null); return Create(type.FullName); } diff --git a/src/DotNetOpenAuth/Messaging/Bindings/ExpiredMessageException.cs b/src/DotNetOpenAuth/Messaging/Bindings/ExpiredMessageException.cs index 417c98e..73ce289 100644 --- a/src/DotNetOpenAuth/Messaging/Bindings/ExpiredMessageException.cs +++ b/src/DotNetOpenAuth/Messaging/Bindings/ExpiredMessageException.cs @@ -6,6 +6,7 @@ namespace DotNetOpenAuth.Messaging.Bindings { using System; + using System.Diagnostics.Contracts; using System.Globalization; /// <summary> @@ -20,6 +21,7 @@ namespace DotNetOpenAuth.Messaging.Bindings { /// <param name="faultedMessage">The expired message.</param> public ExpiredMessageException(DateTime utcExpirationDate, IProtocolMessage faultedMessage) : base(string.Format(CultureInfo.CurrentCulture, MessagingStrings.ExpiredMessage, utcExpirationDate.ToLocalTime(), DateTime.Now), faultedMessage) { + Contract.Requires<ArgumentException>(utcExpirationDate.Kind == DateTimeKind.Utc); } /// <summary> diff --git a/src/DotNetOpenAuth/Messaging/Bindings/NonceMemoryStore.cs b/src/DotNetOpenAuth/Messaging/Bindings/NonceMemoryStore.cs index fcea3d9..6e64acc 100644 --- a/src/DotNetOpenAuth/Messaging/Bindings/NonceMemoryStore.cs +++ b/src/DotNetOpenAuth/Messaging/Bindings/NonceMemoryStore.cs @@ -48,7 +48,7 @@ namespace DotNetOpenAuth.Messaging.Bindings { /// Initializes a new instance of the <see cref="NonceMemoryStore"/> class. /// </summary> internal NonceMemoryStore() - : this(StandardExpirationBindingElement.DefaultMaximumMessageAge) { + : this(StandardExpirationBindingElement.MaximumMessageAge) { } /// <summary> diff --git a/src/DotNetOpenAuth/Messaging/Bindings/StandardExpirationBindingElement.cs b/src/DotNetOpenAuth/Messaging/Bindings/StandardExpirationBindingElement.cs index 7b00e34..ddfa88a 100644 --- a/src/DotNetOpenAuth/Messaging/Bindings/StandardExpirationBindingElement.cs +++ b/src/DotNetOpenAuth/Messaging/Bindings/StandardExpirationBindingElement.cs @@ -6,6 +6,7 @@ namespace DotNetOpenAuth.Messaging.Bindings { using System; + using DotNetOpenAuth.Configuration; /// <summary> /// A message expiration enforcing binding element that supports messages @@ -13,11 +14,6 @@ namespace DotNetOpenAuth.Messaging.Bindings { /// </summary> internal class StandardExpirationBindingElement : IChannelBindingElement { /// <summary> - /// The default maximum message age to use if the default constructor is called. - /// </summary> - internal static readonly TimeSpan DefaultMaximumMessageAge = TimeSpan.FromMinutes(13); - - /// <summary> /// Initializes a new instance of the <see cref="StandardExpirationBindingElement"/> class. /// </summary> internal StandardExpirationBindingElement() { diff --git a/src/DotNetOpenAuth/Messaging/Bindings/StandardReplayProtectionBindingElement.cs b/src/DotNetOpenAuth/Messaging/Bindings/StandardReplayProtectionBindingElement.cs index c8d5873..0a7ddbd 100644 --- a/src/DotNetOpenAuth/Messaging/Bindings/StandardReplayProtectionBindingElement.cs +++ b/src/DotNetOpenAuth/Messaging/Bindings/StandardReplayProtectionBindingElement.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.Messaging.Bindings { using System; using System.Diagnostics; + using System.Diagnostics.Contracts; /// <summary> /// A binding element that checks/verifies a nonce message part. @@ -41,7 +42,7 @@ namespace DotNetOpenAuth.Messaging.Bindings { /// <param name="nonceStore">The store where nonces will be persisted and checked.</param> /// <param name="allowEmptyNonces">A value indicating whether zero-length nonces will be allowed.</param> internal StandardReplayProtectionBindingElement(INonceStore nonceStore, bool allowEmptyNonces) { - ErrorUtilities.VerifyArgumentNotNull(nonceStore, "nonceStore"); + Contract.Requires<ArgumentNullException>(nonceStore != null); this.nonceStore = nonceStore; this.AllowZeroLengthNonce = allowEmptyNonces; diff --git a/src/DotNetOpenAuth/Messaging/CachedDirectWebResponse.cs b/src/DotNetOpenAuth/Messaging/CachedDirectWebResponse.cs index 65cbba1..b37c93a 100644 --- a/src/DotNetOpenAuth/Messaging/CachedDirectWebResponse.cs +++ b/src/DotNetOpenAuth/Messaging/CachedDirectWebResponse.cs @@ -15,6 +15,7 @@ namespace DotNetOpenAuth.Messaging { /// <summary> /// Cached details on the response from a direct web request to a remote party. /// </summary> + [ContractVerification(true)] [DebuggerDisplay("{Status} {ContentType.MediaType}, length: {ResponseStream.Length}")] internal class CachedDirectWebResponse : IncomingWebResponse { /// <summary> @@ -36,6 +37,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="maximumBytesToRead">The maximum bytes to read.</param> internal CachedDirectWebResponse(Uri requestUri, HttpWebResponse response, int maximumBytesToRead) : base(requestUri, response) { + Contract.Requires<ArgumentNullException>(requestUri != null); + Contract.Requires<ArgumentNullException>(response != null); this.responseStream = CacheNetworkStreamAndClose(response, maximumBytesToRead); // BUGBUG: if the response was exactly maximumBytesToRead, we'll incorrectly believe it was truncated. @@ -54,8 +57,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="responseStream">The response stream.</param> internal CachedDirectWebResponse(Uri requestUri, Uri responseUri, WebHeaderCollection headers, HttpStatusCode statusCode, string contentType, string contentEncoding, MemoryStream responseStream) : base(requestUri, responseUri, headers, statusCode, contentType, contentEncoding) { - ErrorUtilities.VerifyArgumentNotNull(responseStream, "responseStream"); - + Contract.Requires<ArgumentNullException>(requestUri != null); + Contract.Requires<ArgumentNullException>(responseStream != null); this.responseStream = responseStream; } @@ -149,7 +152,7 @@ namespace DotNetOpenAuth.Messaging { /// <param name="maximumBytesToRead">The maximum bytes to cache.</param> /// <returns>The seekable Stream instance that contains a copy of what was returned in the HTTP response.</returns> private static MemoryStream CacheNetworkStreamAndClose(HttpWebResponse response, int maximumBytesToRead) { - ErrorUtilities.VerifyArgumentNotNull(response, "response"); + Contract.Requires<ArgumentNullException>(response != null); // Now read and cache the network stream Stream networkStream = response.GetResponseStream(); diff --git a/src/DotNetOpenAuth/Messaging/Channel.cs b/src/DotNetOpenAuth/Messaging/Channel.cs index 2e0f1a8..718fac8 100644 --- a/src/DotNetOpenAuth/Messaging/Channel.cs +++ b/src/DotNetOpenAuth/Messaging/Channel.cs @@ -28,15 +28,15 @@ namespace DotNetOpenAuth.Messaging { [ContractClass(typeof(ChannelContract))] public abstract class Channel : IDisposable { /// <summary> - /// The content-type used on HTTP POST requests where the POST entity is a - /// URL-encoded series of key=value pairs. + /// The encoding to use when writing out POST entity strings. /// </summary> - protected internal const string HttpFormUrlEncoded = "application/x-www-form-urlencoded"; + internal static readonly Encoding PostEntityEncoding = new UTF8Encoding(false); /// <summary> - /// The encoding to use when writing out POST entity strings. + /// The content-type used on HTTP POST requests where the POST entity is a + /// URL-encoded series of key=value pairs. /// </summary> - private static readonly Encoding PostEntityEncoding = new UTF8Encoding(false); + protected internal const string HttpFormUrlEncoded = "application/x-www-form-urlencoded"; /// <summary> /// The maximum allowable size for a 301 Redirect response before we send @@ -112,8 +112,7 @@ namespace DotNetOpenAuth.Messaging { /// </param> /// <param name="bindingElements">The binding elements to use in sending and receiving messages.</param> protected Channel(IMessageFactory messageTypeProvider, params IChannelBindingElement[] bindingElements) { - Contract.Requires(messageTypeProvider != null); - ErrorUtilities.VerifyArgumentNotNull(messageTypeProvider, "messageTypeProvider"); + Contract.Requires<ArgumentNullException>(messageTypeProvider != null); this.messageTypeProvider = messageTypeProvider; this.WebRequestHandler = new StandardWebRequestHandler(); @@ -146,13 +145,11 @@ namespace DotNetOpenAuth.Messaging { /// </summary> internal MessageDescriptionCollection MessageDescriptions { get { - Contract.Ensures(Contract.Result<MessageDescriptionCollection>() != null); return this.messageDescriptions; } set { - Contract.Requires(value != null); - ErrorUtilities.VerifyArgumentNotNull(value, "value"); + Contract.Requires<ArgumentNullException>(value != null); this.messageDescriptions = value; } } @@ -180,7 +177,11 @@ namespace DotNetOpenAuth.Messaging { /// Gets the binding elements used by this channel, in the order applied to incoming messages. /// </summary> protected internal ReadOnlyCollection<IChannelBindingElement> IncomingBindingElements { - get { return this.incomingBindingElements.AsReadOnly(); } + get { + Contract.Ensures(Contract.Result<ReadOnlyCollection<IChannelBindingElement>>().All(be => be.Channel != null)); + Contract.Ensures(Contract.Result<ReadOnlyCollection<IChannelBindingElement>>().All(be => be != null)); + return this.incomingBindingElements.AsReadOnly(); + } } /// <summary> @@ -209,8 +210,7 @@ namespace DotNetOpenAuth.Messaging { } set { - Contract.Requires(value != null); - ErrorUtilities.VerifyArgumentNotNull(value, "value"); + Contract.Requires<ArgumentNullException>(value != null); this.cachePolicy = value; } } @@ -226,8 +226,8 @@ namespace DotNetOpenAuth.Messaging { /// Requires an HttpContext.Current context. /// </remarks> public void Send(IProtocolMessage message) { - Contract.Requires(HttpContext.Current != null); - Contract.Requires(message != null); + Contract.Requires<InvalidOperationException>(HttpContext.Current != null, MessagingStrings.CurrentHttpContextRequired); + Contract.Requires<ArgumentNullException>(message != null); this.PrepareResponse(message).Send(); } @@ -238,9 +238,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="message">The one-way message to send</param> /// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns> public OutgoingWebResponse PrepareResponse(IProtocolMessage message) { - Contract.Requires(message != null); + Contract.Requires<ArgumentNullException>(message != null); Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); - ErrorUtilities.VerifyArgumentNotNull(message, "message"); this.ProcessOutgoingMessage(message); Logger.Channel.DebugFormat("Sending message: {0}", message.GetType().Name); @@ -309,7 +308,7 @@ namespace DotNetOpenAuth.Messaging { /// <exception cref="ProtocolException">Thrown when a request message of an unexpected type is received.</exception> public bool TryReadFromRequest<TRequest>(HttpRequestInfo httpRequest, out TRequest request) where TRequest : class, IProtocolMessage { - Contract.Requires(httpRequest != null); + Contract.Requires<ArgumentNullException>(httpRequest != null); Contract.Ensures(Contract.Result<bool>() == (Contract.ValueAtReturn<TRequest>(out request) != null)); IProtocolMessage untypedRequest = this.ReadFromRequest(httpRequest); @@ -350,7 +349,7 @@ namespace DotNetOpenAuth.Messaging { [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "This returns and verifies the appropriate message type.")] public TRequest ReadFromRequest<TRequest>(HttpRequestInfo httpRequest) where TRequest : class, IProtocolMessage { - Contract.Requires(httpRequest != null); + Contract.Requires<ArgumentNullException>(httpRequest != null); TRequest request; if (this.TryReadFromRequest<TRequest>(httpRequest, out request)) { return request; @@ -365,8 +364,7 @@ namespace DotNetOpenAuth.Messaging { /// <param name="httpRequest">The request to search for an embedded message.</param> /// <returns>The deserialized message, if one is found. Null otherwise.</returns> public IDirectedProtocolMessage ReadFromRequest(HttpRequestInfo httpRequest) { - Contract.Requires(httpRequest != null); - ErrorUtilities.VerifyArgumentNotNull(httpRequest, "httpRequest"); + Contract.Requires<ArgumentNullException>(httpRequest != null); if (Logger.Channel.IsInfoEnabled && httpRequest.UrlBeforeRewriting != null) { Logger.Channel.InfoFormat("Scanning incoming request for messages: {0}", httpRequest.UrlBeforeRewriting.AbsoluteUri); @@ -393,7 +391,7 @@ namespace DotNetOpenAuth.Messaging { [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "This returns and verifies the appropriate message type.")] public TResponse Request<TResponse>(IDirectedProtocolMessage requestMessage) where TResponse : class, IProtocolMessage { - Contract.Requires(requestMessage != null); + Contract.Requires<ArgumentNullException>(requestMessage != null); Contract.Ensures(Contract.Result<TResponse>() != null); IProtocolMessage response = this.Request(requestMessage); @@ -412,8 +410,7 @@ namespace DotNetOpenAuth.Messaging { /// <returns>The remote party's response. Guaranteed to never be null.</returns> /// <exception cref="ProtocolException">Thrown if the response does not include a protocol message.</exception> public IProtocolMessage Request(IDirectedProtocolMessage requestMessage) { - Contract.Requires(requestMessage != null); - ErrorUtilities.VerifyArgumentNotNull(requestMessage, "requestMessage"); + Contract.Requires<ArgumentNullException>(requestMessage != null); this.ProcessOutgoingMessage(requestMessage); Logger.Channel.DebugFormat("Sending {0} request.", requestMessage.GetType().Name); @@ -448,9 +445,14 @@ namespace DotNetOpenAuth.Messaging { /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception> [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Costly call should not be a property.")] protected internal virtual HttpRequestInfo GetRequestFromContext() { + Contract.Requires<InvalidOperationException>(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); Contract.Ensures(Contract.Result<HttpRequestInfo>() != null); - ErrorUtilities.VerifyHttpContext(); + Contract.Ensures(Contract.Result<HttpRequestInfo>().Url != null); + Contract.Ensures(Contract.Result<HttpRequestInfo>().RawUrl != null); + Contract.Ensures(Contract.Result<HttpRequestInfo>().UrlBeforeRewriting != null); + Contract.Assume(HttpContext.Current.Request.Url != null); + Contract.Assume(HttpContext.Current.Request.RawUrl != null); return new HttpRequestInfo(HttpContext.Current.Request); } @@ -492,8 +494,7 @@ namespace DotNetOpenAuth.Messaging { /// </summary> /// <param name="message">The message about to be encoded and sent.</param> protected virtual void OnSending(IProtocolMessage message) { - Contract.Requires(message != null); - ErrorUtilities.VerifyArgumentNotNull(message, "message"); + Contract.Requires<ArgumentNullException>(message != null); var sending = this.Sending; if (sending != null) { @@ -508,8 +509,7 @@ namespace DotNetOpenAuth.Messaging { /// <returns>The response to the web request.</returns> /// <exception cref="ProtocolException">Thrown on network or protocol errors.</exception> protected virtual IncomingWebResponse GetDirectResponse(HttpWebRequest webRequest) { - Contract.Requires(webRequest != null); - ErrorUtilities.VerifyArgumentNotNull(webRequest, "webRequest"); + Contract.Requires<ArgumentNullException>(webRequest != null); return this.WebRequestHandler.GetResponse(webRequest); } @@ -524,7 +524,9 @@ namespace DotNetOpenAuth.Messaging { /// this method to eliminate all use of an HTTP transport. /// </remarks> protected virtual IProtocolMessage RequestCore(IDirectedProtocolMessage request) { - Contract.Requires(request != null); + Contract.Requires<ArgumentNullException>(request != null); + Contract.Requires<ArgumentException>(request.Recipient != null, MessagingStrings.DirectedMessageMissingRecipient); + HttpWebRequest webRequest = this.CreateHttpRequest(request); IDictionary<string, string> responseFields; IDirectResponseProtocolMessage responseMessage; @@ -535,6 +537,9 @@ namespace DotNetOpenAuth.Messaging { } responseFields = this.ReadFromResponseCore(response); + if (responseFields == null) { + return null; + } responseMessage = this.MessageFactory.GetNewResponseMessage(request, responseFields); if (responseMessage == null) { @@ -564,8 +569,7 @@ namespace DotNetOpenAuth.Messaging { /// <param name="request">The request to search for an embedded message.</param> /// <returns>The deserialized message, if one is found. Null otherwise.</returns> protected virtual IDirectedProtocolMessage ReadFromRequestCore(HttpRequestInfo request) { - Contract.Requires(request != null); - ErrorUtilities.VerifyArgumentNotNull(request, "request"); + Contract.Requires<ArgumentNullException>(request != null); Logger.Channel.DebugFormat("Incoming HTTP request: {0} {1}", request.HttpMethod, request.UrlBeforeRewriting.AbsoluteUri); @@ -586,8 +590,7 @@ namespace DotNetOpenAuth.Messaging { /// <param name="recipient">Information about where the message was directed. Null for direct response messages.</param> /// <returns>The deserialized message, or null if no message could be recognized in the provided data.</returns> protected virtual IProtocolMessage Receive(Dictionary<string, string> fields, MessageReceivingEndpoint recipient) { - Contract.Requires(fields != null); - ErrorUtilities.VerifyArgumentNotNull(fields, "fields"); + Contract.Requires<ArgumentNullException>(fields != null); IProtocolMessage message = this.MessageFactory.GetNewRequestMessage(recipient, fields); @@ -613,11 +616,13 @@ namespace DotNetOpenAuth.Messaging { /// <param name="message">The message to send.</param> /// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns> protected virtual OutgoingWebResponse PrepareIndirectResponse(IDirectedProtocolMessage message) { - Contract.Requires(message != null && message.Recipient != null); + Contract.Requires<ArgumentNullException>(message != null); + Contract.Requires<ArgumentException>(message.Recipient != null, MessagingStrings.DirectedMessageMissingRecipient); Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); - ErrorUtilities.VerifyArgumentNotNull(message, "message"); + Contract.Assert(message != null && message.Recipient != null); var messageAccessor = this.MessageDescriptions.GetAccessor(message); + Contract.Assert(message != null && message.Recipient != null); var fields = messageAccessor.Serialize(); // First try creating a 301 redirect, and fallback to a form POST @@ -637,15 +642,13 @@ namespace DotNetOpenAuth.Messaging { /// <param name="message">The message to forward.</param> /// <param name="fields">The pre-serialized fields from the message.</param> /// <returns>The encoded HTTP response.</returns> + [Pure] protected virtual OutgoingWebResponse Create301RedirectResponse(IDirectedProtocolMessage message, IDictionary<string, string> fields) { - Contract.Requires(message != null && message.Recipient != null); - Contract.Requires(fields != null); + Contract.Requires<ArgumentNullException>(message != null); + Contract.Requires<ArgumentException>(message.Recipient != null, MessagingStrings.DirectedMessageMissingRecipient); + Contract.Requires<ArgumentNullException>(fields != null); Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); - ErrorUtilities.VerifyArgumentNotNull(message, "message"); - ErrorUtilities.VerifyArgumentNamed(message.Recipient != null, "message", MessagingStrings.DirectedMessageMissingRecipient); - ErrorUtilities.VerifyArgumentNotNull(fields, "fields"); - WebHeaderCollection headers = new WebHeaderCollection(); UriBuilder builder = new UriBuilder(message.Recipient); MessagingUtilities.AppendQueryArgs(builder, fields); @@ -669,12 +672,10 @@ namespace DotNetOpenAuth.Messaging { /// <param name="fields">The pre-serialized fields from the message.</param> /// <returns>The encoded HTTP response.</returns> protected virtual OutgoingWebResponse CreateFormPostResponse(IDirectedProtocolMessage message, IDictionary<string, string> fields) { - Contract.Requires(message != null && message.Recipient != null); - Contract.Requires(fields != null); + Contract.Requires<ArgumentNullException>(message != null); + Contract.Requires<ArgumentException>(message.Recipient != null, MessagingStrings.DirectedMessageMissingRecipient); + Contract.Requires<ArgumentNullException>(fields != null); Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); - ErrorUtilities.VerifyArgumentNotNull(message, "message"); - ErrorUtilities.VerifyArgumentNamed(message.Recipient != null, "message", MessagingStrings.DirectedMessageMissingRecipient); - ErrorUtilities.VerifyArgumentNotNull(fields, "fields"); WebHeaderCollection headers = new WebHeaderCollection(); headers.Add(HttpResponseHeader.ContentType, "text/html"); @@ -719,7 +720,8 @@ namespace DotNetOpenAuth.Messaging { /// is overridden and does not require this method. /// </remarks> protected virtual HttpWebRequest CreateHttpRequest(IDirectedProtocolMessage request) { - Contract.Requires(request != null); + Contract.Requires<ArgumentNullException>(request != null); + Contract.Requires<ArgumentException>(request.Recipient != null, MessagingStrings.DirectedMessageMissingRecipient); Contract.Ensures(Contract.Result<HttpWebRequest>() != null); throw new NotImplementedException(); } @@ -744,8 +746,7 @@ namespace DotNetOpenAuth.Messaging { /// except when sending ONE WAY request messages. /// </remarks> protected void ProcessOutgoingMessage(IProtocolMessage message) { - Contract.Requires(message != null); - ErrorUtilities.VerifyArgumentNotNull(message, "message"); + Contract.Requires<ArgumentNullException>(message != null); Logger.Channel.DebugFormat("Preparing to send {0} ({1}) message.", message.GetType().Name, message.Version); this.OnSending(message); @@ -758,6 +759,7 @@ namespace DotNetOpenAuth.Messaging { MessageProtections appliedProtection = MessageProtections.None; foreach (IChannelBindingElement bindingElement in this.outgoingBindingElements) { + Contract.Assume(bindingElement.Channel != null); MessageProtections? elementProtection = bindingElement.ProcessOutgoingMessage(message); if (elementProtection.HasValue) { Logger.Bindings.DebugFormat("Binding element {0} applied to message.", bindingElement.GetType().FullName); @@ -803,8 +805,8 @@ namespace DotNetOpenAuth.Messaging { /// This method satisfies OAuth 1.0 section 5.2, item #3. /// </remarks> protected virtual HttpWebRequest InitializeRequestAsGet(IDirectedProtocolMessage requestMessage) { - Contract.Requires(requestMessage != null); - ErrorUtilities.VerifyArgumentNotNull(requestMessage, "requestMessage"); + Contract.Requires<ArgumentNullException>(requestMessage != null); + Contract.Requires<ArgumentException>(requestMessage.Recipient != null, MessagingStrings.DirectedMessageMissingRecipient); var messageAccessor = this.MessageDescriptions.GetAccessor(requestMessage); var fields = messageAccessor.Serialize(); @@ -827,9 +829,8 @@ namespace DotNetOpenAuth.Messaging { /// This method satisfies OAuth 1.0 section 5.2, item #2 and OpenID 2.0 section 4.1.2. /// </remarks> protected virtual HttpWebRequest InitializeRequestAsPost(IDirectedProtocolMessage requestMessage) { - Contract.Requires(requestMessage != null); + Contract.Requires<ArgumentNullException>(requestMessage != null); Contract.Ensures(Contract.Result<HttpWebRequest>() != null); - ErrorUtilities.VerifyArgumentNotNull(requestMessage, "requestMessage"); var messageAccessor = this.MessageDescriptions.GetAccessor(requestMessage); var fields = messageAccessor.Serialize(); @@ -843,6 +844,40 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Prepares to send a request to the Service Provider as the query string in a PUT request. + /// </summary> + /// <param name="requestMessage">The message to be transmitted to the ServiceProvider.</param> + /// <returns>The web request ready to send.</returns> + /// <remarks> + /// This method is simply a standard HTTP PUT request with the message parts serialized to the query string. + /// </remarks> + protected virtual HttpWebRequest InitializeRequestAsPut(IDirectedProtocolMessage requestMessage) { + Contract.Requires<ArgumentNullException>(requestMessage != null); + Contract.Ensures(Contract.Result<HttpWebRequest>() != null); + + HttpWebRequest request = this.InitializeRequestAsGet(requestMessage); + request.Method = "PUT"; + return request; + } + + /// <summary> + /// Prepares to send a request to the Service Provider as the query string in a DELETE request. + /// </summary> + /// <param name="requestMessage">The message to be transmitted to the ServiceProvider.</param> + /// <returns>The web request ready to send.</returns> + /// <remarks> + /// This method is simply a standard HTTP DELETE request with the message parts serialized to the query string. + /// </remarks> + protected virtual HttpWebRequest InitializeRequestAsDelete(IDirectedProtocolMessage requestMessage) { + Contract.Requires<ArgumentNullException>(requestMessage != null); + Contract.Ensures(Contract.Result<HttpWebRequest>() != null); + + HttpWebRequest request = this.InitializeRequestAsGet(requestMessage); + request.Method = "DELETE"; + return request; + } + + /// <summary> /// Sends the given parameters in the entity stream of an HTTP request. /// </summary> /// <param name="httpRequest">The HTTP request.</param> @@ -852,10 +887,8 @@ namespace DotNetOpenAuth.Messaging { /// the request stream, but does not call <see cref="HttpWebRequest.GetResponse"/>. /// </remarks> protected void SendParametersInEntity(HttpWebRequest httpRequest, IDictionary<string, string> fields) { - Contract.Requires(httpRequest != null); - Contract.Requires(fields != null); - ErrorUtilities.VerifyArgumentNotNull(httpRequest, "httpRequest"); - ErrorUtilities.VerifyArgumentNotNull(fields, "fields"); + Contract.Requires<ArgumentNullException>(httpRequest != null); + Contract.Requires<ArgumentNullException>(fields != null); httpRequest.ContentType = HttpFormUrlEncoded; @@ -889,7 +922,7 @@ namespace DotNetOpenAuth.Messaging { /// This can be due to tampering, replay attack or expiration, among other things. /// </exception> protected virtual void ProcessIncomingMessage(IProtocolMessage message) { - Contract.Requires(message != null); + Contract.Requires<ArgumentNullException>(message != null); if (Logger.Channel.IsInfoEnabled) { var messageAccessor = this.MessageDescriptions.GetAccessor(message); @@ -902,7 +935,8 @@ namespace DotNetOpenAuth.Messaging { } MessageProtections appliedProtection = MessageProtections.None; - foreach (IChannelBindingElement bindingElement in this.incomingBindingElements) { + foreach (IChannelBindingElement bindingElement in this.IncomingBindingElements) { + Contract.Assume(bindingElement.Channel != null); // CC bug: this.IncomingBindingElements ensures this... why must we assume it here? MessageProtections? elementProtection = bindingElement.ProcessIncomingMessage(message); if (elementProtection.HasValue) { Logger.Bindings.DebugFormat("Binding element {0} applied to message.", bindingElement.GetType().FullName); @@ -963,10 +997,8 @@ namespace DotNetOpenAuth.Messaging { /// </remarks> /// <exception cref="ArgumentException">Thrown if a binding element is new or missing in one of the ordered lists.</exception> protected void CustomizeBindingElementOrder(IEnumerable<IChannelBindingElement> outgoingOrder, IEnumerable<IChannelBindingElement> incomingOrder) { - Contract.Requires(outgoingOrder != null); - Contract.Requires(incomingOrder != null); - ErrorUtilities.VerifyArgumentNotNull(outgoingOrder, "outgoingOrder"); - ErrorUtilities.VerifyArgumentNotNull(incomingOrder, "incomingOrder"); + Contract.Requires<ArgumentNullException>(outgoingOrder != null); + Contract.Requires<ArgumentNullException>(incomingOrder != null); ErrorUtilities.VerifyArgument(this.IsBindingElementOrderValid(outgoingOrder), MessagingStrings.InvalidCustomBindingElementOrder); ErrorUtilities.VerifyArgument(this.IsBindingElementOrderValid(incomingOrder), MessagingStrings.InvalidCustomBindingElementOrder); @@ -976,6 +1008,18 @@ namespace DotNetOpenAuth.Messaging { this.incomingBindingElements.AddRange(incomingOrder); } +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + protected void ObjectInvariant() { + Contract.Invariant(this.MessageDescriptions != null); + } +#endif + /// <summary> /// Ensures a consistent and secure set of binding elements and /// sorts them as necessary for a valid sequence of operations. @@ -984,14 +1028,12 @@ namespace DotNetOpenAuth.Messaging { /// <returns>The properly ordered list of elements.</returns> /// <exception cref="ProtocolException">Thrown when the binding elements are incomplete or inconsistent with each other.</exception> private static IEnumerable<IChannelBindingElement> ValidateAndPrepareBindingElements(IEnumerable<IChannelBindingElement> elements) { - Contract.Requires(elements == null || elements.All(e => e != null)); + Contract.Requires<ArgumentException>(elements == null || elements.All(e => e != null)); Contract.Ensures(Contract.Result<IEnumerable<IChannelBindingElement>>() != null); if (elements == null) { return new IChannelBindingElement[0]; } - ErrorUtilities.VerifyArgumentNamed(!elements.Contains(null), "elements", MessagingStrings.SequenceContainsNullElement); - // Filter the elements between the mere transforming ones and the protection ones. var transformationElements = new List<IChannelBindingElement>( elements.Where(element => element.Protection == MessageProtections.None)); @@ -1047,7 +1089,7 @@ namespace DotNetOpenAuth.Messaging { /// Thrown when any required message part does not have a value. /// </exception> private void EnsureValidMessageParts(IProtocolMessage message) { - Contract.Requires(message != null); + Contract.Requires<ArgumentNullException>(message != null); MessageDictionary dictionary = this.MessageDescriptions.GetAccessor(message); MessageDescription description = this.MessageDescriptions.Get(message); description.EnsureMessagePartsPassBasicValidation(dictionary); @@ -1063,8 +1105,7 @@ namespace DotNetOpenAuth.Messaging { /// </returns> [Pure] private bool IsBindingElementOrderValid(IEnumerable<IChannelBindingElement> order) { - Contract.Requires(order != null); - ErrorUtilities.VerifyArgumentNotNull(order, "order"); + Contract.Requires<ArgumentNullException>(order != null); // Check that the same number of binding elements are defined. if (order.Count() != this.OutgoingBindingElements.Count) { diff --git a/src/DotNetOpenAuth/Messaging/ChannelContract.cs b/src/DotNetOpenAuth/Messaging/ChannelContract.cs index 551d7c4..9b85d28 100644 --- a/src/DotNetOpenAuth/Messaging/ChannelContract.cs +++ b/src/DotNetOpenAuth/Messaging/ChannelContract.cs @@ -30,7 +30,7 @@ namespace DotNetOpenAuth.Messaging { /// </returns> /// <exception cref="ProtocolException">Thrown when the response is not valid.</exception> protected override IDictionary<string, string> ReadFromResponseCore(IncomingWebResponse response) { - Contract.Requires(response != null); + Contract.Requires<ArgumentNullException>(response != null); throw new NotImplementedException(); } @@ -46,7 +46,7 @@ namespace DotNetOpenAuth.Messaging { /// This method implements spec V1.0 section 5.3. /// </remarks> protected override OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response) { - Contract.Requires(response != null); + Contract.Requires<ArgumentNullException>(response != null); Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); throw new NotImplementedException(); } diff --git a/src/DotNetOpenAuth/Messaging/ChannelEventArgs.cs b/src/DotNetOpenAuth/Messaging/ChannelEventArgs.cs index 29310dc..1e71bf2 100644 --- a/src/DotNetOpenAuth/Messaging/ChannelEventArgs.cs +++ b/src/DotNetOpenAuth/Messaging/ChannelEventArgs.cs @@ -6,6 +6,7 @@ namespace DotNetOpenAuth.Messaging { using System; + using System.Diagnostics.Contracts; /// <summary> /// The data packet sent with Channel events. @@ -16,7 +17,7 @@ namespace DotNetOpenAuth.Messaging { /// </summary> /// <param name="message">The message behind the fired event..</param> internal ChannelEventArgs(IProtocolMessage message) { - ErrorUtilities.VerifyArgumentNotNull(message, "message"); + Contract.Requires<ArgumentNullException>(message != null); this.Message = message; } diff --git a/src/DotNetOpenAuth/Messaging/EmptyDictionary.cs b/src/DotNetOpenAuth/Messaging/EmptyDictionary.cs index 22c947a..062fcf3 100644 --- a/src/DotNetOpenAuth/Messaging/EmptyDictionary.cs +++ b/src/DotNetOpenAuth/Messaging/EmptyDictionary.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.Messaging { using System; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; using System.Linq; /// <summary> @@ -153,6 +154,7 @@ namespace DotNetOpenAuth.Messaging { /// <exception cref="T:System.NotSupportedException"> /// The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only. /// </exception> + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Code Contracts generates the code FxCop is complaining about.")] public void Add(KeyValuePair<TKey, TValue> item) { throw new NotSupportedException(); } diff --git a/src/DotNetOpenAuth/Messaging/EmptyList.cs b/src/DotNetOpenAuth/Messaging/EmptyList.cs index b418623..2261684 100644 --- a/src/DotNetOpenAuth/Messaging/EmptyList.cs +++ b/src/DotNetOpenAuth/Messaging/EmptyList.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.Messaging { using System; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; /// <summary> /// An empty, read-only list. @@ -98,6 +99,7 @@ namespace DotNetOpenAuth.Messaging { /// <exception cref="T:System.NotSupportedException"> /// The <see cref="T:System.Collections.Generic.IList`1"/> is read-only. /// </exception> + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Code Contracts generates the code FxCop is complaining about.")] public void RemoveAt(int index) { throw new ArgumentOutOfRangeException("index"); } @@ -113,6 +115,7 @@ namespace DotNetOpenAuth.Messaging { /// <exception cref="T:System.NotSupportedException"> /// The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only. /// </exception> + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Code Contracts generates the code FxCop is complaining about.")] public void Add(T item) { throw new NotSupportedException(); } diff --git a/src/DotNetOpenAuth/Messaging/EnumerableCache.cs b/src/DotNetOpenAuth/Messaging/EnumerableCache.cs index d343410..6639de1 100644 --- a/src/DotNetOpenAuth/Messaging/EnumerableCache.cs +++ b/src/DotNetOpenAuth/Messaging/EnumerableCache.cs @@ -9,6 +9,7 @@ namespace DotNetOpenAuth.Messaging { using System; using System.Collections; using System.Collections.Generic; + using System.Diagnostics.Contracts; /// <summary> /// Extension methods for <see cref="IEnumerable<T>"/> types. @@ -80,9 +81,7 @@ namespace DotNetOpenAuth.Messaging { /// </summary> /// <param name="generator">The generator.</param> internal EnumerableCache(IEnumerable<T> generator) { - if (generator == null) { - throw new ArgumentNullException("generator"); - } + Contract.Requires<ArgumentNullException>(generator != null); this.generator = generator; } @@ -140,9 +139,7 @@ namespace DotNetOpenAuth.Messaging { /// </summary> /// <param name="parent">The parent cached enumerable whose GetEnumerator method is calling this constructor.</param> internal EnumeratorCache(EnumerableCache<T> parent) { - if (parent == null) { - throw new ArgumentNullException("parent"); - } + Contract.Requires<ArgumentNullException>(parent != null); this.parent = parent; } diff --git a/src/DotNetOpenAuth/Messaging/ErrorUtilities.cs b/src/DotNetOpenAuth/Messaging/ErrorUtilities.cs index 0a60b19..b560fdc 100644 --- a/src/DotNetOpenAuth/Messaging/ErrorUtilities.cs +++ b/src/DotNetOpenAuth/Messaging/ErrorUtilities.cs @@ -26,7 +26,7 @@ namespace DotNetOpenAuth.Messaging { /// <returns>The newly constructed (unthrown) exception.</returns> [Pure] internal static Exception Wrap(Exception inner, string errorMessage, params object[] args) { - Contract.Requires(args != null); + Contract.Requires<ArgumentNullException>(args != null); Contract.Assume(errorMessage != null); return new ProtocolException(string.Format(CultureInfo.CurrentCulture, errorMessage, args), inner); } @@ -73,7 +73,7 @@ namespace DotNetOpenAuth.Messaging { /// <exception cref="InternalErrorException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> [Pure] internal static void VerifyInternal(bool condition, string errorMessage, params object[] args) { - Contract.Requires(args != null); + Contract.Requires<ArgumentNullException>(args != null); Contract.Ensures(condition); Contract.EnsuresOnThrow<InternalErrorException>(!condition); Contract.Assume(errorMessage != null); @@ -122,7 +122,7 @@ namespace DotNetOpenAuth.Messaging { /// <exception cref="NotSupportedException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> [Pure] internal static void VerifySupported(bool condition, string errorMessage, params object[] args) { - Contract.Requires(args != null); + Contract.Requires<ArgumentNullException>(args != null); Contract.Ensures(condition); Contract.EnsuresOnThrow<NotSupportedException>(!condition); Contract.Assume(errorMessage != null); @@ -140,7 +140,7 @@ namespace DotNetOpenAuth.Messaging { /// <exception cref="InvalidOperationException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> [Pure] internal static void VerifyOperation(bool condition, string errorMessage, params object[] args) { - Contract.Requires(args != null); + Contract.Requires<ArgumentNullException>(args != null); Contract.Ensures(condition); Contract.EnsuresOnThrow<InvalidOperationException>(!condition); Contract.Assume(errorMessage != null); @@ -160,7 +160,7 @@ namespace DotNetOpenAuth.Messaging { /// <exception cref="InfoCard.InformationCardException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> [Pure] internal static void VerifyInfoCard(bool condition, string errorMessage, params object[] args) { - Contract.Requires(args != null); + Contract.Requires<ArgumentNullException>(args != null); Contract.Ensures(condition); Contract.EnsuresOnThrow<InfoCard.InformationCardException>(!condition); Contract.Assume(errorMessage != null); @@ -179,7 +179,7 @@ namespace DotNetOpenAuth.Messaging { /// <exception cref="HostErrorException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> [Pure] internal static void VerifyHost(bool condition, string errorMessage, params object[] args) { - Contract.Requires(args != null); + Contract.Requires<ArgumentNullException>(args != null); Contract.Ensures(condition); Contract.EnsuresOnThrow<ProtocolException>(!condition); Contract.Assume(errorMessage != null); @@ -198,7 +198,8 @@ namespace DotNetOpenAuth.Messaging { /// <exception cref="ProtocolException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> [Pure] internal static void VerifyProtocol(bool condition, IProtocolMessage faultedMessage, string errorMessage, params object[] args) { - Contract.Requires(args != null); + Contract.Requires<ArgumentNullException>(args != null); + Contract.Requires<ArgumentNullException>(faultedMessage != null); Contract.Ensures(condition); Contract.EnsuresOnThrow<ProtocolException>(!condition); Contract.Assume(errorMessage != null); @@ -216,7 +217,7 @@ namespace DotNetOpenAuth.Messaging { /// <exception cref="ProtocolException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> [Pure] internal static void VerifyProtocol(bool condition, string message, params object[] args) { - Contract.Requires(args != null); + Contract.Requires<ArgumentNullException>(args != null); Contract.Ensures(condition); Contract.EnsuresOnThrow<ProtocolException>(!condition); Contract.Assume(message != null); @@ -248,7 +249,7 @@ namespace DotNetOpenAuth.Messaging { /// <exception cref="ProtocolException">Always thrown.</exception> [Pure] internal static Exception ThrowProtocol(string message, params object[] args) { - Contract.Requires(args != null); + Contract.Requires<ArgumentNullException>(args != null); Contract.Assume(message != null); VerifyProtocol(false, message, args); @@ -264,7 +265,7 @@ namespace DotNetOpenAuth.Messaging { /// <returns>Nothing. It's just here so the caller can throw this method for C# compilation check.</returns> [Pure] internal static Exception ThrowFormat(string message, params object[] args) { - Contract.Requires(args != null); + Contract.Requires<ArgumentNullException>(args != null); Contract.Assume(message != null); throw new FormatException(string.Format(CultureInfo.CurrentCulture, message, args)); } @@ -278,7 +279,7 @@ namespace DotNetOpenAuth.Messaging { /// <exception cref="FormatException">Thrown when <paramref name="condition"/> is <c>false</c>.</exception> [Pure] internal static void VerifyFormat(bool condition, string message, params object[] args) { - Contract.Requires(args != null); + Contract.Requires<ArgumentNullException>(args != null); Contract.Ensures(condition); Contract.EnsuresOnThrow<FormatException>(!condition); Contract.Assume(message != null); @@ -296,7 +297,7 @@ namespace DotNetOpenAuth.Messaging { /// <exception cref="ArgumentException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> [Pure] internal static void VerifyArgument(bool condition, string message, params object[] args) { - Contract.Requires(args != null); + Contract.Requires<ArgumentNullException>(args != null); Contract.Ensures(condition); Contract.EnsuresOnThrow<ArgumentException>(!condition); Contract.Assume(message != null); @@ -308,38 +309,6 @@ namespace DotNetOpenAuth.Messaging { /// <summary> /// Verifies something about the argument supplied to a method. /// </summary> - /// <param name="condition">The condition that must evaluate to true to avoid an exception.</param> - /// <param name="parameterName">Name of the parameter.</param> - /// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> - [Pure] - internal static void VerifyArgumentInRange(bool condition, string parameterName) { - Contract.Requires(condition); - if (!condition) { - throw new ArgumentOutOfRangeException(parameterName); - } - } - - /// <summary> - /// Verifies something about the argument supplied to a method. - /// </summary> - /// <param name="condition">The condition that must evaluate to true to avoid an exception.</param> - /// <param name="parameterName">Name of the parameter.</param> - /// <param name="message">The unformatted message.</param> - /// <param name="args">The string formatting arguments.</param> - /// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> - [Pure] - internal static void VerifyArgumentInRange(bool condition, string parameterName, string message, params object[] args) { - Contract.Requires(condition); - Contract.Requires(args != null); - Contract.Assume(message != null); - if (!condition) { - throw new ArgumentOutOfRangeException(parameterName, string.Format(CultureInfo.CurrentCulture, message, args)); - } - } - - /// <summary> - /// Verifies something about the argument supplied to a method. - /// </summary> /// <param name="parameterName">Name of the parameter.</param> /// <param name="message">The message to use in the exception if the condition is false.</param> /// <param name="args">The string formatting arguments, if any.</param> @@ -347,7 +316,7 @@ namespace DotNetOpenAuth.Messaging { /// <exception cref="ArgumentException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> [Pure] internal static Exception ThrowArgumentNamed(string parameterName, string message, params object[] args) { - Contract.Requires(args != null); + Contract.Requires<ArgumentNullException>(args != null); Contract.Assume(message != null); throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, message, args), parameterName); } @@ -362,7 +331,7 @@ namespace DotNetOpenAuth.Messaging { /// <exception cref="ArgumentException">Thrown if <paramref name="condition"/> evaluates to <c>false</c>.</exception> [Pure] internal static void VerifyArgumentNamed(bool condition, string parameterName, string message, params object[] args) { - Contract.Requires(args != null); + Contract.Requires<ArgumentNullException>(args != null); Contract.Ensures(condition); Contract.EnsuresOnThrow<ArgumentException>(!condition); Contract.Assume(message != null); @@ -379,7 +348,8 @@ namespace DotNetOpenAuth.Messaging { /// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is null.</exception> [Pure] internal static void VerifyArgumentNotNull(object value, string paramName) { - Contract.Requires(value != null); + Contract.Ensures(value != null); + Contract.EnsuresOnThrow<ArgumentNullException>(value == null); if (value == null) { throw new ArgumentNullException(paramName); } @@ -394,7 +364,8 @@ namespace DotNetOpenAuth.Messaging { /// <exception cref="ArgumentException">Thrown if <paramref name="value"/> has zero length.</exception> [Pure] internal static void VerifyNonZeroLength(string value, string paramName) { - Contract.Requires((value != null && value.Length > 0) || !string.IsNullOrEmpty(value)); + Contract.Ensures((value != null && value.Length > 0) && !string.IsNullOrEmpty(value)); + Contract.EnsuresOnThrow<ArgumentException>(value == null || value.Length == 0); VerifyArgumentNotNull(value, paramName); if (value.Length == 0) { throw new ArgumentException(MessagingStrings.UnexpectedEmptyString, paramName); @@ -409,9 +380,7 @@ namespace DotNetOpenAuth.Messaging { internal static void VerifyHttpContext() { Contract.Ensures(HttpContext.Current != null); Contract.Ensures(HttpContext.Current.Request != null); - - ErrorUtilities.VerifyOperation(HttpContext.Current != null, MessagingStrings.HttpContextRequired); - ErrorUtilities.VerifyOperation(HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); + ErrorUtilities.VerifyOperation(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); } } } diff --git a/src/DotNetOpenAuth/Messaging/HttpDeliveryMethods.cs b/src/DotNetOpenAuth/Messaging/HttpDeliveryMethods.cs index cbbe28e..17c8f7a 100644 --- a/src/DotNetOpenAuth/Messaging/HttpDeliveryMethods.cs +++ b/src/DotNetOpenAuth/Messaging/HttpDeliveryMethods.cs @@ -34,5 +34,20 @@ namespace DotNetOpenAuth.Messaging { /// Added to the URLs in the query part (as defined by [RFC3986] (Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” .) section 3). /// </summary> GetRequest = 0x4, + + /// <summary> + /// Added to the URLs in the query part (as defined by [RFC3986] (Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” .) section 3). + /// </summary> + PutRequest = 0x8, + + /// <summary> + /// Added to the URLs in the query part (as defined by [RFC3986] (Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” .) section 3). + /// </summary> + DeleteRequest = 0x10, + + /// <summary> + /// The flags that control HTTP verbs. + /// </summary> + HttpVerbMask = PostRequest | GetRequest | PutRequest | DeleteRequest, } } diff --git a/src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs b/src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs index 9e9deb4..2951514 100644 --- a/src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs +++ b/src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs @@ -50,8 +50,15 @@ namespace DotNetOpenAuth.Messaging { /// </summary> /// <param name="request">The ASP.NET structure to copy from.</param> public HttpRequestInfo(HttpRequest request) { - Contract.Requires(request != null); - ErrorUtilities.VerifyArgumentNotNull(request, "request"); + Contract.Requires<ArgumentNullException>(request != null); + Contract.Ensures(this.HttpMethod == request.HttpMethod); + Contract.Ensures(this.Url == request.Url); + Contract.Ensures(this.RawUrl == request.RawUrl); + Contract.Ensures(this.UrlBeforeRewriting != null); + Contract.Ensures(this.Headers != null); + Contract.Ensures(this.InputStream == request.InputStream); + Contract.Ensures(this.form == request.Form); + Contract.Ensures(this.queryString == request.QueryString); this.HttpMethod = request.HttpMethod; this.Url = request.Url; @@ -78,14 +85,10 @@ namespace DotNetOpenAuth.Messaging { /// <param name="headers">Headers in the HTTP request.</param> /// <param name="inputStream">The entity stream, if any. (POST requests typically have these). Use <c>null</c> for GET requests.</param> public HttpRequestInfo(string httpMethod, Uri requestUrl, string rawUrl, WebHeaderCollection headers, Stream inputStream) { - Contract.Requires(!string.IsNullOrEmpty(httpMethod)); - Contract.Requires(requestUrl != null); - Contract.Requires(rawUrl != null); - Contract.Requires(headers != null); - ErrorUtilities.VerifyNonZeroLength(httpMethod, "httpMethod"); - ErrorUtilities.VerifyArgumentNotNull(requestUrl, "requestUrl"); - ErrorUtilities.VerifyArgumentNotNull(rawUrl, "rawUrl"); - ErrorUtilities.VerifyArgumentNotNull(headers, "headers"); + Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(httpMethod)); + Contract.Requires<ArgumentNullException>(requestUrl != null); + Contract.Requires<ArgumentNullException>(rawUrl != null); + Contract.Requires<ArgumentNullException>(headers != null); this.HttpMethod = httpMethod; this.Url = requestUrl; @@ -100,8 +103,7 @@ namespace DotNetOpenAuth.Messaging { /// </summary> /// <param name="listenerRequest">Details on the incoming HTTP request.</param> public HttpRequestInfo(HttpListenerRequest listenerRequest) { - Contract.Requires(listenerRequest != null); - ErrorUtilities.VerifyArgumentNotNull(listenerRequest, "listenerRequest"); + Contract.Requires<ArgumentNullException>(listenerRequest != null); this.HttpMethod = listenerRequest.HttpMethod; this.Url = listenerRequest.Url; @@ -121,10 +123,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="request">The WCF incoming request structure to get the HTTP information from.</param> /// <param name="requestUri">The URI of the service endpoint.</param> public HttpRequestInfo(HttpRequestMessageProperty request, Uri requestUri) { - Contract.Requires(request != null); - Contract.Requires(requestUri != null); - ErrorUtilities.VerifyArgumentNotNull(request, "request"); - ErrorUtilities.VerifyArgumentNotNull(requestUri, "requestUri"); + Contract.Requires<ArgumentNullException>(request != null); + Contract.Requires<ArgumentNullException>(requestUri != null); this.HttpMethod = request.Method; this.Headers = request.Headers; @@ -137,6 +137,9 @@ namespace DotNetOpenAuth.Messaging { /// Initializes a new instance of the <see cref="HttpRequestInfo"/> class. /// </summary> internal HttpRequestInfo() { + Contract.Ensures(this.HttpMethod == "GET"); + Contract.Ensures(this.Headers != null); + this.HttpMethod = "GET"; this.Headers = new WebHeaderCollection(); } @@ -146,8 +149,7 @@ namespace DotNetOpenAuth.Messaging { /// </summary> /// <param name="request">The HttpWebRequest (that was never used) to copy from.</param> internal HttpRequestInfo(WebRequest request) { - Contract.Requires(request != null); - ErrorUtilities.VerifyArgumentNotNull(request, "request"); + Contract.Requires<ArgumentNullException>(request != null); this.HttpMethod = request.Method; this.Url = request.RequestUri; @@ -164,11 +166,7 @@ namespace DotNetOpenAuth.Messaging { /// <param name="httpMethod">The HTTP method that the incoming request came in on, whether or not <paramref name="message"/> is null.</param> internal HttpRequestInfo(IDirectedProtocolMessage message, HttpDeliveryMethods httpMethod) { this.message = message; - if ((httpMethod & HttpDeliveryMethods.GetRequest) != 0) { - this.HttpMethod = "GET"; - } else if ((httpMethod & HttpDeliveryMethods.PostRequest) != 0) { - this.HttpMethod = "POST"; - } + this.HttpMethod = MessagingUtilities.GetHttpVerb(httpMethod); } /// <summary> @@ -307,10 +305,8 @@ namespace DotNetOpenAuth.Messaging { /// is a read-only kind of <see cref="NameValueCollection"/>. /// </remarks> internal static Uri GetPublicFacingUrl(HttpRequest request, NameValueCollection serverVariables) { - Contract.Requires(request != null); - Contract.Requires(serverVariables != null); - ErrorUtilities.VerifyArgumentNotNull(request, "request"); - ErrorUtilities.VerifyArgumentNotNull(serverVariables, "serverVariables"); + Contract.Requires<ArgumentNullException>(request != null); + Contract.Requires<ArgumentNullException>(serverVariables != null); // Due to URL rewriting, cloud computing (i.e. Azure) // and web farms, etc., we have to be VERY careful about what @@ -326,7 +322,7 @@ namespace DotNetOpenAuth.Messaging { UriBuilder publicRequestUri = new UriBuilder(request.Url); publicRequestUri.Scheme = scheme; publicRequestUri.Host = hostAndPort.Host; - publicRequestUri.Port = hostAndPort.Port; + publicRequestUri.Port = hostAndPort.Port; // CC missing Uri.Port contract that's on UriBuilder.Port return publicRequestUri.Uri; } else { // Failover to the method that works for non-web farm enviroments. @@ -356,15 +352,24 @@ namespace DotNetOpenAuth.Messaging { return query; } +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + protected void ObjectInvariant() { + } +#endif + /// <summary> /// Gets the public facing URL for the given incoming HTTP request. /// </summary> /// <param name="request">The request.</param> /// <returns>The URI that the outside world used to create this request.</returns> private static Uri GetPublicFacingUrl(HttpRequest request) { - Contract.Requires(request != null); - ErrorUtilities.VerifyArgumentNotNull(request, "request"); - + Contract.Requires<ArgumentNullException>(request != null); return GetPublicFacingUrl(request, request.ServerVariables); } @@ -374,7 +379,7 @@ namespace DotNetOpenAuth.Messaging { /// <param name="url">A full URL.</param> /// <returns>A raw URL that might have come in on the HTTP verb.</returns> private static string MakeUpRawUrlFromUrl(Uri url) { - Contract.Requires(url != null); + Contract.Requires<ArgumentNullException>(url != null); return url.AbsolutePath + url.Query + url.Fragment; } @@ -384,7 +389,7 @@ namespace DotNetOpenAuth.Messaging { /// <param name="pairs">The collection a HTTP headers.</param> /// <returns>A new collection of the given headers.</returns> private static WebHeaderCollection GetHeaderCollection(NameValueCollection pairs) { - Debug.Assert(pairs != null, "pairs == null"); + Contract.Requires<ArgumentNullException>(pairs != null); WebHeaderCollection headers = new WebHeaderCollection(); foreach (string key in pairs) { diff --git a/src/DotNetOpenAuth/Messaging/IChannelBindingElement.cs b/src/DotNetOpenAuth/Messaging/IChannelBindingElement.cs index 5ae07c3..db5dd24 100644 --- a/src/DotNetOpenAuth/Messaging/IChannelBindingElement.cs +++ b/src/DotNetOpenAuth/Messaging/IChannelBindingElement.cs @@ -107,8 +107,8 @@ namespace DotNetOpenAuth.Messaging { /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable. /// </remarks> MessageProtections? IChannelBindingElement.ProcessOutgoingMessage(IProtocolMessage message) { - Contract.Requires(((IChannelBindingElement)this).Channel != null); - Contract.Requires(message != null); + Contract.Requires<ArgumentNullException>(message != null); + Contract.Requires<InvalidOperationException>(((IChannelBindingElement)this).Channel != null); throw new NotImplementedException(); } @@ -130,8 +130,8 @@ namespace DotNetOpenAuth.Messaging { /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable. /// </remarks> MessageProtections? IChannelBindingElement.ProcessIncomingMessage(IProtocolMessage message) { - Contract.Requires(((IChannelBindingElement)this).Channel != null); - Contract.Requires(message != null); + Contract.Requires<ArgumentNullException>(message != null); + Contract.Requires<InvalidOperationException>(((IChannelBindingElement)this).Channel != null); throw new NotImplementedException(); } diff --git a/src/DotNetOpenAuth/Messaging/IDirectWebRequestHandler.cs b/src/DotNetOpenAuth/Messaging/IDirectWebRequestHandler.cs index 380e2d5..af03ba9 100644 --- a/src/DotNetOpenAuth/Messaging/IDirectWebRequestHandler.cs +++ b/src/DotNetOpenAuth/Messaging/IDirectWebRequestHandler.cs @@ -5,7 +5,9 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.Messaging { + using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.IO; using System.Net; using DotNetOpenAuth.Messaging; @@ -16,6 +18,7 @@ namespace DotNetOpenAuth.Messaging { /// <remarks> /// Implementations of this interface must be thread safe. /// </remarks> + [ContractClass(typeof(IDirectWebRequestHandlerContract))] public interface IDirectWebRequestHandler { /// <summary> /// Determines whether this instance can support the specified options. @@ -24,6 +27,7 @@ namespace DotNetOpenAuth.Messaging { /// <returns> /// <c>true</c> if this instance can support the specified options; otherwise, <c>false</c>. /// </returns> + [Pure] bool CanSupport(DirectWebRequestOptions options); /// <summary> @@ -98,4 +102,117 @@ namespace DotNetOpenAuth.Messaging { /// </remarks> IncomingWebResponse GetResponse(HttpWebRequest request, DirectWebRequestOptions options); } + + /// <summary> + /// Code contract for the <see cref="IDirectWebRequestHandler"/> type. + /// </summary> + [ContractClassFor(typeof(IDirectWebRequestHandler))] + internal abstract class IDirectWebRequestHandlerContract : IDirectWebRequestHandler { + #region IDirectWebRequestHandler Members + + /// <summary> + /// Determines whether this instance can support the specified options. + /// </summary> + /// <param name="options">The set of options that might be given in a subsequent web request.</param> + /// <returns> + /// <c>true</c> if this instance can support the specified options; otherwise, <c>false</c>. + /// </returns> + bool IDirectWebRequestHandler.CanSupport(DirectWebRequestOptions options) { + throw new System.NotImplementedException(); + } + + /// <summary> + /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. + /// </summary> + /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> + /// <returns> + /// The stream the caller should write out the entity data to. + /// </returns> + /// <exception cref="ProtocolException">Thrown for any network error.</exception> + /// <remarks> + /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/> + /// and any other appropriate properties <i>before</i> calling this method. + /// Callers <i>must</i> close and dispose of the request stream when they are done + /// writing to it to avoid taking up the connection too long and causing long waits on + /// subsequent requests.</para> + /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a + /// <see cref="ProtocolException"/> to abstract away the transport and provide + /// a single exception type for hosts to catch.</para> + /// </remarks> + Stream IDirectWebRequestHandler.GetRequestStream(HttpWebRequest request) { + Contract.Requires<ArgumentNullException>(request != null); + throw new System.NotImplementedException(); + } + + /// <summary> + /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. + /// </summary> + /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> + /// <param name="options">The options to apply to this web request.</param> + /// <returns> + /// The stream the caller should write out the entity data to. + /// </returns> + /// <exception cref="ProtocolException">Thrown for any network error.</exception> + /// <remarks> + /// <para>The caller should have set the <see cref="HttpWebRequest.ContentLength"/> + /// and any other appropriate properties <i>before</i> calling this method. + /// Callers <i>must</i> close and dispose of the request stream when they are done + /// writing to it to avoid taking up the connection too long and causing long waits on + /// subsequent requests.</para> + /// <para>Implementations should catch <see cref="WebException"/> and wrap it in a + /// <see cref="ProtocolException"/> to abstract away the transport and provide + /// a single exception type for hosts to catch.</para> + /// </remarks> + Stream IDirectWebRequestHandler.GetRequestStream(HttpWebRequest request, DirectWebRequestOptions options) { + Contract.Requires<ArgumentNullException>(request != null); + Contract.Requires<NotSupportedException>(((IDirectWebRequestHandler)this).CanSupport(options), MessagingStrings.DirectWebRequestOptionsNotSupported); + ////ErrorUtilities.VerifySupported(((IDirectWebRequestHandler)this).CanSupport(options), string.Format(MessagingStrings.DirectWebRequestOptionsNotSupported, options, this.GetType().Name)); + throw new System.NotImplementedException(); + } + + /// <summary> + /// Processes an <see cref="HttpWebRequest"/> and converts the + /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance. + /// </summary> + /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> + /// <returns> + /// An instance of <see cref="IncomingWebResponse"/> describing the response. + /// </returns> + /// <exception cref="ProtocolException">Thrown for any network error.</exception> + /// <remarks> + /// Implementations should catch <see cref="WebException"/> and wrap it in a + /// <see cref="ProtocolException"/> to abstract away the transport and provide + /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> + /// value, if set, shoud be Closed before throwing. + /// </remarks> + IncomingWebResponse IDirectWebRequestHandler.GetResponse(HttpWebRequest request) { + Contract.Requires<ArgumentNullException>(request != null); + throw new System.NotImplementedException(); + } + + /// <summary> + /// Processes an <see cref="HttpWebRequest"/> and converts the + /// <see cref="HttpWebResponse"/> to a <see cref="IncomingWebResponse"/> instance. + /// </summary> + /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> + /// <param name="options">The options to apply to this web request.</param> + /// <returns> + /// An instance of <see cref="IncomingWebResponse"/> describing the response. + /// </returns> + /// <exception cref="ProtocolException">Thrown for any network error.</exception> + /// <remarks> + /// Implementations should catch <see cref="WebException"/> and wrap it in a + /// <see cref="ProtocolException"/> to abstract away the transport and provide + /// a single exception type for hosts to catch. The <see cref="WebException.Response"/> + /// value, if set, shoud be Closed before throwing. + /// </remarks> + IncomingWebResponse IDirectWebRequestHandler.GetResponse(HttpWebRequest request, DirectWebRequestOptions options) { + Contract.Requires<ArgumentNullException>(request != null); + Contract.Requires<NotSupportedException>(((IDirectWebRequestHandler)this).CanSupport(options), MessagingStrings.DirectWebRequestOptionsNotSupported); + ////ErrorUtilities.VerifySupported(((IDirectWebRequestHandler)this).CanSupport(options), string.Format(MessagingStrings.DirectWebRequestOptionsNotSupported, options, this.GetType().Name)); + throw new System.NotImplementedException(); + } + + #endregion + } } diff --git a/src/DotNetOpenAuth/Messaging/IMessageFactory.cs b/src/DotNetOpenAuth/Messaging/IMessageFactory.cs index 9ce5f89..3718545 100644 --- a/src/DotNetOpenAuth/Messaging/IMessageFactory.cs +++ b/src/DotNetOpenAuth/Messaging/IMessageFactory.cs @@ -7,11 +7,13 @@ namespace DotNetOpenAuth.Messaging { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; /// <summary> /// A tool to analyze an incoming message to figure out what concrete class /// is designed to deserialize it and instantiates that class. /// </summary> + [ContractClass(typeof(IMessageFactoryContract))] public interface IMessageFactory { /// <summary> /// Analyzes an incoming request message payload to discover what kind of @@ -39,4 +41,47 @@ namespace DotNetOpenAuth.Messaging { /// </returns> IDirectResponseProtocolMessage GetNewResponseMessage(IDirectedProtocolMessage request, IDictionary<string, string> fields); } + + /// <summary> + /// Code contract for the <see cref="IMessageFactory"/> interface. + /// </summary> + [ContractClassFor(typeof(IMessageFactory))] + internal class IMessageFactoryContract : IMessageFactory { + #region IMessageFactory Members + + /// <summary> + /// Analyzes an incoming request message payload to discover what kind of + /// message is embedded in it and returns the type, or null if no match is found. + /// </summary> + /// <param name="recipient">The intended or actual recipient of the request message.</param> + /// <param name="fields">The name/value pairs that make up the message payload.</param> + /// <returns> + /// A newly instantiated <see cref="IProtocolMessage"/>-derived object that this message can + /// deserialize to. Null if the request isn't recognized as a valid protocol message. + /// </returns> + IDirectedProtocolMessage IMessageFactory.GetNewRequestMessage(MessageReceivingEndpoint recipient, IDictionary<string, string> fields) { + Contract.Requires<ArgumentNullException>(recipient != null); + Contract.Requires<ArgumentNullException>(fields != null); + + throw new NotImplementedException(); + } + + /// <summary> + /// Analyzes an incoming request message payload to discover what kind of + /// message is embedded in it and returns the type, or null if no match is found. + /// </summary> + /// <param name="request">The message that was sent as a request that resulted in the response.</param> + /// <param name="fields">The name/value pairs that make up the message payload.</param> + /// <returns> + /// A newly instantiated <see cref="IProtocolMessage"/>-derived object that this message can + /// deserialize to. Null if the request isn't recognized as a valid protocol message. + /// </returns> + IDirectResponseProtocolMessage IMessageFactory.GetNewResponseMessage(IDirectedProtocolMessage request, IDictionary<string, string> fields) { + Contract.Requires<ArgumentNullException>(request != null); + Contract.Requires<ArgumentNullException>(fields != null); + throw new NotImplementedException(); + } + + #endregion + } } diff --git a/src/DotNetOpenAuth/Messaging/IncomingWebResponse.cs b/src/DotNetOpenAuth/Messaging/IncomingWebResponse.cs index e471a06..70b1032 100644 --- a/src/DotNetOpenAuth/Messaging/IncomingWebResponse.cs +++ b/src/DotNetOpenAuth/Messaging/IncomingWebResponse.cs @@ -6,8 +6,8 @@ namespace DotNetOpenAuth.Messaging { using System; - using System.Diagnostics; using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; using System.Globalization; using System.IO; using System.Net; @@ -17,6 +17,8 @@ namespace DotNetOpenAuth.Messaging { /// <summary> /// Details on the incoming response from a direct web request to a remote party. /// </summary> + [ContractVerification(true)] + [ContractClass(typeof(IncomingWebResponseContract))] public abstract class IncomingWebResponse : IDisposable { /// <summary> /// The encoding to use in reading a response that does not declare its own content encoding. @@ -37,8 +39,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="requestUri">The original request URI.</param> /// <param name="response">The response to initialize from. The network stream is used by this class directly.</param> protected IncomingWebResponse(Uri requestUri, HttpWebResponse response) { - ErrorUtilities.VerifyArgumentNotNull(requestUri, "requestUri"); - ErrorUtilities.VerifyArgumentNotNull(response, "response"); + Contract.Requires<ArgumentNullException>(requestUri != null); + Contract.Requires<ArgumentNullException>(response != null); this.RequestUri = requestUri; if (!string.IsNullOrEmpty(response.ContentType)) { @@ -64,7 +66,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="contentType">Type of the content.</param> /// <param name="contentEncoding">The content encoding.</param> protected IncomingWebResponse(Uri requestUri, Uri responseUri, WebHeaderCollection headers, HttpStatusCode statusCode, string contentType, string contentEncoding) { - ErrorUtilities.VerifyArgumentNotNull(requestUri, "requestUri"); + Contract.Requires<ArgumentNullException>(requestUri != null); + this.RequestUri = requestUri; this.Status = statusCode; if (!string.IsNullOrEmpty(contentType)) { diff --git a/src/DotNetOpenAuth/Messaging/IncomingWebResponseContract.cs b/src/DotNetOpenAuth/Messaging/IncomingWebResponseContract.cs new file mode 100644 index 0000000..e9c1941 --- /dev/null +++ b/src/DotNetOpenAuth/Messaging/IncomingWebResponseContract.cs @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------- +// <copyright file="IncomingWebResponseContract.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Diagnostics.Contracts; + using System.IO; + + /// <summary> + /// Code contract for the <see cref="IncomingWebResponse"/> class. + /// </summary> + [ContractClassFor(typeof(IncomingWebResponse))] + internal abstract class IncomingWebResponseContract : IncomingWebResponse { + /// <summary> + /// Gets the body of the HTTP response. + /// </summary> + /// <value></value> + public override Stream ResponseStream { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Creates a text reader for the response stream. + /// </summary> + /// <returns> + /// The text reader, initialized for the proper encoding. + /// </returns> + public override StreamReader GetResponseReader() { + Contract.Ensures(Contract.Result<StreamReader>() != null); + throw new NotImplementedException(); + } + + /// <summary> + /// Gets an offline snapshot version of this instance. + /// </summary> + /// <param name="maximumBytesToCache">The maximum bytes from the response stream to cache.</param> + /// <returns>A snapshot version of this instance.</returns> + /// <remarks> + /// If this instance is a <see cref="NetworkDirectWebResponse"/> creating a snapshot + /// will automatically close and dispose of the underlying response stream. + /// If this instance is a <see cref="CachedDirectWebResponse"/>, the result will + /// be the self same instance. + /// </remarks> + internal override CachedDirectWebResponse GetSnapshot(int maximumBytesToCache) { + Contract.Requires<ArgumentOutOfRangeException>(maximumBytesToCache >= 0); + Contract.Requires<InvalidOperationException>(this.RequestUri != null); + Contract.Ensures(Contract.Result<CachedDirectWebResponse>() != null); + throw new NotImplementedException(); + } + } +} diff --git a/src/DotNetOpenAuth/Messaging/KeyedCollectionDelegate.cs b/src/DotNetOpenAuth/Messaging/KeyedCollectionDelegate.cs index 16786e3..5555f9c 100644 --- a/src/DotNetOpenAuth/Messaging/KeyedCollectionDelegate.cs +++ b/src/DotNetOpenAuth/Messaging/KeyedCollectionDelegate.cs @@ -27,8 +27,7 @@ namespace DotNetOpenAuth.Messaging { /// </summary> /// <param name="getKeyForItemDelegate">The delegate that gets the key for a given item.</param> internal KeyedCollectionDelegate(Func<TItem, TKey> getKeyForItemDelegate) { - Contract.Requires(getKeyForItemDelegate != null); - ErrorUtilities.VerifyArgumentNotNull(getKeyForItemDelegate, "getKeyForItemDelegate"); + Contract.Requires<ArgumentNullException>(getKeyForItemDelegate != null); this.getKeyForItemDelegate = getKeyForItemDelegate; } @@ -39,7 +38,7 @@ namespace DotNetOpenAuth.Messaging { /// <param name="item">The element from which to extract the key.</param> /// <returns>The key for the specified element.</returns> protected override TKey GetKeyForItem(TItem item) { - ErrorUtilities.VerifyArgumentNotNull(item, "item"); + ErrorUtilities.VerifyArgumentNotNull(item, "item"); // null items not supported. return this.getKeyForItemDelegate(item); } } diff --git a/src/DotNetOpenAuth/Messaging/MessageReceivingEndpoint.cs b/src/DotNetOpenAuth/Messaging/MessageReceivingEndpoint.cs index 79a1107..e26a672 100644 --- a/src/DotNetOpenAuth/Messaging/MessageReceivingEndpoint.cs +++ b/src/DotNetOpenAuth/Messaging/MessageReceivingEndpoint.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.Messaging { using System; using System.Diagnostics; + using System.Diagnostics.Contracts; /// <summary> /// An immutable description of a URL that receives messages. @@ -20,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. @@ -28,9 +32,9 @@ namespace DotNetOpenAuth.Messaging { /// <param name="location">The URL of this endpoint.</param> /// <param name="method">The HTTP method(s) allowed.</param> public MessageReceivingEndpoint(Uri location, HttpDeliveryMethods method) { - ErrorUtilities.VerifyArgumentNotNull(location, "location"); - ErrorUtilities.VerifyArgumentInRange(method != HttpDeliveryMethods.None, "method"); - ErrorUtilities.VerifyArgumentInRange((method & (HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.GetRequest)) != 0, "method", MessagingStrings.GetOrPostFlagsRequired); + Contract.Requires<ArgumentNullException>(location != null); + Contract.Requires<ArgumentOutOfRangeException>(method != HttpDeliveryMethods.None); + Contract.Requires<ArgumentOutOfRangeException>((method & HttpDeliveryMethods.HttpVerbMask) != 0, MessagingStrings.GetOrPostFlagsRequired); this.Location = location; this.AllowedMethods = method; diff --git a/src/DotNetOpenAuth/Messaging/MessageSerializer.cs b/src/DotNetOpenAuth/Messaging/MessageSerializer.cs index 1aafc11..a21559e 100644 --- a/src/DotNetOpenAuth/Messaging/MessageSerializer.cs +++ b/src/DotNetOpenAuth/Messaging/MessageSerializer.cs @@ -33,17 +33,9 @@ namespace DotNetOpenAuth.Messaging { /// that will be serialized and deserialized using this class.</param> [ContractVerification(false)] // bugs/limitations in CC static analysis private MessageSerializer(Type messageType) { - Contract.Requires(messageType != null); - Contract.Requires(typeof(IMessage).IsAssignableFrom(messageType)); + Contract.Requires<ArgumentNullException>(messageType != null); + Contract.Requires<ArgumentException>(typeof(IMessage).IsAssignableFrom(messageType)); Contract.Ensures(this.messageType != null); - - ErrorUtilities.VerifyArgumentNamed( - typeof(IMessage).IsAssignableFrom(messageType), - "messageType", - MessagingStrings.UnexpectedType, - typeof(IMessage).FullName, - messageType.FullName); - this.messageType = messageType; } @@ -54,9 +46,8 @@ namespace DotNetOpenAuth.Messaging { /// <returns>A message serializer for the given message type.</returns> [ContractVerification(false)] // bugs/limitations in CC static analysis internal static MessageSerializer Get(Type messageType) { - Contract.Requires(messageType != null); - Contract.Requires(typeof(IMessage).IsAssignableFrom(messageType)); - ErrorUtilities.VerifyArgumentNotNull(messageType, "messageType"); + Contract.Requires<ArgumentNullException>(messageType != null); + Contract.Requires<ArgumentException>(typeof(IMessage).IsAssignableFrom(messageType)); return new MessageSerializer(messageType); } @@ -66,11 +57,11 @@ namespace DotNetOpenAuth.Messaging { /// </summary> /// <param name="messageDictionary">The message to be serialized.</param> /// <returns>The dictionary of values to send for the message.</returns> + [Pure] [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Parallel design with Deserialize method.")] internal IDictionary<string, string> Serialize(MessageDictionary messageDictionary) { - Contract.Requires(messageDictionary != null); + Contract.Requires<ArgumentNullException>(messageDictionary != null); Contract.Ensures(Contract.Result<IDictionary<string, string>>() != null); - ErrorUtilities.VerifyArgumentNotNull(messageDictionary, "messageDictionary"); // Rather than hand back the whole message dictionary (which // includes keys with blank values), create a new dictionary @@ -100,10 +91,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="messageDictionary">The message to deserialize into.</param> /// <exception cref="ProtocolException">Thrown when protocol rules are broken by the incoming message.</exception> internal void Deserialize(IDictionary<string, string> fields, MessageDictionary messageDictionary) { - Contract.Requires(fields != null); - Contract.Requires(messageDictionary != null); - ErrorUtilities.VerifyArgumentNotNull(fields, "fields"); - ErrorUtilities.VerifyArgumentNotNull(messageDictionary, "messageDictionary"); + Contract.Requires<ArgumentNullException>(fields != null); + Contract.Requires<ArgumentNullException>(messageDictionary != null); var messageDescription = messageDictionary.Description; @@ -124,6 +113,7 @@ namespace DotNetOpenAuth.Messaging { /// <summary> /// Verifies conditions that should be true for any valid state of this object. /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] [ContractInvariantMethod] protected void ObjectInvariant() { diff --git a/src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs b/src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs index 13e83e1..f2c9add 100644 --- a/src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs +++ b/src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs @@ -115,7 +115,7 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> - /// Looks up a localized string similar to The given set of options ({0}) is not supported by {1}.. + /// Looks up a localized string similar to The given set of options is not supported by this web request handler.. /// </summary> internal static string DirectWebRequestOptionsNotSupported { get { @@ -304,6 +304,15 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Looks up a localized string similar to A non-empty string was expected.. + /// </summary> + internal static string NonEmptyStringExpected { + get { + return ResourceManager.GetString("NonEmptyStringExpected", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to A message response is already queued for sending in the response stream.. /// </summary> internal static string QueuedMessageResponseAlreadyExists { @@ -394,6 +403,15 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Looks up a localized string similar to The stream must have a known length.. + /// </summary> + internal static string StreamMustHaveKnownLength { + get { + return ResourceManager.GetString("StreamMustHaveKnownLength", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to The stream's CanRead property returned false.. /// </summary> internal static string StreamUnreadable { diff --git a/src/DotNetOpenAuth/Messaging/MessagingStrings.resx b/src/DotNetOpenAuth/Messaging/MessagingStrings.resx index 4bc92a7..3d4e317 100644 --- a/src/DotNetOpenAuth/Messaging/MessagingStrings.resx +++ b/src/DotNetOpenAuth/Messaging/MessagingStrings.resx @@ -136,7 +136,7 @@ <value>The directed message's Recipient property must not be null.</value> </data> <data name="DirectWebRequestOptionsNotSupported" xml:space="preserve"> - <value>The given set of options ({0}) is not supported by {1}.</value> + <value>The given set of options is not supported by this web request handler.</value> </data> <data name="ErrorDeserializingMessage" xml:space="preserve"> <value>Error while deserializing message {0}.</value> @@ -291,4 +291,10 @@ <data name="UnsupportedHttpVerb" xml:space="preserve"> <value>The HTTP verb '{0}' is unrecognized and unsupported.</value> </data> + <data name="NonEmptyStringExpected" xml:space="preserve"> + <value>A non-empty string was expected.</value> + </data> + <data name="StreamMustHaveKnownLength" xml:space="preserve"> + <value>The stream must have a known length.</value> + </data> </root>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/Messaging/MessagingStrings.sr.resx b/src/DotNetOpenAuth/Messaging/MessagingStrings.sr.resx new file mode 100644 index 0000000..5b7b716 --- /dev/null +++ b/src/DotNetOpenAuth/Messaging/MessagingStrings.sr.resx @@ -0,0 +1,294 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="ArgumentPropertyMissing" xml:space="preserve"> + <value>Svojstvo {0}.{1} argumenta je neophodno, ali je ono prazno ili nepostojeće.</value> + </data> + <data name="CurrentHttpContextRequired" xml:space="preserve"> + <value>HttpContext.Current je nepostojeći. Mora postojati ASP.NET zahtev u procesu da bi ova operacija bila uspešna.</value> + </data> + <data name="DataContractMissingFromMessageType" xml:space="preserve"> + <value>DataContractSerializer se ne može inicijalizovati na tipu poruke {0}. Da li nedostaje [DataContract] atribut?</value> + </data> + <data name="DataContractMissingNamespace" xml:space="preserve"> + <value>DataContractSerializer se ne može inicijalizovati na tipu poruke {0} jer svojstvo DataContractAttribute.Namespace property nije podešeno.</value> + </data> + <data name="DerivedTypeNotExpected" xml:space="preserve"> + <value>Instanca tipa {0} je bila očekivana, a primljena je neočekivana izvedena instanca tipa {1}.</value> + </data> + <data name="DirectedMessageMissingRecipient" xml:space="preserve"> + <value>Svojstvo Recipient usmerene poruke ne sme biti nepostojeće.</value> + </data> + <data name="DirectWebRequestOptionsNotSupported" xml:space="preserve"> + <value>Dati set opcija ({0}) nije podržan od strane {1}.</value> + </data> + <data name="ErrorDeserializingMessage" xml:space="preserve"> + <value>Greška prilikom deserijalizacije poruke {0}.</value> + </data> + <data name="ErrorInRequestReplyMessage" xml:space="preserve"> + <value>Greška se desila tokom slanja usmerene poruke ili tokom primanja odgovora.</value> + </data> + <data name="ExceptionNotConstructedForTransit" xml:space="preserve"> + <value>Ovaj izuzetak nije napravljen sa početnom porukom koja ga je izazvala.</value> + </data> + <data name="ExpectedMessageNotReceived" xml:space="preserve"> + <value>Očekivana je poruka {0} a primljena poruka nije prepoznata.</value> + </data> + <data name="ExpiredMessage" xml:space="preserve"> + <value>Poruka ističe u {0} a sada je {1}.</value> + </data> + <data name="GetOrPostFlagsRequired" xml:space="preserve"> + <value>Bar jedan od GET ili POST flegova mora biti prisutan.</value> + </data> + <data name="HttpContextRequired" xml:space="preserve"> + <value>Ovaj metod zahteva tekući HttpContext. Kao alternativa, koristite preklopljeni metod koji dozvoljava da se prosledi informacija bez HttpContext-a.</value> + </data> + <data name="IndirectMessagesMustImplementIDirectedProtocolMessage" xml:space="preserve"> + <value>Poruke koje ukazuju na indirektni transport moraju implementirati {0} interfejs.</value> + </data> + <data name="InsecureWebRequestWithSslRequired" xml:space="preserve"> + <value>Nebezbedan web zahtev za '{0}' prekinut zbog bezbednosnih zahteva koji zahtevaju HTTPS.</value> + </data> + <data name="InsufficientMessageProtection" xml:space="preserve"> + <value>Poruka {0} je zahtevala zaštite {{{1}}} ali prenosni kanal nije mogao primeniti {{{2}}}.</value> + </data> + <data name="InvalidCustomBindingElementOrder" xml:space="preserve"> + <value>Redosled prilagođenih vezujućih elemenata je neispravan.</value> + </data> + <data name="InvalidMessageParts" xml:space="preserve"> + <value>Neki deo ili delovi poruke imaju nevalidne vrednosti: {0}</value> + </data> + <data name="InvalidNonceReceived" xml:space="preserve"> + <value>Primljena poruka imala je neispravan ili nedostajući jedinstveni identifikator.</value> + </data> + <data name="KeyAlreadyExists" xml:space="preserve"> + <value>Element sa istom vrednošću ključa je već dodat.</value> + </data> + <data name="MessageNotExtensible" xml:space="preserve"> + <value>Poruka {0} ne podržava ekstenzije.</value> + </data> + <data name="MessagePartEncoderWrongType" xml:space="preserve"> + <value>Vrednost za {0}.{1} člana {1} je trebala da bude izvedena od {2} ali je izvedena od {3}.</value> + </data> + <data name="MessagePartReadFailure" xml:space="preserve"> + <value>Greška prilikom čitanja poruke '{0}' parametar '{1}' sa vrednošću '{2}'.</value> + </data> + <data name="MessagePartValueBase64DecodingFault" xml:space="preserve"> + <value>Parametar poruke '{0}' sa vrednošću '{1}' nije se base64-dekodovao.</value> + </data> + <data name="MessagePartWriteFailure" xml:space="preserve"> + <value>Greška prilikom pripremanja poruke '{0}' parametra '{1}' za slanje.</value> + </data> + <data name="QueuedMessageResponseAlreadyExists" xml:space="preserve"> + <value>Poruka-odgovor je već u redu za slanje u stream-u za odgovore.</value> + </data> + <data name="ReplayAttackDetected" xml:space="preserve"> + <value>Ova poruka je već obrađena. Ovo može ukazivati na replay napad u toku.</value> + </data> + <data name="ReplayProtectionNotSupported" xml:space="preserve"> + <value>Ovaj kanal ne podržava replay zaštitu.</value> + </data> + <data name="RequiredNonEmptyParameterWasEmpty" xml:space="preserve"> + <value>Sledeći zahtevani parametri koji ne smeju biti prazni su bili prazni u {0} poruke: {1}</value> + </data> + <data name="RequiredParametersMissing" xml:space="preserve"> + <value>Sledeći zahtevani parametri nedostaju u {0} poruke: {1}</value> + </data> + <data name="RequiredProtectionMissing" xml:space="preserve"> + <value>Povezujući element koji nudi {0} zaštitu zahteva drugu zaštitu koja nije ponuđena.</value> + </data> + <data name="SequenceContainsNoElements" xml:space="preserve"> + <value>Lista je prazna.</value> + </data> + <data name="SequenceContainsNullElement" xml:space="preserve"> + <value>Lista sadrži prazan (null) element.</value> + </data> + <data name="SignatureInvalid" xml:space="preserve"> + <value>Potpis poruke je neispravan.</value> + </data> + <data name="SigningNotSupported" xml:space="preserve"> + <value>Ovaj kanal ne podržava potpisivanje poruka. Da bi podržao potpisivanje poruka, izvedeni tip Channel mora preklopiti Sign i IsSignatureValid metode.</value> + </data> + <data name="StreamUnreadable" xml:space="preserve"> + <value>Svojstvo stream-a CanRead je vratilo false.</value> + </data> + <data name="StreamUnwritable" xml:space="preserve"> + <value>Svojstvo stream-a CanWrite je vratilo false.</value> + </data> + <data name="TooManyBindingsOfferingSameProtection" xml:space="preserve"> + <value>Očekivano je da najviše 1 povezujući element primeni zaštitu {0}, ali je više njih primenjeno.</value> + </data> + <data name="TooManyRedirects" xml:space="preserve"> + <value>Maksimalno dozvoljeni broj redirekcija je prekoračen u toku zahtevanja '{0}'.</value> + </data> + <data name="UnexpectedEmptyArray" xml:space="preserve"> + <value>Niz ne sme biti prazan.</value> + </data> + <data name="UnexpectedEmptyString" xml:space="preserve"> + <value>Prazan string nije dozvoljen.</value> + </data> + <data name="UnexpectedMessagePartValue" xml:space="preserve"> + <value>Parametar poruke '{0}' ima neočekivanu '{1}'.</value> + </data> + <data name="UnexpectedMessagePartValueForConstant" xml:space="preserve"> + <value>Očekivano je da od poruke {0} parametar '{1}' ima vrednost '{2}' ali je imao vrednost '{3}'.</value> + </data> + <data name="UnexpectedMessageReceived" xml:space="preserve"> + <value>Očekivana je poruka {0} ali je umesto nje primljena {1}.</value> + </data> + <data name="UnexpectedMessageReceivedOfMany" xml:space="preserve"> + <value>Poruka neočekivanog tipa je primljena.</value> + </data> + <data name="UnexpectedNullKey" xml:space="preserve"> + <value>null ključ je uključen a nije dozvoljen.</value> + </data> + <data name="UnexpectedNullOrEmptyKey" xml:space="preserve"> + <value>null ili prazan ključ je uključen a nije dozvoljen.</value> + </data> + <data name="UnexpectedNullValue" xml:space="preserve"> + <value>null vrednost je uključena za ključ '{0}' a nije dozvoljena.</value> + </data> + <data name="UnexpectedType" xml:space="preserve"> + <value>Tip {0} ili izvedeni tip je očekivan, a dat je {1}.</value> + </data> + <data name="UnrecognizedEnumValue" xml:space="preserve"> + <value>{0} svojstvo ima nepoznatu vrednost {1}.</value> + </data> + <data name="UnsafeWebRequestDetected" xml:space="preserve"> + <value>URL '{0}' je rangiran kao nebezbedan i ne može se zahtevati na ovaj način.</value> + </data> + <data name="UntrustedRedirectsOnPOSTNotSupported" xml:space="preserve"> + <value>Redirekcije na POST zahteve usmerene ka serverima kojima se ne veruje nisu podržane.</value> + </data> + <data name="WebRequestFailed" xml:space="preserve"> + <value>Web zahtev za '{0}' nije uspeo.</value> + </data> + <data name="ExceptionUndeliverable" xml:space="preserve"> + <value>Ovaj izuzetak mora se kreirati zajedno sa primaocem koji će primiti poruku o grešci ili sa instancom poruke direktnog zahteva na koju će ovaj izuzetak odgovoriti.</value> + </data> + <data name="UnsupportedHttpVerbForMessageType" xml:space="preserve"> + <value>'{0}' poruka ne može biti primljeno sa HTTP glagolom '{1}'.</value> + </data> + <data name="UnexpectedHttpStatusCode" xml:space="preserve"> + <value>Očekivano je da direktan odgovor koristi HTTP status kod {0} a korišćen je {1}.</value> + </data> + <data name="UnsupportedHttpVerb" xml:space="preserve"> + <value>HTTP glagol '{0}' je neprepoznat i nije podržan.</value> + </data> +</root>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs index 7d89b4e..e1e3f59 100644 --- a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs +++ b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs @@ -10,6 +10,7 @@ namespace DotNetOpenAuth.Messaging { using System.Collections.Specialized; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; + using System.Globalization; using System.IO; using System.Linq; using System.Net; @@ -90,8 +91,7 @@ namespace DotNetOpenAuth.Messaging { /// <param name="response">The response to send to the uesr agent.</param> /// <returns>The <see cref="ActionResult"/> instance to be returned by the Controller's action method.</returns> public static ActionResult AsActionResult(this OutgoingWebResponse response) { - Contract.Requires(response != null); - ErrorUtilities.VerifyArgumentNotNull(response, "response"); + Contract.Requires<ArgumentNullException>(response != null); return new OutgoingWebResponseActionResult(response); } @@ -103,7 +103,7 @@ namespace DotNetOpenAuth.Messaging { [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "The Uri merging requires use of a string value.")] [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Expensive call should not be a property.")] public static Uri GetRequestUrlFromContext() { - ErrorUtilities.VerifyHttpContext(); + Contract.Requires<InvalidOperationException>(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); HttpContext context = HttpContext.Current; // We use Request.Url for the full path to the server, and modify it @@ -123,8 +123,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="prefix">The prefix for parameters to remove. A period is NOT automatically appended.</param> /// <returns>Either a new Uri with the parameters removed if there were any to remove, or the same Uri instance if no parameters needed to be removed.</returns> public static Uri StripQueryArgumentsWithPrefix(this Uri uri, string prefix) { - ErrorUtilities.VerifyArgumentNotNull(uri, "uri"); - ErrorUtilities.VerifyNonZeroLength(prefix, "prefix"); + Contract.Requires<ArgumentNullException>(uri != null); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(prefix)); NameValueCollection queryArgs = HttpUtility.ParseQueryString(uri.Query); var matchingKeys = queryArgs.Keys.OfType<string>().Where(key => key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)).ToList(); @@ -141,11 +141,59 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Sends an multipart HTTP POST request (useful for posting files). + /// </summary> + /// <param name="request">The HTTP request.</param> + /// <param name="requestHandler">The request handler.</param> + /// <param name="parts">The parts to include in the POST entity.</param> + /// <returns>The HTTP response.</returns> + public static IncomingWebResponse PostMultipart(this HttpWebRequest request, IDirectWebRequestHandler requestHandler, IEnumerable<MultipartPostPart> parts) { + Contract.Requires<ArgumentNullException>(request != null); + Contract.Requires<ArgumentNullException>(requestHandler != null); + Contract.Requires<ArgumentNullException>(parts != null); + + string boundary = Guid.NewGuid().ToString(); + string partLeadingBoundary = string.Format(CultureInfo.InvariantCulture, "\r\n--{0}\r\n", boundary); + string finalTrailingBoundary = string.Format(CultureInfo.InvariantCulture, "\r\n--{0}--\r\n", boundary); + + request.Method = "POST"; + request.ContentType = "multipart/form-data; boundary=" + boundary; + request.ContentLength = parts.Sum(p => partLeadingBoundary.Length + p.Length) + finalTrailingBoundary.Length; + + // Setting the content-encoding to "utf-8" causes Google to reply + // with a 415 UnsupportedMediaType. But adding it doesn't buy us + // anything specific, so we disable it until we know how to get it right. + ////request.Headers[HttpRequestHeader.ContentEncoding] = Channel.PostEntityEncoding.WebName; + + var requestStream = requestHandler.GetRequestStream(request); + try { + StreamWriter writer = new StreamWriter(requestStream, Channel.PostEntityEncoding); + foreach (var part in parts) { + writer.Write(partLeadingBoundary); + part.Serialize(writer); + part.Dispose(); + } + + writer.Write(finalTrailingBoundary); + writer.Flush(); + } finally { + // We need to be sure to close the request stream... + // unless it is a MemoryStream, which is a clue that we're in + // a mock stream situation and closing it would preclude reading it later. + if (!(requestStream is MemoryStream)) { + requestStream.Dispose(); + } + } + + return requestHandler.GetResponse(request); + } + + /// <summary> /// Assembles a message comprised of the message on a given exception and all inner exceptions. /// </summary> /// <param name="exception">The exception.</param> /// <returns>The assembled message.</returns> - internal static string GetAllMessages(this Exception exception) { + public static string ToStringDescriptive(this Exception exception) { // The input being null is probably bad, but since this method is called // from a catch block, we don't really want to throw a new exception and // hide the details of this one. @@ -206,8 +254,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="allowableCharacters">The allowable characters.</param> /// <returns>A random string.</returns> internal static string GetRandomString(int length, string allowableCharacters) { - Contract.Requires(length >= 0); - Contract.Requires(allowableCharacters != null && allowableCharacters.Length >= 2); + Contract.Requires<ArgumentOutOfRangeException>(length >= 0); + Contract.Requires<ArgumentException>(allowableCharacters != null && allowableCharacters.Length >= 2); char[] randomString = new char[length]; for (int i = 0; i < length; i++) { @@ -225,8 +273,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="headers">The headers to add.</param> /// <param name="response">The <see cref="HttpResponse"/> instance to set the appropriate values to.</param> internal static void ApplyHeadersToResponse(WebHeaderCollection headers, HttpResponse response) { - ErrorUtilities.VerifyArgumentNotNull(headers, "headers"); - ErrorUtilities.VerifyArgumentNotNull(response, "response"); + Contract.Requires<ArgumentNullException>(headers != null); + Contract.Requires<ArgumentNullException>(response != null); foreach (string headerName in headers) { switch (headerName) { @@ -250,8 +298,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="headers">The headers to add.</param> /// <param name="response">The <see cref="HttpListenerResponse"/> instance to set the appropriate values to.</param> internal static void ApplyHeadersToResponse(WebHeaderCollection headers, HttpListenerResponse response) { - ErrorUtilities.VerifyArgumentNotNull(headers, "headers"); - ErrorUtilities.VerifyArgumentNotNull(response, "response"); + Contract.Requires<ArgumentNullException>(headers != null); + Contract.Requires<ArgumentNullException>(response != null); foreach (string headerName in headers) { switch (headerName) { @@ -278,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); } @@ -293,10 +345,10 @@ namespace DotNetOpenAuth.Messaging { /// The positions are NOT reset after copying is complete. /// </remarks> internal static int CopyTo(this Stream copyFrom, Stream copyTo, int maximumBytesToCopy) { - ErrorUtilities.VerifyArgumentNotNull(copyFrom, "copyFrom"); - ErrorUtilities.VerifyArgumentNotNull(copyTo, "copyTo"); - ErrorUtilities.VerifyArgument(copyFrom.CanRead, MessagingStrings.StreamUnreadable); - ErrorUtilities.VerifyArgument(copyTo.CanWrite, MessagingStrings.StreamUnwritable, "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); byte[] buffer = new byte[1024]; int readBytes; @@ -317,7 +369,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="copyFrom">The stream to copy bytes from.</param> /// <returns>A seekable stream with the same contents as the original.</returns> internal static Stream CreateSnapshot(this Stream copyFrom) { - ErrorUtilities.VerifyArgumentNotNull(copyFrom, "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); @@ -331,7 +384,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="request">The request to clone.</param> /// <returns>The newly created instance.</returns> internal static HttpWebRequest Clone(this HttpWebRequest request) { - ErrorUtilities.VerifyArgumentNotNull(request, "request"); + Contract.Requires<ArgumentNullException>(request != null); + Contract.Requires<ArgumentException>(request.RequestUri != null); return Clone(request, request.RequestUri); } @@ -342,8 +396,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="newRequestUri">The new recipient of the request.</param> /// <returns>The newly created instance.</returns> internal static HttpWebRequest Clone(this HttpWebRequest request, Uri newRequestUri) { - ErrorUtilities.VerifyArgumentNotNull(request, "request"); - ErrorUtilities.VerifyArgumentNotNull(newRequestUri, "newRequestUri"); + Contract.Requires<ArgumentNullException>(request != null); + Contract.Requires<ArgumentNullException>(newRequestUri != null); var newRequest = (HttpWebRequest)WebRequest.Create(newRequestUri); @@ -409,8 +463,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="second">The second array in the comparison. May not be null.</param> /// <returns>True if the arrays equal; false otherwise.</returns> internal static bool AreEquivalent<T>(T[] first, T[] second) { - ErrorUtilities.VerifyArgumentNotNull(first, "first"); - ErrorUtilities.VerifyArgumentNotNull(second, "second"); + Contract.Requires<ArgumentNullException>(first != null); + Contract.Requires<ArgumentNullException>(second != null); if (first.Length != second.Length) { return false; } @@ -499,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()); } @@ -510,9 +566,9 @@ namespace DotNetOpenAuth.Messaging { /// <param name="args">The dictionary of key/values to read from.</param> /// <returns>The formulated querystring style string.</returns> internal static string CreateQueryString(IEnumerable<KeyValuePair<string, string>> args) { - Contract.Requires(args != null); + Contract.Requires<ArgumentNullException>(args != null); Contract.Ensures(Contract.Result<string>() != null); - ErrorUtilities.VerifyArgumentNotNull(args, "args"); + if (args.Count() == 0) { return string.Empty; } @@ -546,7 +602,7 @@ namespace DotNetOpenAuth.Messaging { /// in the query string, the existing ones are <i>not</i> replaced. /// </remarks> internal static void AppendQueryArgs(this UriBuilder builder, IEnumerable<KeyValuePair<string, string>> args) { - ErrorUtilities.VerifyArgumentNotNull(builder, "builder"); + Contract.Requires<ArgumentNullException>(builder != null); if (args != null && args.Count() > 0) { StringBuilder sb = new StringBuilder(50 + (args.Count() * 10)); @@ -570,7 +626,7 @@ namespace DotNetOpenAuth.Messaging { /// If null, <paramref name="builder"/> is not changed. /// </param> internal static void AppendAndReplaceQueryArgs(this UriBuilder builder, IEnumerable<KeyValuePair<string, string>> args) { - ErrorUtilities.VerifyArgumentNotNull(builder, "builder"); + Contract.Requires<ArgumentNullException>(builder != null); if (args != null && args.Count() > 0) { NameValueCollection aggregatedArgs = HttpUtility.ParseQueryString(builder.Query); @@ -588,7 +644,47 @@ namespace DotNetOpenAuth.Messaging { /// <param name="request">The request to get recipient information from.</param> /// <returns>The recipient.</returns> internal static MessageReceivingEndpoint GetRecipient(this HttpRequestInfo request) { - return new MessageReceivingEndpoint(request.UrlBeforeRewriting, request.HttpMethod == "GET" ? HttpDeliveryMethods.GetRequest : HttpDeliveryMethods.PostRequest); + return new MessageReceivingEndpoint(request.UrlBeforeRewriting, GetHttpDeliveryMethod(request.HttpMethod)); + } + + /// <summary> + /// Gets the <see cref="HttpDeliveryMethods"/> enum value for a given HTTP verb. + /// </summary> + /// <param name="httpVerb">The HTTP verb.</param> + /// <returns>A <see cref="HttpDeliveryMethods"/> enum value that is within the <see cref="HttpDeliveryMethods.HttpVerbMask"/>.</returns> + internal static HttpDeliveryMethods GetHttpDeliveryMethod(string httpVerb) { + if (httpVerb == "GET") { + return HttpDeliveryMethods.GetRequest; + } else if (httpVerb == "POST") { + return HttpDeliveryMethods.PostRequest; + } else if (httpVerb == "PUT") { + return HttpDeliveryMethods.PutRequest; + } else if (httpVerb == "DELETE") { + return HttpDeliveryMethods.DeleteRequest; + } else { + throw ErrorUtilities.ThrowArgumentNamed("httpVerb", MessagingStrings.UnsupportedHttpVerb, httpVerb); + } + } + + /// <summary> + /// Gets the HTTP verb to use for a given <see cref="HttpDeliveryMethods"/> enum value. + /// </summary> + /// <param name="httpMethod">The HTTP method.</param> + /// <returns>An HTTP verb, such as GET, POST, PUT, or DELETE.</returns> + internal static string GetHttpVerb(HttpDeliveryMethods httpMethod) { + if ((httpMethod & HttpDeliveryMethods.GetRequest) != 0) { + return "GET"; + } else if ((httpMethod & HttpDeliveryMethods.PostRequest) != 0) { + return "POST"; + } else if ((httpMethod & HttpDeliveryMethods.PutRequest) != 0) { + return "PUT"; + } else if ((httpMethod & HttpDeliveryMethods.DeleteRequest) != 0) { + return "DELETE"; + } else if ((httpMethod & HttpDeliveryMethods.AuthorizationHeaderRequest) != 0) { + return "GET"; // if AuthorizationHeaderRequest is specified without an explicit HTTP verb, assume GET. + } else { + throw ErrorUtilities.ThrowArgumentNamed("httpMethod", MessagingStrings.UnsupportedHttpVerb, httpMethod); + } } /// <summary> @@ -597,7 +693,7 @@ namespace DotNetOpenAuth.Messaging { /// <param name="messageDictionary">The message to copy the extra data into.</param> /// <param name="extraParameters">The extra data to copy into the message. May be null to do nothing.</param> internal static void AddExtraParameters(this MessageDictionary messageDictionary, IDictionary<string, string> extraParameters) { - ErrorUtilities.VerifyArgumentNotNull(messageDictionary, "messageAccessor"); + Contract.Requires<ArgumentNullException>(messageDictionary != null); if (extraParameters != null) { foreach (var pair in extraParameters) { @@ -664,6 +760,10 @@ 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)); } @@ -680,7 +780,7 @@ namespace DotNetOpenAuth.Messaging { /// if their <see cref="IDirectedProtocolMessage.Recipient"/> property is non-null. /// </remarks> internal static bool IsRequest(this IDirectedProtocolMessage message) { - ErrorUtilities.VerifyArgumentNotNull(message, "message"); + Contract.Requires<ArgumentNullException>(message != null); return message.Recipient != null; } @@ -698,7 +798,7 @@ namespace DotNetOpenAuth.Messaging { /// <see cref="IDirectResponseProtocolMessage.OriginatingRequest"/> property is non-null. /// </remarks> internal static bool IsDirectResponse(this IDirectResponseProtocolMessage message) { - ErrorUtilities.VerifyArgumentNotNull(message, "message"); + Contract.Requires<ArgumentNullException>(message != null); return message.OriginatingRequest != null; } @@ -707,15 +807,16 @@ namespace DotNetOpenAuth.Messaging { /// on the user agent when assigned to a variable. /// </summary> /// <param name="namesAndValues">The untrusted names and untrusted values to inject into the JSON object.</param> + /// <param name="valuesPreEncoded">if set to <c>true</c> the values will NOT be escaped as if it were a pure string.</param> /// <returns>The Javascript JSON object as a string.</returns> - internal static string CreateJsonObject(IEnumerable<KeyValuePair<string, string>> namesAndValues) { + internal static string CreateJsonObject(IEnumerable<KeyValuePair<string, string>> namesAndValues, bool valuesPreEncoded) { StringBuilder builder = new StringBuilder(); builder.Append("{ "); foreach (var pair in namesAndValues) { builder.Append(MessagingUtilities.GetSafeJavascriptValue(pair.Key)); builder.Append(": "); - builder.Append(MessagingUtilities.GetSafeJavascriptValue(pair.Value)); + builder.Append(valuesPreEncoded ? pair.Value : MessagingUtilities.GetSafeJavascriptValue(pair.Value)); builder.Append(","); } @@ -760,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 @@ -816,9 +919,7 @@ namespace DotNetOpenAuth.Messaging { /// </summary> /// <param name="comparison">The comparison method to use.</param> internal ComparisonHelper(Comparison<T> comparison) { - if (comparison == null) { - throw new ArgumentNullException("comparison"); - } + Contract.Requires<ArgumentNullException>(comparison != null); this.comparison = comparison; } diff --git a/src/DotNetOpenAuth/Messaging/MultipartPostPart.cs b/src/DotNetOpenAuth/Messaging/MultipartPostPart.cs new file mode 100644 index 0000000..9cbf11b --- /dev/null +++ b/src/DotNetOpenAuth/Messaging/MultipartPostPart.cs @@ -0,0 +1,202 @@ +//----------------------------------------------------------------------- +// <copyright file="MultipartPostPart.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.IO; + using System.Net; + using System.Text; + + /// <summary> + /// Represents a single part in a HTTP multipart POST request. + /// </summary> + public class MultipartPostPart : IDisposable { + /// <summary> + /// The "Content-Disposition" string. + /// </summary> + private const string ContentDispositionHeader = "Content-Disposition"; + + /// <summary> + /// The two-character \r\n newline character sequence to use. + /// </summary> + private const string NewLine = "\r\n"; + + /// <summary> + /// Initializes a new instance of the <see cref="MultipartPostPart"/> class. + /// </summary> + /// <param name="contentDisposition">The content disposition of the part.</param> + public MultipartPostPart(string contentDisposition) { + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(contentDisposition)); + + this.ContentDisposition = contentDisposition; + this.ContentAttributes = new Dictionary<string, string>(); + this.PartHeaders = new WebHeaderCollection(); + } + + /// <summary> + /// Gets the content disposition. + /// </summary> + /// <value>The content disposition.</value> + public string ContentDisposition { get; private set; } + + /// <summary> + /// Gets the key=value attributes that appear on the same line as the Content-Disposition. + /// </summary> + /// <value>The content attributes.</value> + public IDictionary<string, string> ContentAttributes { get; private set; } + + /// <summary> + /// Gets the headers that appear on subsequent lines after the Content-Disposition. + /// </summary> + public WebHeaderCollection PartHeaders { get; private set; } + + /// <summary> + /// Gets or sets the content of the part. + /// </summary> + public Stream Content { get; set; } + + /// <summary> + /// Gets the length of this entire part. + /// </summary> + /// <remarks>Useful for calculating the ContentLength HTTP header to send before actually serializing the content.</remarks> + public long Length { + get { + ErrorUtilities.VerifyOperation(this.Content != null && this.Content.Length >= 0, MessagingStrings.StreamMustHaveKnownLength); + + long length = 0; + length += ContentDispositionHeader.Length; + length += ": ".Length; + length += this.ContentDisposition.Length; + foreach (var pair in this.ContentAttributes) { + length += "; ".Length + pair.Key.Length + "=\"".Length + pair.Value.Length + "\"".Length; + } + + length += NewLine.Length; + foreach (string headerName in this.PartHeaders) { + length += headerName.Length; + length += ": ".Length; + length += this.PartHeaders[headerName].Length; + length += NewLine.Length; + } + + length += NewLine.Length; + length += this.Content.Length; + + return length; + } + } + + /// <summary> + /// Creates a part that represents a simple form field. + /// </summary> + /// <param name="name">The name of the form field.</param> + /// <param name="value">The value.</param> + /// <returns>The constructed part.</returns> + public static MultipartPostPart CreateFormPart(string name, string value) { + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(name)); + Contract.Requires<ArgumentException>(value != null); + + var part = new MultipartPostPart("form-data"); + part.ContentAttributes["name"] = name; + part.Content = new MemoryStream(Encoding.UTF8.GetBytes(value)); + return part; + } + + /// <summary> + /// Creates a part that represents a file attachment. + /// </summary> + /// <param name="name">The name of the form field.</param> + /// <param name="filePath">The path to the file to send.</param> + /// <param name="contentType">Type of the content in HTTP Content-Type format.</param> + /// <returns>The constructed part.</returns> + public static MultipartPostPart CreateFormFilePart(string name, string filePath, string contentType) { + string fileName = Path.GetFileName(filePath); + return CreateFormFilePart(name, fileName, contentType, File.OpenRead(filePath)); + } + + /// <summary> + /// Creates a part that represents a file attachment. + /// </summary> + /// <param name="name">The name of the form field.</param> + /// <param name="fileName">Name of the file as the server should see it.</param> + /// <param name="contentType">Type of the content in HTTP Content-Type format.</param> + /// <param name="content">The content of the file.</param> + /// <returns>The constructed part.</returns> + public static MultipartPostPart CreateFormFilePart(string name, string fileName, string contentType, Stream content) { + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(name)); + Contract.Requires<ArgumentException>(fileName != null); + Contract.Requires<ArgumentException>(contentType != null); + Contract.Requires<ArgumentException>(content != null); + + var part = new MultipartPostPart("form-data"); + part.ContentAttributes["name"] = name; + part.ContentAttributes["filename"] = fileName; + part.PartHeaders[HttpRequestHeader.ContentType] = contentType; + if (!contentType.StartsWith("text/", StringComparison.Ordinal)) { + part.PartHeaders["Content-Transfer-Encoding"] = "binary"; + } + part.Content = content; + return part; + } + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Serializes the part to a stream. + /// </summary> + /// <param name="streamWriter">The stream writer.</param> + internal void Serialize(StreamWriter streamWriter) { + // VERY IMPORTANT: any changes at all made to this must be kept in sync with the + // Length property which calculates exactly how many bytes this method will write. + streamWriter.NewLine = NewLine; + streamWriter.Write("{0}: {1}", ContentDispositionHeader, this.ContentDisposition); + foreach (var pair in this.ContentAttributes) { + streamWriter.Write("; {0}=\"{1}\"", pair.Key, pair.Value); + } + + streamWriter.WriteLine(); + foreach (string headerName in this.PartHeaders) { + streamWriter.WriteLine("{0}: {1}", headerName, this.PartHeaders[headerName]); + } + + streamWriter.WriteLine(); + streamWriter.Flush(); + this.Content.CopyTo(streamWriter.BaseStream); + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool disposing) { + if (disposing) { + this.Content.Dispose(); + } + } + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + protected void Invariant() { + Contract.Invariant(!string.IsNullOrEmpty(this.ContentDisposition)); + Contract.Invariant(this.PartHeaders != null); + Contract.Invariant(this.ContentAttributes != null); + } +#endif + } +} diff --git a/src/DotNetOpenAuth/Messaging/NetworkDirectWebResponse.cs b/src/DotNetOpenAuth/Messaging/NetworkDirectWebResponse.cs index 2ee7feb..643cf81 100644 --- a/src/DotNetOpenAuth/Messaging/NetworkDirectWebResponse.cs +++ b/src/DotNetOpenAuth/Messaging/NetworkDirectWebResponse.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.Messaging { using System; using System.Diagnostics; + using System.Diagnostics.Contracts; using System.IO; using System.Net; using System.Text; @@ -15,6 +16,7 @@ namespace DotNetOpenAuth.Messaging { /// A live network HTTP response /// </summary> [DebuggerDisplay("{Status} {ContentType.MediaType}")] + [ContractVerification(true)] internal class NetworkDirectWebResponse : IncomingWebResponse, IDisposable { /// <summary> /// The network response object, used to initialize this instance, that still needs @@ -40,6 +42,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="response">The response.</param> internal NetworkDirectWebResponse(Uri requestUri, HttpWebResponse response) : base(requestUri, response) { + Contract.Requires<ArgumentNullException>(requestUri != null); + Contract.Requires<ArgumentNullException>(response != null); this.httpWebResponse = response; this.responseStream = response.GetResponseStream(); } @@ -82,6 +86,8 @@ namespace DotNetOpenAuth.Messaging { /// </remarks> internal override CachedDirectWebResponse GetSnapshot(int maximumBytesToCache) { ErrorUtilities.VerifyOperation(!this.streamReadBegun, "Network stream reading has already begun."); + ErrorUtilities.VerifyOperation(this.httpWebResponse != null, "httpWebResponse != null"); + this.streamReadBegun = true; var result = new CachedDirectWebResponse(this.RequestUri, this.httpWebResponse, maximumBytesToCache); this.Dispose(); diff --git a/src/DotNetOpenAuth/Messaging/OutgoingWebResponse.cs b/src/DotNetOpenAuth/Messaging/OutgoingWebResponse.cs index 147cd66..f6a930c 100644 --- a/src/DotNetOpenAuth/Messaging/OutgoingWebResponse.cs +++ b/src/DotNetOpenAuth/Messaging/OutgoingWebResponse.cs @@ -49,7 +49,7 @@ namespace DotNetOpenAuth.Messaging { /// <param name="response">The <see cref="HttpWebResponse"/> to clone.</param> /// <param name="maximumBytesToRead">The maximum bytes to read from the response stream.</param> protected internal OutgoingWebResponse(HttpWebResponse response, int maximumBytesToRead) { - ErrorUtilities.VerifyArgumentNotNull(response, "response"); + Contract.Requires<ArgumentNullException>(response != null); this.Status = response.StatusCode; this.Headers = response.Headers; @@ -125,8 +125,7 @@ namespace DotNetOpenAuth.Messaging { /// Requires a current HttpContext. /// </remarks> public virtual void Send() { - Contract.Requires(HttpContext.Current != null); - ErrorUtilities.VerifyHttpContext(); + Contract.Requires<InvalidOperationException>(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); this.Send(HttpContext.Current); } @@ -139,8 +138,7 @@ namespace DotNetOpenAuth.Messaging { /// Typically this is <see cref="HttpContext.Current"/>.</param> /// <exception cref="ThreadAbortException">Thrown by ASP.NET in order to prevent additional data from the page being sent to the client and corrupting the response.</exception> public virtual void Send(HttpContext context) { - Contract.Requires(context != null); - ErrorUtilities.VerifyArgumentNotNull(context, "context"); + Contract.Requires<ArgumentNullException>(context != null); context.Response.Clear(); context.Response.StatusCode = (int)this.Status; @@ -167,8 +165,7 @@ namespace DotNetOpenAuth.Messaging { /// </summary> /// <param name="response">The response to set to this message.</param> public virtual void Send(HttpListenerResponse response) { - Contract.Requires(response != null); - ErrorUtilities.VerifyArgumentNotNull(response, "response"); + Contract.Requires<ArgumentNullException>(response != null); response.StatusCode = (int)this.Status; MessagingUtilities.ApplyHeadersToResponse(this.Headers, response); @@ -191,8 +188,7 @@ namespace DotNetOpenAuth.Messaging { /// rather than cause a redirect. /// </remarks> internal Uri GetDirectUriRequest(Channel channel) { - Contract.Requires(channel != null); - ErrorUtilities.VerifyArgumentNotNull(channel, "channel"); + Contract.Requires<ArgumentNullException>(channel != null); var message = this.OriginalMessage as IDirectedProtocolMessage; if (message == null) { diff --git a/src/DotNetOpenAuth/Messaging/OutgoingWebResponseActionResult.cs b/src/DotNetOpenAuth/Messaging/OutgoingWebResponseActionResult.cs index 2da1ebf..1cfc638 100644 --- a/src/DotNetOpenAuth/Messaging/OutgoingWebResponseActionResult.cs +++ b/src/DotNetOpenAuth/Messaging/OutgoingWebResponseActionResult.cs @@ -5,6 +5,7 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.Messaging { + using System; using System.Diagnostics.Contracts; using System.Web.Mvc; using DotNetOpenAuth.Messaging; @@ -24,8 +25,7 @@ namespace DotNetOpenAuth.Messaging { /// </summary> /// <param name="response">The response.</param> internal OutgoingWebResponseActionResult(OutgoingWebResponse response) { - Contract.Requires(response != null); - ErrorUtilities.VerifyArgumentNotNull(response, "response"); + Contract.Requires<ArgumentNullException>(response != null); this.response = response; } diff --git a/src/DotNetOpenAuth/Messaging/ProtocolException.cs b/src/DotNetOpenAuth/Messaging/ProtocolException.cs index daf13d7..25f8eee 100644 --- a/src/DotNetOpenAuth/Messaging/ProtocolException.cs +++ b/src/DotNetOpenAuth/Messaging/ProtocolException.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.Messaging { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.Security.Permissions; /// <summary> @@ -43,7 +44,7 @@ namespace DotNetOpenAuth.Messaging { /// <param name="faultedMessage">The message that was the cause of the exception. Must not be null.</param> protected internal ProtocolException(string message, IProtocolMessage faultedMessage) : base(message) { - ErrorUtilities.VerifyArgumentNotNull(faultedMessage, "faultedMessage"); + Contract.Requires<ArgumentNullException>(faultedMessage != null); this.FaultedMessage = faultedMessage; } diff --git a/src/DotNetOpenAuth/Messaging/Reflection/IMessagePartEncoder.cs b/src/DotNetOpenAuth/Messaging/Reflection/IMessagePartEncoder.cs index d2c9596..03534f2 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/IMessagePartEncoder.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/IMessagePartEncoder.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.Linq; using System.Text; @@ -16,6 +17,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <remarks> /// Implementations of this interface must include a default constructor and must be thread-safe. /// </remarks> + [ContractClass(typeof(IMessagePartEncoderContract))] public interface IMessagePartEncoder { /// <summary> /// Encodes the specified value. @@ -32,4 +34,45 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <exception cref="FormatException">Thrown when the string value given cannot be decoded into the required object type.</exception> object Decode(string value); } + + /// <summary> + /// Code contract for the <see cref="IMessagePartEncoder"/> type. + /// </summary> + [ContractClassFor(typeof(IMessagePartEncoder))] + internal abstract class IMessagePartEncoderContract : IMessagePartEncoder { + /// <summary> + /// Initializes a new instance of the <see cref="IMessagePartEncoderContract"/> class. + /// </summary> + protected IMessagePartEncoderContract() { + } + + #region IMessagePartEncoder Members + + /// <summary> + /// Encodes the specified value. + /// </summary> + /// <param name="value">The value. Guaranteed to never be null.</param> + /// <returns> + /// The <paramref name="value"/> in string form, ready for message transport. + /// </returns> + string IMessagePartEncoder.Encode(object value) { + Contract.Requires<ArgumentNullException>(value != null); + throw new NotImplementedException(); + } + + /// <summary> + /// Decodes the specified value. + /// </summary> + /// <param name="value">The string value carried by the transport. Guaranteed to never be null, although it may be empty.</param> + /// <returns> + /// The deserialized form of the given string. + /// </returns> + /// <exception cref="FormatException">Thrown when the string value given cannot be decoded into the required object type.</exception> + object IMessagePartEncoder.Decode(string value) { + Contract.Requires<ArgumentNullException>(value != null); + throw new NotImplementedException(); + } + + #endregion + } } diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs index cadce44..738c2a3 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs @@ -40,17 +40,9 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <param name="messageType">Type of the message.</param> /// <param name="messageVersion">The message version.</param> internal MessageDescription(Type messageType, Version messageVersion) { - Contract.Requires(messageType != null && typeof(IMessage).IsAssignableFrom(messageType)); - Contract.Requires(messageVersion != null); - ErrorUtilities.VerifyArgumentNotNull(messageType, "messageType"); - ErrorUtilities.VerifyArgumentNotNull(messageVersion, "messageVersion"); - if (!typeof(IMessage).IsAssignableFrom(messageType)) { - throw new ArgumentException(string.Format( - CultureInfo.CurrentCulture, - MessagingStrings.UnexpectedType, - typeof(IMessage), - messageType)); - } + Contract.Requires<ArgumentNullException>(messageType != null); + Contract.Requires<ArgumentException>(typeof(IMessage).IsAssignableFrom(messageType)); + Contract.Requires<ArgumentNullException>(messageVersion != null); this.messageType = messageType; this.messageVersion = messageVersion; @@ -70,10 +62,10 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// </summary> /// <param name="message">The message the dictionary should provide access to.</param> /// <returns>The dictionary accessor to the message</returns> + [Pure] internal MessageDictionary GetDictionary(IMessage message) { - Contract.Requires(message != null); + Contract.Requires<ArgumentNullException>(message != null); Contract.Ensures(Contract.Result<MessageDictionary>() != null); - ErrorUtilities.VerifyArgumentNotNull(message, "message"); return new MessageDictionary(message, this); } diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessageDescriptionCollection.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessageDescriptionCollection.cs index 9af4bdf..ff8b74b 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessageDescriptionCollection.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/MessageDescriptionCollection.cs @@ -33,12 +33,12 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <param name="messageType">A type that implements <see cref="IMessage"/>.</param> /// <param name="messageVersion">The protocol version of the message.</param> /// <returns>A <see cref="MessageDescription"/> instance.</returns> + [Pure] internal MessageDescription Get(Type messageType, Version messageVersion) { - Contract.Requires(messageType != null && typeof(IMessage).IsAssignableFrom(messageType)); - Contract.Requires(messageVersion != null); + Contract.Requires<ArgumentNullException>(messageType != null); + Contract.Requires<ArgumentException>(typeof(IMessage).IsAssignableFrom(messageType)); + Contract.Requires<ArgumentNullException>(messageVersion != null); Contract.Ensures(Contract.Result<MessageDescription>() != null); - ErrorUtilities.VerifyArgumentNotNull(messageType, "messageType"); - ErrorUtilities.VerifyArgumentNotNull(messageVersion, "messageVersion"); MessageTypeAndVersion key = new MessageTypeAndVersion(messageType, messageVersion); @@ -63,10 +63,10 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <returns> /// A <see cref="MessageDescription"/> instance. /// </returns> + [Pure] internal MessageDescription Get(IMessage message) { - Contract.Requires(message != null); + Contract.Requires<ArgumentNullException>(message != null); Contract.Ensures(Contract.Result<MessageDescription>() != null); - ErrorUtilities.VerifyArgumentNotNull(message, "message"); return this.Get(message.GetType(), message.Version); } @@ -75,9 +75,9 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// </summary> /// <param name="message">The message.</param> /// <returns>The dictionary.</returns> + [Pure] internal MessageDictionary GetAccessor(IMessage message) { - Contract.Requires(message != null); - ErrorUtilities.VerifyArgumentNotNull(message, "message"); + Contract.Requires<ArgumentNullException>(message != null); return this.Get(message).GetDictionary(message); } @@ -102,10 +102,8 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <param name="messageType">Type of the message.</param> /// <param name="messageVersion">The message version.</param> internal MessageTypeAndVersion(Type messageType, Version messageVersion) { - Contract.Requires(messageType != null); - Contract.Requires(messageVersion != null); - ErrorUtilities.VerifyArgumentNotNull(messageType, "messageType"); - ErrorUtilities.VerifyArgumentNotNull(messageVersion, "messageVersion"); + Contract.Requires<ArgumentNullException>(messageType != null); + Contract.Requires<ArgumentNullException>(messageVersion != null); this.type = messageType; this.version = messageVersion; diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs index 0b5b6d0..fedc136 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs @@ -9,6 +9,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { using System.Collections; using System.Collections.Generic; using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; /// <summary> @@ -16,27 +17,27 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// provides access to both well-defined message properties and "extra" /// name/value pairs that have no properties associated with them. /// </summary> + [ContractVerification(false)] internal class MessageDictionary : IDictionary<string, string> { /// <summary> /// The <see cref="IMessage"/> instance manipulated by this dictionary. /// </summary> - private IMessage message; + private readonly IMessage message; /// <summary> /// The <see cref="MessageDescription"/> instance that describes the message type. /// </summary> - private MessageDescription description; + private readonly MessageDescription description; /// <summary> /// Initializes a new instance of the <see cref="MessageDictionary"/> class. /// </summary> /// <param name="message">The message instance whose values will be manipulated by this dictionary.</param> /// <param name="description">The message description.</param> + [Pure] internal MessageDictionary(IMessage message, MessageDescription description) { - Contract.Requires(message != null); - Contract.Requires(description != null); - ErrorUtilities.VerifyArgumentNotNull(message, "message"); - ErrorUtilities.VerifyArgumentNotNull(description, "description"); + Contract.Requires<ArgumentNullException>(message != null); + Contract.Requires<ArgumentNullException>(description != null); this.message = message; this.description = description; @@ -202,9 +203,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// Thrown if <paramref name="value"/> is null. /// </exception> public void Add(string key, string value) { - if (value == null) { - throw new ArgumentNullException("value"); - } + ErrorUtilities.VerifyArgumentNotNull(value, "value"); MessagePart part; if (this.description.Mapping.TryGetValue(key, out part)) { @@ -270,6 +269,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// Sets a named value in the message. /// </summary> /// <param name="item">The name-value pair to add. The name is the serialized form of the key.</param> + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Code Contracts generates the code FxCop is complaining about.")] public void Add(KeyValuePair<string, string> item) { this.Add(item.Key, item.Value); } @@ -371,7 +371,9 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// Saves the data in a message to a standard dictionary. /// </summary> /// <returns>The generated dictionary.</returns> + [Pure] public IDictionary<string, string> Serialize() { + Contract.Ensures(Contract.Result<IDictionary<string, string>>() != null); return this.Serializer.Serialize(this); } @@ -380,7 +382,21 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// </summary> /// <param name="fields">The data to load into the message.</param> public void Deserialize(IDictionary<string, string> fields) { + Contract.Requires<ArgumentNullException>(fields != null); this.Serializer.Deserialize(fields, this); } + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + protected void ObjectInvariant() { + Contract.Invariant(this.Message != null); + Contract.Invariant(this.Description != null); + } +#endif } } diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs index f4bc3fe..32d2fec 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs @@ -9,6 +9,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; using System.Globalization; using System.Linq; using System.Net.Security; @@ -19,6 +20,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <summary> /// Describes an individual member of a message and assists in its serialization. /// </summary> + [ContractVerification(true)] [DebuggerDisplay("MessagePart {Name}")] internal class MessagePart { /// <summary> @@ -63,12 +65,37 @@ namespace DotNetOpenAuth.Messaging.Reflection { [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "By design.")] [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Much more efficient initialization when we can call methods.")] static MessagePart() { - Map<Uri>(uri => uri.AbsoluteUri, str => new Uri(str)); + Func<string, Uri> safeUri = str => { + Contract.Assume(str != null); + return new Uri(str); + }; + Func<string, bool> safeBool = str => { + Contract.Assume(str != null); + return bool.Parse(str); + }; + Func<string, Identifier> safeIdentfier = str => { + Contract.Assume(str != null); + ErrorUtilities.VerifyFormat(str.Length > 0, MessagingStrings.NonEmptyStringExpected); + return Identifier.Parse(str); + }; + Func<byte[], string> safeFromByteArray = bytes => { + Contract.Assume(bytes != null); + return Convert.ToBase64String(bytes); + }; + Func<string, byte[]> safeToByteArray = str => { + Contract.Assume(str != null); + return Convert.FromBase64String(str); + }; + Func<string, Realm> safeRealm = str => { + Contract.Assume(str != null); + return new Realm(str); + }; + Map<Uri>(uri => uri.AbsoluteUri, safeUri); Map<DateTime>(dt => XmlConvert.ToString(dt, XmlDateTimeSerializationMode.Utc), str => XmlConvert.ToDateTime(str, XmlDateTimeSerializationMode.Utc)); - Map<byte[]>(bytes => Convert.ToBase64String(bytes), str => Convert.FromBase64String(str)); - Map<Realm>(realm => realm.ToString(), str => new Realm(str)); - Map<Identifier>(id => id.ToString(), str => Identifier.Parse(str)); - Map<bool>(value => value.ToString().ToLowerInvariant(), str => bool.Parse(str)); + Map<byte[]>(safeFromByteArray, safeToByteArray); + Map<Realm>(realm => realm.ToString(), safeRealm); + Map<Identifier>(id => id.ToString(), safeIdentfier); + Map<bool>(value => value.ToString().ToLowerInvariant(), safeBool); Map<CultureInfo>(c => c.Name, str => new CultureInfo(str)); Map<CultureInfo[]>(cs => string.Join(",", cs.Select(c => c.Name).ToArray()), str => str.Split(',').Select(s => new CultureInfo(s)).ToArray()); } @@ -84,27 +111,14 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// The attribute discovered on <paramref name="member"/> that describes the /// serialization requirements of the message part. /// </param> + [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Code contracts requires it.")] internal MessagePart(MemberInfo member, MessagePartAttribute attribute) { - if (member == null) { - throw new ArgumentNullException("member"); - } + Contract.Requires<ArgumentNullException>(member != null); + Contract.Requires<ArgumentException>(member is FieldInfo || member is PropertyInfo); + Contract.Requires<ArgumentNullException>(attribute != null); this.field = member as FieldInfo; this.property = member as PropertyInfo; - if (this.field == null && this.property == null) { - throw new ArgumentException( - string.Format( - CultureInfo.CurrentCulture, - MessagingStrings.UnexpectedType, - typeof(FieldInfo).Name + ", " + typeof(PropertyInfo).Name, - member.GetType().Name), - "member"); - } - - if (attribute == null) { - throw new ArgumentNullException("attribute"); - } - this.Name = attribute.Name ?? member.Name; this.RequiredProtection = attribute.RequiredProtection; this.IsRequired = attribute.IsRequired; @@ -112,6 +126,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { this.memberDeclaredType = (this.field != null) ? this.field.FieldType : this.property.PropertyType; this.defaultMemberValue = DeriveDefaultValue(this.memberDeclaredType); + Contract.Assume(this.memberDeclaredType != null); // CC missing PropertyInfo.PropertyType ensures result != null if (attribute.Encoder == null) { if (!converters.TryGetValue(this.memberDeclaredType, out this.converter)) { this.converter = new ValueMapping( @@ -169,9 +184,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <param name="message">The message instance containing the member whose value should be set.</param> /// <param name="value">The string representation of the value to set.</param> internal void SetValue(IMessage message, string value) { - if (message == null) { - throw new ArgumentNullException("message"); - } + Contract.Requires<ArgumentNullException>(message != null); try { if (this.IsConstantValue) { diff --git a/src/DotNetOpenAuth/Messaging/Reflection/ValueMapping.cs b/src/DotNetOpenAuth/Messaging/Reflection/ValueMapping.cs index 332274e..1c7631e 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/ValueMapping.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/ValueMapping.cs @@ -6,10 +6,12 @@ namespace DotNetOpenAuth.Messaging.Reflection { using System; + using System.Diagnostics.Contracts; /// <summary> /// A pair of conversion functions to map some type to a string and back again. /// </summary> + [ContractVerification(true)] internal struct ValueMapping { /// <summary> /// The mapping function that converts some custom type to a string. @@ -27,8 +29,8 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <param name="toString">The mapping function that converts some custom type to a string.</param> /// <param name="toValue">The mapping function that converts a string to some custom type.</param> internal ValueMapping(Func<object, string> toString, Func<string, object> toValue) { - ErrorUtilities.VerifyArgumentNotNull(toString, "toString"); - ErrorUtilities.VerifyArgumentNotNull(toValue, "toValue"); + Contract.Requires<ArgumentNullException>(toString != null); + Contract.Requires<ArgumentNullException>(toValue != null); this.ValueToString = toString; this.StringToValue = toValue; @@ -39,7 +41,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// </summary> /// <param name="encoder">The encoder.</param> internal ValueMapping(IMessagePartEncoder encoder) { - ErrorUtilities.VerifyArgumentNotNull(encoder, "encoder"); + Contract.Requires<ArgumentNullException>(encoder != null); var nullEncoder = encoder as IMessagePartNullEncoder; string nullString = nullEncoder != null ? nullEncoder.EncodedNullValue : null; diff --git a/src/DotNetOpenAuth/Messaging/StandardWebRequestHandler.cs b/src/DotNetOpenAuth/Messaging/StandardWebRequestHandler.cs index cc991cd..5b17c09 100644 --- a/src/DotNetOpenAuth/Messaging/StandardWebRequestHandler.cs +++ b/src/DotNetOpenAuth/Messaging/StandardWebRequestHandler.cs @@ -6,6 +6,7 @@ namespace DotNetOpenAuth.Messaging { using System; + using System.Diagnostics.Contracts; using System.IO; using System.Net; using System.Net.Sockets; @@ -16,7 +17,7 @@ namespace DotNetOpenAuth.Messaging { /// The default handler for transmitting <see cref="HttpWebRequest"/> instances /// and returning the responses. /// </summary> - internal class StandardWebRequestHandler : IDirectWebRequestHandler { + public class StandardWebRequestHandler : IDirectWebRequestHandler { /// <summary> /// The set of options this web request handler supports. /// </summary> @@ -36,6 +37,7 @@ namespace DotNetOpenAuth.Messaging { /// <returns> /// <c>true</c> if this instance can support the specified options; otherwise, <c>false</c>. /// </returns> + [Pure] public bool CanSupport(DirectWebRequestOptions options) { return (options & ~SupportedOptions) == 0; } @@ -76,9 +78,6 @@ namespace DotNetOpenAuth.Messaging { /// a single exception type for hosts to catch.</para> /// </remarks> public Stream GetRequestStream(HttpWebRequest request, DirectWebRequestOptions options) { - ErrorUtilities.VerifyArgumentNotNull(request, "request"); - ErrorUtilities.VerifySupported(this.CanSupport(options), MessagingStrings.DirectWebRequestOptionsNotSupported, options, this.GetType().Name); - return GetRequestStreamCore(request); } @@ -118,9 +117,6 @@ namespace DotNetOpenAuth.Messaging { /// value, if set, shoud be Closed before throwing.</para> /// </remarks> public IncomingWebResponse GetResponse(HttpWebRequest request, DirectWebRequestOptions options) { - ErrorUtilities.VerifyArgumentNotNull(request, "request"); - ErrorUtilities.VerifySupported(this.CanSupport(options), MessagingStrings.DirectWebRequestOptionsNotSupported, options, this.GetType().Name); - // This request MAY have already been prepared by GetRequestStream, but // we have no guarantee, so do it just to be safe. PrepareRequest(request, false); @@ -229,7 +225,7 @@ namespace DotNetOpenAuth.Messaging { /// <param name="request">The request.</param> /// <param name="preparingPost"><c>true</c> if this is a POST request whose headers have not yet been sent out; <c>false</c> otherwise.</param> private static void PrepareRequest(HttpWebRequest request, bool preparingPost) { - ErrorUtilities.VerifyArgumentNotNull(request, "request"); + Contract.Requires<ArgumentNullException>(request != null); // Be careful to not try to change the HTTP headers that have already gone out. if (preparingPost || request.Method == "GET") { diff --git a/src/DotNetOpenAuth/Messaging/UntrustedWebRequestHandler.cs b/src/DotNetOpenAuth/Messaging/UntrustedWebRequestHandler.cs index 1656155..3ea1bf2 100644 --- a/src/DotNetOpenAuth/Messaging/UntrustedWebRequestHandler.cs +++ b/src/DotNetOpenAuth/Messaging/UntrustedWebRequestHandler.cs @@ -9,6 +9,7 @@ namespace DotNetOpenAuth.Messaging { using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; using System.Globalization; using System.IO; using System.Net; @@ -87,7 +88,7 @@ namespace DotNetOpenAuth.Messaging { /// </summary> /// <param name="chainedWebRequestHandler">The chained web request handler.</param> public UntrustedWebRequestHandler(IDirectWebRequestHandler chainedWebRequestHandler) { - ErrorUtilities.VerifyArgumentNotNull(chainedWebRequestHandler, "chainedWebRequestHandler"); + Contract.Requires<ArgumentNullException>(chainedWebRequestHandler != null); this.chainedWebRequestHandler = chainedWebRequestHandler; if (Debugger.IsAttached) { @@ -111,7 +112,7 @@ namespace DotNetOpenAuth.Messaging { } set { - ErrorUtilities.VerifyArgumentInRange(value >= 2048, "value"); + Contract.Requires<ArgumentOutOfRangeException>(value >= 2048); this.maximumBytesToRead = value; } } @@ -126,7 +127,7 @@ namespace DotNetOpenAuth.Messaging { } set { - ErrorUtilities.VerifyArgumentInRange(value >= 0, "value"); + Contract.Requires<ArgumentOutOfRangeException>(value >= 0); this.maximumRedirections = value; } } @@ -185,6 +186,7 @@ namespace DotNetOpenAuth.Messaging { /// <returns> /// <c>true</c> if this instance can support the specified options; otherwise, <c>false</c>. /// </returns> + [Pure] public bool CanSupport(DirectWebRequestOptions options) { // We support whatever our chained handler supports, plus RequireSsl. return this.chainedWebRequestHandler.CanSupport(options & ~DirectWebRequestOptions.RequireSsl); @@ -207,7 +209,6 @@ namespace DotNetOpenAuth.Messaging { /// a single exception type for hosts to catch.</para> /// </remarks> public Stream GetRequestStream(HttpWebRequest request, DirectWebRequestOptions options) { - ErrorUtilities.VerifyArgumentNotNull(request, "request"); this.EnsureAllowableRequestUri(request.RequestUri, (options & DirectWebRequestOptions.RequireSsl) != 0); this.PrepareRequest(request, true); @@ -234,8 +235,6 @@ namespace DotNetOpenAuth.Messaging { /// </remarks> [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Uri(Uri, string) accepts second arguments that Uri(Uri, new Uri(string)) does not that we must support.")] public IncomingWebResponse GetResponse(HttpWebRequest request, DirectWebRequestOptions options) { - ErrorUtilities.VerifyArgumentNotNull(request, "request"); - // This request MAY have already been prepared by GetRequestStream, but // we have no guarantee, so do it just to be safe. this.PrepareRequest(request, false); @@ -315,7 +314,7 @@ namespace DotNetOpenAuth.Messaging { /// <c>true</c> if this is a loopback IP address; <c>false</c> otherwise. /// </returns> private static bool IsIPv6Loopback(IPAddress ip) { - ErrorUtilities.VerifyArgumentNotNull(ip, "ip"); + Contract.Requires<ArgumentNullException>(ip != null); byte[] addressBytes = ip.GetAddressBytes(); for (int i = 0; i < addressBytes.Length - 1; i++) { if (addressBytes[i] != 0) { @@ -338,9 +337,9 @@ namespace DotNetOpenAuth.Messaging { /// <c>true</c> if the specified host falls within at least one of the given lists; otherwise, <c>false</c>. /// </returns> private static bool IsHostInList(string host, ICollection<string> stringList, ICollection<Regex> regexList) { - ErrorUtilities.VerifyNonZeroLength(host, "host"); - ErrorUtilities.VerifyArgumentNotNull(stringList, "stringList"); - ErrorUtilities.VerifyArgumentNotNull(regexList, "regexList"); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(host)); + Contract.Requires<ArgumentNullException>(stringList != null); + Contract.Requires<ArgumentNullException>(regexList != null); foreach (string testHost in stringList) { if (string.Equals(host, testHost, StringComparison.OrdinalIgnoreCase)) { return true; @@ -396,7 +395,7 @@ namespace DotNetOpenAuth.Messaging { /// <c>true</c> if [is URI allowable] [the specified URI]; otherwise, <c>false</c>. /// </returns> private bool IsUriAllowable(Uri uri) { - ErrorUtilities.VerifyArgumentNotNull(uri, "uri"); + Contract.Requires<ArgumentNullException>(uri != null); if (!this.allowableSchemes.Contains(uri.Scheme)) { Logger.Http.WarnFormat("Rejecting URL {0} because it uses a disallowed scheme.", uri); return false; @@ -456,7 +455,7 @@ namespace DotNetOpenAuth.Messaging { /// <param name="request">The request to prepare.</param> /// <param name="preparingPost"><c>true</c> if this is a POST request whose headers have not yet been sent out; <c>false</c> otherwise.</param> private void PrepareRequest(HttpWebRequest request, bool preparingPost) { - ErrorUtilities.VerifyArgumentNotNull(request, "request"); + Contract.Requires<ArgumentNullException>(request != null); // Be careful to not try to change the HTTP headers that have already gone out. if (preparingPost || request.Method == "GET") { diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/HmacSha1SigningBindingElement.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/HmacSha1SigningBindingElement.cs index 42a38fe..53930bc 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/HmacSha1SigningBindingElement.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/HmacSha1SigningBindingElement.cs @@ -31,7 +31,6 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// This method signs the message per OAuth 1.0 section 9.2. /// </remarks> protected override string GetSignature(ITamperResistantOAuthMessage message) { - ErrorUtilities.VerifyOperation(this.Channel != null, "Channel property has not been set."); string key = GetConsumerAndTokenSecretString(message); HashAlgorithm hasher = new HMACSHA1(Encoding.ASCII.GetBytes(key)); string baseString = ConstructSignatureBaseString(message, this.Channel.MessageDescriptions.GetAccessor(message)); diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/ICombinedOpenIdProviderTokenManager.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/ICombinedOpenIdProviderTokenManager.cs index ff004cb..dd28e71 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/ICombinedOpenIdProviderTokenManager.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/ICombinedOpenIdProviderTokenManager.cs @@ -15,7 +15,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// This interface should be implemented by the same class that implements /// <see cref="ITokenManager"/> in order to enable the OpenID+OAuth extension. /// </remarks> - public interface ICombinedOpenIdProviderTokenManager { + public interface ICombinedOpenIdProviderTokenManager : IOpenIdOAuthTokenManager, ITokenManager { /// <summary> /// Gets the OAuth consumer key for a given OpenID relying party realm. /// </summary> diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderTokenManager.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderTokenManager.cs index 02ebffb..ea0e90c 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderTokenManager.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderTokenManager.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.Linq; using System.Text; @@ -14,6 +15,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// A token manager for use by a web site in its role as a /// service provider. /// </summary> + [ContractClass(typeof(IServiceProviderTokenManagerContract))] public interface IServiceProviderTokenManager : ITokenManager { /// <summary> /// Gets the Consumer description for a given a Consumer Key. @@ -24,6 +26,18 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { IConsumerDescription GetConsumer(string consumerKey); /// <summary> + /// Checks whether a given request token has already been authorized + /// by some user for use by the Consumer that requested it. + /// </summary> + /// <param name="requestToken">The Consumer's request token.</param> + /// <returns> + /// True if the request token has already been fully authorized by the user + /// who owns the relevant protected resources. False if the token has not yet + /// been authorized, has expired or does not exist. + /// </returns> + bool IsRequestTokenAuthorized(string requestToken); + + /// <summary> /// Gets details on the named request token. /// </summary> /// <param name="token">The request token.</param> @@ -50,5 +64,197 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// log and throw the appropriate error. /// </remarks> IServiceProviderAccessToken GetAccessToken(string token); + + /// <summary> + /// Persists any changes made to the token. + /// </summary> + /// <param name="token">The token whose properties have been changed.</param> + /// <remarks> + /// This library will invoke this method after making a set + /// of changes to the token as part of a web request to give the host + /// the opportunity to persist those changes to a database. + /// Depending on the object persistence framework the host site uses, + /// this method MAY not need to do anything (if changes made to the token + /// will automatically be saved without any extra handling). + /// </remarks> + void UpdateToken(IServiceProviderRequestToken token); + } + + /// <summary> + /// Code contract class for the <see cref="IServiceProviderTokenManager"/> interface. + /// </summary> + [ContractClassFor(typeof(IServiceProviderTokenManager))] + internal class IServiceProviderTokenManagerContract : IServiceProviderTokenManager { + /// <summary> + /// Initializes a new instance of the <see cref="IServiceProviderTokenManagerContract"/> class. + /// </summary> + internal IServiceProviderTokenManagerContract() { + } + + #region IServiceProviderTokenManager Members + + /// <summary> + /// Gets the Consumer description for a given a Consumer Key. + /// </summary> + /// <param name="consumerKey">The Consumer Key.</param> + /// <returns> + /// A description of the consumer. Never null. + /// </returns> + /// <exception cref="KeyNotFoundException">Thrown if the consumer key cannot be found.</exception> + IConsumerDescription IServiceProviderTokenManager.GetConsumer(string consumerKey) { + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(consumerKey)); + Contract.Ensures(Contract.Result<IConsumerDescription>() != null); + throw new NotImplementedException(); + } + + /// <summary> + /// Checks whether a given request token has already been authorized + /// by some user for use by the Consumer that requested it. + /// </summary> + /// <param name="requestToken">The Consumer's request token.</param> + /// <returns> + /// True if the request token has already been fully authorized by the user + /// who owns the relevant protected resources. False if the token has not yet + /// been authorized, has expired or does not exist. + /// </returns> + bool IServiceProviderTokenManager.IsRequestTokenAuthorized(string requestToken) { + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(requestToken)); + throw new NotImplementedException(); + } + + /// <summary> + /// Gets details on the named request token. + /// </summary> + /// <param name="token">The request token.</param> + /// <returns>A description of the token. Never null.</returns> + /// <exception cref="KeyNotFoundException">Thrown if the token cannot be found.</exception> + /// <remarks> + /// It is acceptable for implementations to find the token, see that it has expired, + /// delete it from the database and then throw <see cref="KeyNotFoundException"/>, + /// or alternatively it can return the expired token anyway and the OAuth channel will + /// log and throw the appropriate error. + /// </remarks> + IServiceProviderRequestToken IServiceProviderTokenManager.GetRequestToken(string token) { + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(token)); + Contract.Ensures(Contract.Result<IServiceProviderRequestToken>() != null); + throw new NotImplementedException(); + } + + /// <summary> + /// Gets details on the named access token. + /// </summary> + /// <param name="token">The access token.</param> + /// <returns>A description of the token. Never null.</returns> + /// <exception cref="KeyNotFoundException">Thrown if the token cannot be found.</exception> + /// <remarks> + /// It is acceptable for implementations to find the token, see that it has expired, + /// delete it from the database and then throw <see cref="KeyNotFoundException"/>, + /// or alternatively it can return the expired token anyway and the OAuth channel will + /// log and throw the appropriate error. + /// </remarks> + IServiceProviderAccessToken IServiceProviderTokenManager.GetAccessToken(string token) { + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(token)); + Contract.Ensures(Contract.Result<IServiceProviderAccessToken>() != null); + throw new NotImplementedException(); + } + + /// <summary> + /// Persists any changes made to the token. + /// </summary> + /// <param name="token">The token whose properties have been changed.</param> + /// <remarks> + /// This library will invoke this method after making a set + /// of changes to the token as part of a web request to give the host + /// the opportunity to persist those changes to a database. + /// Depending on the object persistence framework the host site uses, + /// this method MAY not need to do anything (if changes made to the token + /// will automatically be saved without any extra handling). + /// </remarks> + void IServiceProviderTokenManager.UpdateToken(IServiceProviderRequestToken token) { + Contract.Requires<ArgumentNullException>(token != null); + throw new NotImplementedException(); + } + + #endregion + + #region ITokenManager Members + + /// <summary> + /// Gets the Token Secret given a request or access token. + /// </summary> + /// <param name="token">The request or access token.</param> + /// <returns> + /// The secret associated with the given token. + /// </returns> + /// <exception cref="ArgumentException">Thrown if the secret cannot be found for the given token.</exception> + string ITokenManager.GetTokenSecret(string token) { + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(token)); + Contract.Ensures(Contract.Result<string>() != null); + throw new NotImplementedException(); + } + + /// <summary> + /// Stores a newly generated unauthorized request token, secret, and optional + /// application-specific parameters for later recall. + /// </summary> + /// <param name="request">The request message that resulted in the generation of a new unauthorized request token.</param> + /// <param name="response">The response message that includes the unauthorized request token.</param> + /// <exception cref="ArgumentException">Thrown if the consumer key is not registered, or a required parameter was not found in the parameters collection.</exception> + /// <remarks> + /// Request tokens stored by this method SHOULD NOT associate any user account with this token. + /// It usually opens up security holes in your application to do so. Instead, you associate a user + /// account with access tokens (not request tokens) in the <see cref="ITokenManager.ExpireRequestTokenAndStoreNewAccessToken"/> + /// method. + /// </remarks> + void ITokenManager.StoreNewRequestToken(DotNetOpenAuth.OAuth.Messages.UnauthorizedTokenRequest request, DotNetOpenAuth.OAuth.Messages.ITokenSecretContainingMessage response) { + Contract.Requires<ArgumentNullException>(request != null); + Contract.Requires<ArgumentNullException>(response != null); + throw new NotImplementedException(); + } + + /// <summary> + /// Deletes a request token and its associated secret and stores a new access token and secret. + /// </summary> + /// <param name="consumerKey">The Consumer that is exchanging its request token for an access token.</param> + /// <param name="requestToken">The Consumer's request token that should be deleted/expired.</param> + /// <param name="accessToken">The new access token that is being issued to the Consumer.</param> + /// <param name="accessTokenSecret">The secret associated with the newly issued access token.</param> + /// <remarks> + /// <para> + /// Any scope of granted privileges associated with the request token from the + /// original call to <see cref="ITokenManager.StoreNewRequestToken"/> should be carried over + /// to the new Access Token. + /// </para> + /// <para> + /// To associate a user account with the new access token, + /// <see cref="System.Web.HttpContext.User">HttpContext.Current.User</see> may be + /// useful in an ASP.NET web application within the implementation of this method. + /// Alternatively you may store the access token here without associating with a user account, + /// and wait until <see cref="WebConsumer.ProcessUserAuthorization()"/> or + /// <see cref="DesktopConsumer.ProcessUserAuthorization(string, string)"/> return the access + /// token to associate the access token with a user account at that point. + /// </para> + /// </remarks> + void ITokenManager.ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken, string accessTokenSecret) { + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(consumerKey)); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(requestToken)); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(accessToken)); + Contract.Requires<ArgumentNullException>(accessTokenSecret != null); + throw new NotImplementedException(); + } + + /// <summary> + /// Classifies a token as a request token or an access token. + /// </summary> + /// <param name="token">The token to classify.</param> + /// <returns> + /// Request or Access token, or invalid if the token is not recognized. + /// </returns> + TokenType ITokenManager.GetTokenType(string token) { + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(token)); + throw new NotImplementedException(); + } + + #endregion } } diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/ITokenManager.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/ITokenManager.cs index 46aa03f..532e4b0 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/ITokenManager.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/ITokenManager.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.Linq; using System.Text; using DotNetOpenAuth.OAuth.Messages; @@ -16,6 +17,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// and recall of tokens and secrets for an individual OAuth consumer /// or service provider. /// </summary> + [ContractClass(typeof(ITokenManagerContract))] public interface ITokenManager { /// <summary> /// Gets the Token Secret given a request or access token. @@ -32,19 +34,92 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// <param name="request">The request message that resulted in the generation of a new unauthorized request token.</param> /// <param name="response">The response message that includes the unauthorized request token.</param> /// <exception cref="ArgumentException">Thrown if the consumer key is not registered, or a required parameter was not found in the parameters collection.</exception> + /// <remarks> + /// Request tokens stored by this method SHOULD NOT associate any user account with this token. + /// It usually opens up security holes in your application to do so. Instead, you associate a user + /// account with access tokens (not request tokens) in the <see cref="ExpireRequestTokenAndStoreNewAccessToken"/> + /// method. + /// </remarks> void StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response); /// <summary> - /// Checks whether a given request token has already been authorized - /// by some user for use by the Consumer that requested it. + /// Deletes a request token and its associated secret and stores a new access token and secret. + /// </summary> + /// <param name="consumerKey">The Consumer that is exchanging its request token for an access token.</param> + /// <param name="requestToken">The Consumer's request token that should be deleted/expired.</param> + /// <param name="accessToken">The new access token that is being issued to the Consumer.</param> + /// <param name="accessTokenSecret">The secret associated with the newly issued access token.</param> + /// <remarks> + /// <para> + /// Any scope of granted privileges associated with the request token from the + /// original call to <see cref="StoreNewRequestToken"/> should be carried over + /// to the new Access Token. + /// </para> + /// <para> + /// To associate a user account with the new access token, + /// <see cref="System.Web.HttpContext.User">HttpContext.Current.User</see> may be + /// useful in an ASP.NET web application within the implementation of this method. + /// Alternatively you may store the access token here without associating with a user account, + /// and wait until <see cref="WebConsumer.ProcessUserAuthorization()"/> or + /// <see cref="DesktopConsumer.ProcessUserAuthorization(string, string)"/> return the access + /// token to associate the access token with a user account at that point. + /// </para> + /// </remarks> + void ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken, string accessTokenSecret); + + /// <summary> + /// Classifies a token as a request token or an access token. + /// </summary> + /// <param name="token">The token to classify.</param> + /// <returns>Request or Access token, or invalid if the token is not recognized.</returns> + TokenType GetTokenType(string token); + } + + /// <summary> + /// The code contract class for the <see cref="ITokenManager"/> interface. + /// </summary> + [ContractClassFor(typeof(ITokenManager))] + internal class ITokenManagerContract : ITokenManager { + /// <summary> + /// Initializes a new instance of the <see cref="ITokenManagerContract"/> class. + /// </summary> + internal ITokenManagerContract() { + } + + #region ITokenManager Members + + /// <summary> + /// Gets the Token Secret given a request or access token. /// </summary> - /// <param name="requestToken">The Consumer's request token.</param> + /// <param name="token">The request or access token.</param> /// <returns> - /// True if the request token has already been fully authorized by the user - /// who owns the relevant protected resources. False if the token has not yet - /// been authorized, has expired or does not exist. + /// The secret associated with the given token. /// </returns> - bool IsRequestTokenAuthorized(string requestToken); + /// <exception cref="ArgumentException">Thrown if the secret cannot be found for the given token.</exception> + string ITokenManager.GetTokenSecret(string token) { + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(token)); + Contract.Ensures(Contract.Result<string>() != null); + throw new NotImplementedException(); + } + + /// <summary> + /// Stores a newly generated unauthorized request token, secret, and optional + /// application-specific parameters for later recall. + /// </summary> + /// <param name="request">The request message that resulted in the generation of a new unauthorized request token.</param> + /// <param name="response">The response message that includes the unauthorized request token.</param> + /// <exception cref="ArgumentException">Thrown if the consumer key is not registered, or a required parameter was not found in the parameters collection.</exception> + /// <remarks> + /// Request tokens stored by this method SHOULD NOT associate any user account with this token. + /// It usually opens up security holes in your application to do so. Instead, you associate a user + /// account with access tokens (not request tokens) in the <see cref="ITokenManager.ExpireRequestTokenAndStoreNewAccessToken"/> + /// method. + /// </remarks> + void ITokenManager.StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response) { + Contract.Requires<ArgumentNullException>(request != null); + Contract.Requires<ArgumentNullException>(response != null); + throw new NotImplementedException(); + } /// <summary> /// Deletes a request token and its associated secret and stores a new access token and secret. @@ -54,17 +129,41 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// <param name="accessToken">The new access token that is being issued to the Consumer.</param> /// <param name="accessTokenSecret">The secret associated with the newly issued access token.</param> /// <remarks> + /// <para> /// Any scope of granted privileges associated with the request token from the - /// original call to <see cref="StoreNewRequestToken"/> should be carried over + /// original call to <see cref="ITokenManager.StoreNewRequestToken"/> should be carried over /// to the new Access Token. + /// </para> + /// <para> + /// To associate a user account with the new access token, + /// <see cref="System.Web.HttpContext.User">HttpContext.Current.User</see> may be + /// useful in an ASP.NET web application within the implementation of this method. + /// Alternatively you may store the access token here without associating with a user account, + /// and wait until <see cref="WebConsumer.ProcessUserAuthorization()"/> or + /// <see cref="DesktopConsumer.ProcessUserAuthorization(string, string)"/> return the access + /// token to associate the access token with a user account at that point. + /// </para> /// </remarks> - void ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken, string accessTokenSecret); + void ITokenManager.ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken, string accessTokenSecret) { + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(consumerKey)); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(requestToken)); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(accessToken)); + Contract.Requires<ArgumentNullException>(accessTokenSecret != null); + throw new NotImplementedException(); + } /// <summary> /// Classifies a token as a request token or an access token. /// </summary> /// <param name="token">The token to classify.</param> - /// <returns>Request or Access token, or invalid if the token is not recognized.</returns> - TokenType GetTokenType(string token); + /// <returns> + /// Request or Access token, or invalid if the token is not recognized. + /// </returns> + TokenType ITokenManager.GetTokenType(string token) { + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(token)); + throw new NotImplementedException(); + } + + #endregion } } diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs index d325825..8c5980f 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs @@ -64,11 +64,11 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// </param> internal OAuthChannel(ITamperProtectionChannelBindingElement signingBindingElement, INonceStore store, ITokenManager tokenManager, IMessageFactory messageTypeProvider) : base(messageTypeProvider, InitializeBindingElements(signingBindingElement, store, tokenManager)) { - ErrorUtilities.VerifyArgumentNotNull(tokenManager, "tokenManager"); + Contract.Requires<ArgumentNullException>(tokenManager != null); + Contract.Requires<ArgumentNullException>(signingBindingElement != null); + Contract.Requires<ArgumentException>(signingBindingElement.SignatureCallback == null, OAuthStrings.SigningElementAlreadyAssociatedWithChannel); this.TokenManager = tokenManager; - ErrorUtilities.VerifyArgumentNamed(signingBindingElement.SignatureCallback == null, "signingBindingElement", OAuthStrings.SigningElementAlreadyAssociatedWithChannel); - signingBindingElement.SignatureCallback = this.SignatureCallback; } @@ -101,7 +101,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// <param name="request">The message to attach.</param> /// <returns>The initialized web request.</returns> internal HttpWebRequest InitializeRequest(IDirectedProtocolMessage request) { - ErrorUtilities.VerifyArgumentNotNull(request, "request"); + Contract.Requires<ArgumentNullException>(request != null); ProcessOutgoingMessage(request); return this.CreateHttpRequest(request); @@ -114,8 +114,6 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// <param name="request">The HTTP request to search.</param> /// <returns>The deserialized message, if one is found. Null otherwise.</returns> protected override IDirectedProtocolMessage ReadFromRequestCore(HttpRequestInfo request) { - ErrorUtilities.VerifyArgumentNotNull(request, "request"); - var fields = new Dictionary<string, string>(); // First search the Authorization header. @@ -178,8 +176,6 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// The deserialized message parts, if found. Null otherwise. /// </returns> protected override IDictionary<string, string> ReadFromResponseCore(IncomingWebResponse response) { - ErrorUtilities.VerifyArgumentNotNull(response, "response"); - string body = response.GetResponseReader().ReadToEnd(); return HttpUtility.ParseQueryString(body).ToDictionary(); } @@ -192,21 +188,19 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// The <see cref="HttpRequest"/> prepared to send the request. /// </returns> protected override HttpWebRequest CreateHttpRequest(IDirectedProtocolMessage request) { - ErrorUtilities.VerifyArgumentNotNull(request, "request"); - ErrorUtilities.VerifyArgumentNamed(request.Recipient != null, "request", MessagingStrings.DirectedMessageMissingRecipient); - - IDirectedProtocolMessage oauthRequest = request as IDirectedProtocolMessage; - ErrorUtilities.VerifyArgument(oauthRequest != null, MessagingStrings.UnexpectedType, typeof(IDirectedProtocolMessage), request.GetType()); - HttpWebRequest httpRequest; - HttpDeliveryMethods transmissionMethod = oauthRequest.HttpMethods; + HttpDeliveryMethods transmissionMethod = request.HttpMethods; if ((transmissionMethod & HttpDeliveryMethods.AuthorizationHeaderRequest) != 0) { httpRequest = this.InitializeRequestAsAuthHeader(request); } else if ((transmissionMethod & HttpDeliveryMethods.PostRequest) != 0) { httpRequest = this.InitializeRequestAsPost(request); } else if ((transmissionMethod & HttpDeliveryMethods.GetRequest) != 0) { httpRequest = InitializeRequestAsGet(request); + } else if ((transmissionMethod & HttpDeliveryMethods.PutRequest) != 0) { + httpRequest = this.InitializeRequestAsPut(request); + } else if ((transmissionMethod & HttpDeliveryMethods.DeleteRequest) != 0) { + httpRequest = InitializeRequestAsDelete(request); } else { throw new NotSupportedException(); } @@ -223,8 +217,6 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// This method implements spec V1.0 section 5.3. /// </remarks> protected override OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response) { - ErrorUtilities.VerifyArgumentNotNull(response, "response"); - var messageAccessor = this.MessageDescriptions.GetAccessor(response); var fields = messageAccessor.Serialize(); string responseBody = MessagingUtilities.CreateQueryString(fields); @@ -273,8 +265,8 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// <param name="source">The dictionary with names and values to encode.</param> /// <param name="destination">The dictionary to add the encoded pairs to.</param> private static void UriEscapeParameters(IDictionary<string, string> source, IDictionary<string, string> destination) { - ErrorUtilities.VerifyArgumentNotNull(source, "source"); - ErrorUtilities.VerifyArgumentNotNull(destination, "destination"); + Contract.Requires<ArgumentNullException>(source != null); + Contract.Requires<ArgumentNullException>(destination != null); foreach (var pair in source) { var key = MessagingUtilities.EscapeUriDataStringRfc3986(pair.Key); @@ -289,14 +281,13 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// <param name="message">The message.</param> /// <returns>"POST", "GET" or some other similar http verb.</returns> private static string GetHttpMethod(IDirectedProtocolMessage message) { - Contract.Requires(message != null); - ErrorUtilities.VerifyArgumentNotNull(message, "message"); + Contract.Requires<ArgumentNullException>(message != null); var signedMessage = message as ITamperResistantOAuthMessage; if (signedMessage != null) { return signedMessage.HttpMethod; } else { - return (message.HttpMethods & HttpDeliveryMethods.PostRequest) != 0 ? "POST" : "GET"; + return MessagingUtilities.GetHttpVerb(message.HttpMethods); } } diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthConsumerMessageFactory.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthConsumerMessageFactory.cs index e05bb62..327b923 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthConsumerMessageFactory.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthConsumerMessageFactory.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth.Messages; @@ -38,9 +39,6 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// UserAuthorizationResponse /// </remarks> public virtual IDirectedProtocolMessage GetNewRequestMessage(MessageReceivingEndpoint recipient, IDictionary<string, string> fields) { - ErrorUtilities.VerifyArgumentNotNull(recipient, "recipient"); - ErrorUtilities.VerifyArgumentNotNull(fields, "fields"); - MessageBase message = null; if (fields.ContainsKey("oauth_token")) { @@ -74,9 +72,6 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// AuthorizedTokenResponse /// </remarks> public virtual IDirectResponseProtocolMessage GetNewResponseMessage(IDirectedProtocolMessage request, IDictionary<string, string> fields) { - ErrorUtilities.VerifyArgumentNotNull(request, "request"); - ErrorUtilities.VerifyArgumentNotNull(fields, "fields"); - MessageBase message = null; // All response messages have the oauth_token field. diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthHttpMethodBindingElement.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthHttpMethodBindingElement.cs index c9df0e8..37fb80b 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthHttpMethodBindingElement.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthHttpMethodBindingElement.cs @@ -42,15 +42,13 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { if (oauthMessage != null) { HttpDeliveryMethods transmissionMethod = oauthMessage.HttpMethods; - if ((transmissionMethod & HttpDeliveryMethods.PostRequest) != 0) { - oauthMessage.HttpMethod = "POST"; - } else if ((transmissionMethod & HttpDeliveryMethods.GetRequest) != 0) { - oauthMessage.HttpMethod = "GET"; - } else { + try { + oauthMessage.HttpMethod = MessagingUtilities.GetHttpVerb(transmissionMethod); + return MessageProtections.None; + } catch (ArgumentException ex) { + Logger.OAuth.Error("Unrecognized HttpDeliveryMethods value.", ex); return null; } - - return MessageProtections.None; } else { return null; } diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthIdentity.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthIdentity.cs index bd57012..65bde20 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthIdentity.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthIdentity.cs @@ -24,8 +24,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// </summary> /// <param name="username">The username.</param> internal OAuthIdentity(string username) { - Contract.Requires(!String.IsNullOrEmpty(username)); - ErrorUtilities.VerifyNonZeroLength(username, "username"); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(username)); this.Name = username; } diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthPrincipal.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthPrincipal.cs index a9f363a..025ef09 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthPrincipal.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthPrincipal.cs @@ -31,7 +31,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// <param name="token">The access token.</param> internal OAuthPrincipal(IServiceProviderAccessToken token) : this(token.Username, token.Roles) { - Contract.Requires(token != null); + Contract.Requires<ArgumentNullException>(token != null); this.AccessToken = token.Token; } diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthServiceProviderMessageFactory.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthServiceProviderMessageFactory.cs index abb99d8..5b3c918 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthServiceProviderMessageFactory.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthServiceProviderMessageFactory.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth.Messages; @@ -25,7 +26,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// </summary> /// <param name="tokenManager">The token manager instance to use.</param> public OAuthServiceProviderMessageFactory(IServiceProviderTokenManager tokenManager) { - ErrorUtilities.VerifyArgumentNotNull(tokenManager, "tokenManager"); + Contract.Requires<ArgumentNullException>(tokenManager != null); this.tokenManager = tokenManager; } @@ -50,9 +51,6 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// AccessProtectedResourceRequest /// </remarks> public virtual IDirectedProtocolMessage GetNewRequestMessage(MessageReceivingEndpoint recipient, IDictionary<string, string> fields) { - ErrorUtilities.VerifyArgumentNotNull(recipient, "recipient"); - ErrorUtilities.VerifyArgumentNotNull(fields, "fields"); - MessageBase message = null; Protocol protocol = Protocol.V10; // default to assuming the less-secure 1.0 instead of 1.0a until we prove otherwise. string token; @@ -119,9 +117,6 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// None. /// </remarks> public virtual IDirectResponseProtocolMessage GetNewResponseMessage(IDirectedProtocolMessage request, IDictionary<string, string> fields) { - ErrorUtilities.VerifyArgumentNotNull(request, "request"); - ErrorUtilities.VerifyArgumentNotNull(fields, "fields"); - Logger.OAuth.Error("Service Providers are not expected to ever receive responses."); return null; } diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/PlaintextSigningBindingElement.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/PlaintextSigningBindingElement.cs index 9f99066..22e5f20 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/PlaintextSigningBindingElement.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/PlaintextSigningBindingElement.cs @@ -41,7 +41,12 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// <param name="message">The message that needs to be signed.</param> /// <returns>True if this binding element can be used to sign the message. False otherwise.</returns> protected override bool IsMessageApplicable(ITamperResistantOAuthMessage message) { - return string.Equals(message.Recipient.Scheme, "https", StringComparison.OrdinalIgnoreCase); + if (string.Equals(message.Recipient.Scheme, "https", StringComparison.OrdinalIgnoreCase)) { + return true; + } else { + Logger.Bindings.DebugFormat("The {0} element will not sign this message because the URI scheme is not https.", this.GetType().Name); + return false; + } } /// <summary> diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/RsaSha1SigningBindingElement.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/RsaSha1SigningBindingElement.cs index 4f8b5e5..f7b8370 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/RsaSha1SigningBindingElement.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/RsaSha1SigningBindingElement.cs @@ -33,8 +33,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// <param name="signingCertificate">The certificate used to sign outgoing messages.</param> public RsaSha1SigningBindingElement(X509Certificate2 signingCertificate) : base(HashAlgorithmName) { - Contract.Requires(signingCertificate != null); - ErrorUtilities.VerifyArgumentNotNull(signingCertificate, "signingCertificate"); + Contract.Requires<ArgumentNullException>(signingCertificate != null); this.SigningCertificate = signingCertificate; } @@ -46,8 +45,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// <param name="tokenManager">The token manager.</param> public RsaSha1SigningBindingElement(IServiceProviderTokenManager tokenManager) : base(HashAlgorithmName) { - Contract.Requires(tokenManager != null); - ErrorUtilities.VerifyArgumentNotNull(tokenManager, "tokenManager"); + Contract.Requires<ArgumentNullException>(tokenManager != null); this.tokenManager = tokenManager; } @@ -66,7 +64,6 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// This method signs the message per OAuth 1.0 section 9.3. /// </remarks> protected override string GetSignature(ITamperResistantOAuthMessage message) { - ErrorUtilities.VerifyArgumentNotNull(message, "message"); ErrorUtilities.VerifyOperation(this.SigningCertificate != null, OAuthStrings.X509CertificateNotProvidedForSigning); string signatureBaseString = ConstructSignatureBaseString(message, this.Channel.MessageDescriptions.GetAccessor(message)); diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs index 6991818..004e7d5 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs @@ -148,19 +148,10 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// This method implements OAuth 1.0 section 9.1. /// </remarks> internal static string ConstructSignatureBaseString(ITamperResistantOAuthMessage message, MessageDictionary messageDictionary) { - Contract.Requires(message != null); - Contract.Requires(messageDictionary != null); - Contract.Requires(messageDictionary.Message == message); - - if (String.IsNullOrEmpty(message.HttpMethod)) { - throw new ArgumentException( - string.Format( - CultureInfo.CurrentCulture, - MessagingStrings.ArgumentPropertyMissing, - typeof(ITamperResistantOAuthMessage).Name, - "HttpMethod"), - "message"); - } + Contract.Requires<ArgumentNullException>(message != null); + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(message.HttpMethod)); + Contract.Requires<ArgumentNullException>(messageDictionary != null); + Contract.Requires<ArgumentException>(messageDictionary.Message == message); List<string> signatureBaseStringElements = new List<string>(3); diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBaseContract.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBaseContract.cs index 7019b45..7b369c3 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBaseContract.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBaseContract.cs @@ -40,7 +40,8 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// <param name="message">The message to sign.</param> /// <returns>The signature for the message.</returns> protected override string GetSignature(ITamperResistantOAuthMessage message) { - Contract.Requires(this.Channel != null); + Contract.Requires<ArgumentNullException>(message != null); + Contract.Requires<InvalidOperationException>(this.Channel != null); throw new NotImplementedException(); } } diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementChain.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementChain.cs index f88e7ab..bdb0219 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementChain.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementChain.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.Linq; using System.Text; using DotNetOpenAuth.Messaging; @@ -29,19 +30,10 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// in preferred use order. /// </param> internal SigningBindingElementChain(ITamperProtectionChannelBindingElement[] signers) { - if (signers == null) { - throw new ArgumentNullException("signers"); - } - if (signers.Length == 0) { - throw new ArgumentException(MessagingStrings.SequenceContainsNoElements, "signers"); - } - if (signers.Contains(null)) { - throw new ArgumentException(MessagingStrings.SequenceContainsNullElement, "signers"); - } - MessageProtections protection = signers[0].Protection; - if (signers.Any(element => element.Protection != protection)) { - throw new ArgumentException(OAuthStrings.SigningElementsMustShareSameProtection, "signers"); - } + Contract.Requires<ArgumentNullException>(signers != null); + Contract.Requires<ArgumentException>(signers.Length > 0); + Contract.Requires<ArgumentException>(!signers.Contains(null), MessagingStrings.SequenceContainsNullElement); + Contract.Requires<ArgumentException>(signers.Select(s => s.Protection).Distinct().Count() == 1, OAuthStrings.SigningElementsMustShareSameProtection); this.signers = signers; } diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/TokenHandlingBindingElement.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/TokenHandlingBindingElement.cs index 3e75e7b..f9547c6 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/TokenHandlingBindingElement.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/TokenHandlingBindingElement.cs @@ -29,8 +29,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// </summary> /// <param name="tokenManager">The token manager.</param> internal TokenHandlingBindingElement(IServiceProviderTokenManager tokenManager) { - Contract.Requires(tokenManager != null); - ErrorUtilities.VerifyArgumentNotNull(tokenManager, "tokenManager"); + Contract.Requires<ArgumentNullException>(tokenManager != null); this.tokenManager = tokenManager; } @@ -68,11 +67,11 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable. /// </remarks> public MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { - ErrorUtilities.VerifyArgumentNotNull(message, "message"); - var userAuthResponse = message as UserAuthorizationResponse; if (userAuthResponse != null && userAuthResponse.Version >= Protocol.V10a.Version) { - this.tokenManager.GetRequestToken(userAuthResponse.RequestToken).VerificationCode = userAuthResponse.VerificationCode; + var requestToken = this.tokenManager.GetRequestToken(userAuthResponse.RequestToken); + requestToken.VerificationCode = userAuthResponse.VerificationCode; + this.tokenManager.UpdateToken(requestToken); return MessageProtections.None; } @@ -80,10 +79,14 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { var grantRequestTokenResponse = message as UnauthorizedTokenResponse; if (grantRequestTokenResponse != null) { this.tokenManager.StoreNewRequestToken(grantRequestTokenResponse.RequestMessage, grantRequestTokenResponse); - this.tokenManager.GetRequestToken(grantRequestTokenResponse.RequestToken).ConsumerVersion = grantRequestTokenResponse.Version; + + // The host may have already set these properties, but just to make sure... + var requestToken = this.tokenManager.GetRequestToken(grantRequestTokenResponse.RequestToken); + requestToken.ConsumerVersion = grantRequestTokenResponse.Version; if (grantRequestTokenResponse.RequestMessage.Callback != null) { - this.tokenManager.GetRequestToken(grantRequestTokenResponse.RequestToken).Callback = grantRequestTokenResponse.RequestMessage.Callback; + requestToken.Callback = grantRequestTokenResponse.RequestMessage.Callback; } + this.tokenManager.UpdateToken(requestToken); return MessageProtections.None; } @@ -109,8 +112,6 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable. /// </remarks> public MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { - ErrorUtilities.VerifyArgumentNotNull(message, "message"); - var authorizedTokenRequest = message as AuthorizedTokenRequest; if (authorizedTokenRequest != null) { if (authorizedTokenRequest.Version >= Protocol.V10a.Version) { @@ -142,7 +143,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// </summary> /// <param name="message">The incoming message carrying the access token.</param> private void VerifyThrowTokenNotExpired(AccessProtectedResourceRequest message) { - ErrorUtilities.VerifyArgumentNotNull(message, "message"); + Contract.Requires<ArgumentNullException>(message != null); try { IServiceProviderAccessToken token = this.tokenManager.GetAccessToken(message.AccessToken); diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/UriOrOobEncoding.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/UriOrOobEncoding.cs index 5aedc9d..287ef01 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/UriOrOobEncoding.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/UriOrOobEncoding.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.Linq; using System.Text; using DotNetOpenAuth.Messaging; @@ -51,8 +52,6 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// The <paramref name="value"/> in string form, ready for message transport. /// </returns> public string Encode(object value) { - ErrorUtilities.VerifyArgumentNotNull(value, "value"); - Uri uriValue = (Uri)value; return uriValue.AbsoluteUri; } diff --git a/src/DotNetOpenAuth/OAuth/ConsumerBase.cs b/src/DotNetOpenAuth/OAuth/ConsumerBase.cs index 55b40ac..b9d4718 100644 --- a/src/DotNetOpenAuth/OAuth/ConsumerBase.cs +++ b/src/DotNetOpenAuth/OAuth/ConsumerBase.cs @@ -26,11 +26,11 @@ namespace DotNetOpenAuth.OAuth { /// <param name="serviceDescription">The endpoints and behavior of the Service Provider.</param> /// <param name="tokenManager">The host's method of storing and recalling tokens and secrets.</param> protected ConsumerBase(ServiceProviderDescription serviceDescription, IConsumerTokenManager tokenManager) { - ErrorUtilities.VerifyArgumentNotNull(serviceDescription, "serviceDescription"); - ErrorUtilities.VerifyArgumentNotNull(tokenManager, "tokenManager"); + Contract.Requires<ArgumentNullException>(serviceDescription != null); + Contract.Requires<ArgumentNullException>(tokenManager != null); ITamperProtectionChannelBindingElement signingElement = serviceDescription.CreateTamperProtectionElement(); - INonceStore store = new NonceMemoryStore(StandardExpirationBindingElement.DefaultMaximumMessageAge); + INonceStore store = new NonceMemoryStore(StandardExpirationBindingElement.MaximumMessageAge); this.OAuthChannel = new OAuthChannel(signingElement, store, tokenManager); this.ServiceProvider = serviceDescription; this.SecuritySettings = DotNetOpenAuthSection.Configuration.OAuth.Consumer.SecuritySettings.CreateSecuritySettings(); @@ -80,10 +80,8 @@ namespace DotNetOpenAuth.OAuth { /// <param name="accessToken">The access token that permits access to the protected resource.</param> /// <returns>The initialized WebRequest object.</returns> public HttpWebRequest PrepareAuthorizedRequest(MessageReceivingEndpoint endpoint, string accessToken) { - Contract.Requires(endpoint != null); - Contract.Requires(!String.IsNullOrEmpty(accessToken)); - ErrorUtilities.VerifyArgumentNotNull(endpoint, "endpoint"); - ErrorUtilities.VerifyNonZeroLength(accessToken, "accessToken"); + Contract.Requires<ArgumentNullException>(endpoint != null); + Contract.Requires<ArgumentNullException>(!String.IsNullOrEmpty(accessToken)); return this.PrepareAuthorizedRequest(endpoint, accessToken, EmptyDictionary<string, string>.Instance); } @@ -97,12 +95,9 @@ namespace DotNetOpenAuth.OAuth { /// <param name="extraData">Extra parameters to include in the message. Must not be null, but may be empty.</param> /// <returns>The initialized WebRequest object.</returns> public HttpWebRequest PrepareAuthorizedRequest(MessageReceivingEndpoint endpoint, string accessToken, IDictionary<string, string> extraData) { - Contract.Requires(endpoint != null); - Contract.Requires(!String.IsNullOrEmpty(accessToken)); - Contract.Requires(extraData != null); - ErrorUtilities.VerifyArgumentNotNull(endpoint, "endpoint"); - ErrorUtilities.VerifyNonZeroLength(accessToken, "accessToken"); - ErrorUtilities.VerifyArgumentNotNull(extraData, "extraData"); + Contract.Requires<ArgumentNullException>(endpoint != null); + Contract.Requires<ArgumentNullException>(!String.IsNullOrEmpty(accessToken)); + Contract.Requires<ArgumentNullException>(extraData != null); IDirectedProtocolMessage message = this.CreateAuthorizingMessage(endpoint, accessToken); foreach (var pair in extraData) { @@ -130,8 +125,7 @@ namespace DotNetOpenAuth.OAuth { /// </remarks> [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Type of parameter forces the method to apply only to specific scenario.")] public HttpWebRequest PrepareAuthorizedRequest(AccessProtectedResourceRequest message) { - Contract.Requires(message != null); - ErrorUtilities.VerifyArgumentNotNull(message, "message"); + Contract.Requires<ArgumentNullException>(message != null); return this.OAuthChannel.InitializeRequest(message); } @@ -169,10 +163,8 @@ namespace DotNetOpenAuth.OAuth { /// <param name="accessToken">The access token that permits access to the protected resource.</param> /// <returns>The initialized WebRequest object.</returns> protected internal AccessProtectedResourceRequest CreateAuthorizingMessage(MessageReceivingEndpoint endpoint, string accessToken) { - Contract.Requires(endpoint != null); - Contract.Requires(!String.IsNullOrEmpty(accessToken)); - ErrorUtilities.VerifyArgumentNotNull(endpoint, "endpoint"); - ErrorUtilities.VerifyNonZeroLength(accessToken, "accessToken"); + Contract.Requires<ArgumentNullException>(endpoint != null); + Contract.Requires<ArgumentNullException>(!String.IsNullOrEmpty(accessToken)); AccessProtectedResourceRequest message = new AccessProtectedResourceRequest(endpoint, this.ServiceProvider.Version) { AccessToken = accessToken, @@ -233,9 +225,8 @@ namespace DotNetOpenAuth.OAuth { /// The access token assigned by the Service Provider. /// </returns> protected AuthorizedTokenResponse ProcessUserAuthorization(string requestToken, string verifier) { - Contract.Requires(!String.IsNullOrEmpty(requestToken)); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(requestToken)); Contract.Ensures(Contract.Result<AuthorizedTokenResponse>() != null); - ErrorUtilities.VerifyNonZeroLength(requestToken, "requestToken"); var requestAccess = new AuthorizedTokenRequest(this.ServiceProvider.AccessTokenEndpoint, this.ServiceProvider.Version) { RequestToken = requestToken, diff --git a/src/DotNetOpenAuth/OAuth/Messages/MessageBase.cs b/src/DotNetOpenAuth/OAuth/Messages/MessageBase.cs index 944bc5c..1a0ba23 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/MessageBase.cs +++ b/src/DotNetOpenAuth/OAuth/Messages/MessageBase.cs @@ -65,8 +65,8 @@ namespace DotNetOpenAuth.OAuth.Messages { /// <param name="originatingRequest">The request that asked for this direct response.</param> /// <param name="version">The OAuth version.</param> protected MessageBase(MessageProtections protectionRequired, IDirectedProtocolMessage originatingRequest, Version version) { - ErrorUtilities.VerifyArgumentNotNull(originatingRequest, "originatingRequest"); - ErrorUtilities.VerifyArgumentNotNull(version, "version"); + Contract.Requires<ArgumentNullException>(originatingRequest != null); + Contract.Requires<ArgumentNullException>(version != null); this.protectionRequired = protectionRequired; this.transport = MessageTransport.Direct; @@ -82,8 +82,8 @@ namespace DotNetOpenAuth.OAuth.Messages { /// <param name="recipient">The URI that a directed message will be delivered to.</param> /// <param name="version">The OAuth version.</param> protected MessageBase(MessageProtections protectionRequired, MessageTransport transport, MessageReceivingEndpoint recipient, Version version) { - ErrorUtilities.VerifyArgumentNotNull(recipient, "recipient"); - ErrorUtilities.VerifyArgumentNotNull(version, "version"); + Contract.Requires<ArgumentNullException>(recipient != null); + Contract.Requires<ArgumentNullException>(version != null); this.protectionRequired = protectionRequired; this.transport = transport; @@ -242,8 +242,7 @@ namespace DotNetOpenAuth.OAuth.Messages { /// The string representation of this object. /// </returns> internal virtual string ToString(Channel channel) { - Contract.Requires(channel != null); - ErrorUtilities.VerifyArgumentNotNull(channel, "channel"); + Contract.Requires<ArgumentNullException>(channel != null); StringBuilder builder = new StringBuilder(); builder.AppendFormat(CultureInfo.InvariantCulture, "{0} message", GetType().Name); diff --git a/src/DotNetOpenAuth/OAuth/Messages/SignedMessageBase.cs b/src/DotNetOpenAuth/OAuth/Messages/SignedMessageBase.cs index 1d8ca21..57ce470 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/SignedMessageBase.cs +++ b/src/DotNetOpenAuth/OAuth/Messages/SignedMessageBase.cs @@ -36,7 +36,7 @@ namespace DotNetOpenAuth.OAuth.Messages { : base(MessageProtections.All, transport, recipient, version) { ITamperResistantOAuthMessage self = (ITamperResistantOAuthMessage)this; HttpDeliveryMethods methods = ((IDirectedProtocolMessage)this).HttpMethods; - self.HttpMethod = (methods & HttpDeliveryMethods.PostRequest) != 0 ? "POST" : "GET"; + self.HttpMethod = MessagingUtilities.GetHttpVerb(methods); } #region ITamperResistantOAuthMessage Members diff --git a/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenResponse.cs b/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenResponse.cs index ce09213..0be9f63 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenResponse.cs +++ b/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenResponse.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.OAuth.Messages { using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; using DotNetOpenAuth.Messaging; /// <summary> @@ -26,8 +27,8 @@ namespace DotNetOpenAuth.OAuth.Messages { /// </remarks> protected internal UnauthorizedTokenResponse(UnauthorizedTokenRequest requestMessage, string requestToken, string tokenSecret) : this(requestMessage, requestMessage.Version) { - ErrorUtilities.VerifyArgumentNotNull(requestToken, "requestToken"); - ErrorUtilities.VerifyArgumentNotNull(tokenSecret, "tokenSecret"); + Contract.Requires<ArgumentNullException>(requestToken != null); + Contract.Requires<ArgumentNullException>(tokenSecret != null); this.RequestToken = requestToken; this.TokenSecret = tokenSecret; diff --git a/src/DotNetOpenAuth/OAuth/OAuthStrings.sr.resx b/src/DotNetOpenAuth/OAuth/OAuthStrings.sr.resx new file mode 100644 index 0000000..ef9ce60 --- /dev/null +++ b/src/DotNetOpenAuth/OAuth/OAuthStrings.sr.resx @@ -0,0 +1,162 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="AccessTokenNotAuthorized" xml:space="preserve"> + <value>Ne može se poslati pristupni token za Consumera za token zahteva '{0}' pre nego što bude autorizovan.</value> + </data> + <data name="BadAccessTokenInProtectedResourceRequest" xml:space="preserve"> + <value>Pristupni token '{0}' je neispravan ili je mu je istekao rok važenja.</value> + </data> + <data name="ConsumerOrTokenSecretNotFound" xml:space="preserve"> + <value>Greška u traženju šifre za korisnika ili za token.</value> + </data> + <data name="IncorrectVerifier" xml:space="preserve"> + <value>oauth_verifier argument je neispravan.</value> + </data> + <data name="InvalidIncomingMessage" xml:space="preserve"> + <value>Neispravna OAuth poruka je primljena i odbačena.</value> + </data> + <data name="MessageNotAllowedExtraParameters" xml:space="preserve"> + <value>Poruka {0} je sadržala višak podataka koji nije dozvoljen.</value> + </data> + <data name="MinimumConsumerVersionRequirementNotMet" xml:space="preserve"> + <value>Ovaj provajder OAuth usluge zahteva od OAuth korisnika da da implementira OAuth {0}, ali izgleda da korisnika podržava jedino {1}.</value> + </data> + <data name="OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface" xml:space="preserve"> + <value>Korišćenje OpenID+OAuth ekstenzije zahteva da token menadžer koji se koristi implementira {0} interfejs.</value> + </data> + <data name="OpenIdOAuthRealmConsumerKeyDoNotMatch" xml:space="preserve"> + <value>OpenID Relying Party domen nije prepoznat kao da pripada OAuth Consumeru identifikovanom po datom korisničkom tokenu.</value> + </data> + <data name="RequestUrlMustNotHaveOAuthParameters" xml:space="preserve"> + <value>URL upit zahteva NE SME sadržati bilo kakve OAuth Protocol Parametre.</value> + </data> + <data name="SigningElementAlreadyAssociatedWithChannel" xml:space="preserve"> + <value>Potpisujući element je već asociran sa kanalom.</value> + </data> + <data name="SigningElementsMustShareSameProtection" xml:space="preserve"> + <value>Svi elementi za potpisivanje moraju nuditi istu zaštitu poruke.</value> + </data> + <data name="TokenNotFound" xml:space="preserve"> + <value>Token u poruci nije prepoznat od strane provajdera usluge.</value> + </data> + <data name="X509CertificateNotProvidedForSigning" xml:space="preserve"> + <value>RSA-SHA1 potpisujući povezujući element nije podešen sa sertifikatom za potpisivanje.</value> + </data> +</root>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/OAuth/Protocol.cs b/src/DotNetOpenAuth/OAuth/Protocol.cs index cd4e486..a524ba7 100644 --- a/src/DotNetOpenAuth/OAuth/Protocol.cs +++ b/src/DotNetOpenAuth/OAuth/Protocol.cs @@ -9,6 +9,7 @@ namespace DotNetOpenAuth.OAuth { using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; using System.Linq; using System.Text; using DotNetOpenAuth.Messaging; @@ -139,10 +140,9 @@ namespace DotNetOpenAuth.OAuth { /// <param name="version">The OAuth version to get.</param> /// <returns>A matching <see cref="Protocol"/> instance.</returns> internal static Protocol Lookup(Version version) { - ErrorUtilities.VerifyArgumentNotNull(version, "version"); - Protocol protocol = AllVersions.FirstOrDefault(p => p.Version == version); - ErrorUtilities.VerifyArgumentInRange(protocol != null, "version"); - return protocol; + Contract.Requires<ArgumentNullException>(version != null); + Contract.Requires<ArgumentOutOfRangeException>(AllVersions.Any(p => p.Version == version)); + return AllVersions.First(p => p.Version == version); } } } diff --git a/src/DotNetOpenAuth/OAuth/ServiceProvider.cs b/src/DotNetOpenAuth/OAuth/ServiceProvider.cs index 95eee32..e2c82bb 100644 --- a/src/DotNetOpenAuth/OAuth/ServiceProvider.cs +++ b/src/DotNetOpenAuth/OAuth/ServiceProvider.cs @@ -69,6 +69,9 @@ namespace DotNetOpenAuth.OAuth { /// <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, DotNetOpenAuthSection.Configuration.OAuth.ServiceProvider.ApplicationStore.CreateInstance(HttpApplicationStore), messageTypeProvider) { + Contract.Requires<ArgumentNullException>(serviceDescription != null); + Contract.Requires<ArgumentNullException>(tokenManager != null); + Contract.Requires<ArgumentNullException>(messageTypeProvider != null); } /// <summary> @@ -89,10 +92,10 @@ namespace DotNetOpenAuth.OAuth { /// <param name="nonceStore">The nonce store.</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, INonceStore nonceStore, OAuthServiceProviderMessageFactory messageTypeProvider) { - ErrorUtilities.VerifyArgumentNotNull(serviceDescription, "serviceDescription"); - ErrorUtilities.VerifyArgumentNotNull(tokenManager, "tokenManager"); - ErrorUtilities.VerifyArgumentNotNull(nonceStore, "nonceStore"); - ErrorUtilities.VerifyArgumentNotNull(messageTypeProvider, "messageTypeProvider"); + Contract.Requires<ArgumentNullException>(serviceDescription != null); + Contract.Requires<ArgumentNullException>(tokenManager != null); + Contract.Requires<ArgumentNullException>(nonceStore != null); + Contract.Requires<ArgumentNullException>(messageTypeProvider != null); var signingElement = serviceDescription.CreateTamperProtectionElement(); this.ServiceDescription = serviceDescription; @@ -117,7 +120,7 @@ namespace DotNetOpenAuth.OAuth { context.Application.Lock(); try { if ((store = (INonceStore)context.Application[ApplicationStoreKey]) == null) { - context.Application[ApplicationStoreKey] = store = new NonceMemoryStore(StandardExpirationBindingElement.DefaultMaximumMessageAge); + context.Application[ApplicationStoreKey] = store = new NonceMemoryStore(StandardExpirationBindingElement.MaximumMessageAge); } } finally { context.Application.UnLock(); @@ -166,8 +169,7 @@ namespace DotNetOpenAuth.OAuth { } set { - Contract.Requires(value != null); - ErrorUtilities.VerifyArgumentNotNull(value, "value"); + Contract.Requires<ArgumentNullException>(value != null); this.channel = value; } } @@ -182,8 +184,7 @@ namespace DotNetOpenAuth.OAuth { /// length of the final string.</param> /// <returns>The verification code.</returns> public static string CreateVerificationCode(VerificationCodeFormat format, int length) { - Contract.Requires(length >= 0); - ErrorUtilities.VerifyArgumentInRange(length >= 0, "length"); + Contract.Requires<ArgumentOutOfRangeException>(length >= 0); switch (format) { case VerificationCodeFormat.IncludedInCallback: @@ -254,7 +255,7 @@ namespace DotNetOpenAuth.OAuth { /// <param name="request">The token request message the Consumer sent that the Service Provider is now responding to.</param> /// <returns>The response message to send using the <see cref="Channel"/>, after optionally adding extra data to it.</returns> public UnauthorizedTokenResponse PrepareUnauthorizedTokenMessage(UnauthorizedTokenRequest request) { - ErrorUtilities.VerifyArgumentNotNull(request, "request"); + Contract.Requires<ArgumentNullException>(request != null); string token = this.TokenGenerator.GenerateRequestToken(request.ConsumerKey); string secret = this.TokenGenerator.GenerateSecret(); @@ -291,11 +292,12 @@ namespace DotNetOpenAuth.OAuth { /// <summary> /// Gets the OAuth authorization request included with an OpenID authentication - /// request. + /// request, if there is one. /// </summary> /// <param name="openIdRequest">The OpenID authentication request.</param> /// <returns> - /// The scope of access the relying party is requesting. + /// The scope of access the relying party is requesting, or null if no OAuth request + /// is present. /// </returns> /// <remarks> /// <para>Call this method rather than simply extracting the OAuth extension @@ -303,9 +305,8 @@ namespace DotNetOpenAuth.OAuth { /// security measures that are required are taken.</para> /// </remarks> public AuthorizationRequest ReadAuthorizationRequest(IHostProcessedRequest openIdRequest) { - Contract.Requires(openIdRequest != null); - Contract.Requires(this.TokenManager is ICombinedOpenIdProviderTokenManager); - ErrorUtilities.VerifyArgumentNotNull(openIdRequest, "openIdAuthenticationRequest"); + Contract.Requires<ArgumentNullException>(openIdRequest != null); + Contract.Requires<InvalidOperationException>(this.TokenManager is ICombinedOpenIdProviderTokenManager); var openidTokenManager = this.TokenManager as ICombinedOpenIdProviderTokenManager; ErrorUtilities.VerifyOperation(openidTokenManager != null, OAuthStrings.OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface, typeof(IOpenIdOAuthTokenManager).FullName); @@ -325,24 +326,39 @@ namespace DotNetOpenAuth.OAuth { return authzRequest; } + /// <summary> + /// Attaches the authorization response to an OpenID authentication response. + /// </summary> + /// <param name="openIdAuthenticationRequest">The OpenID authentication request.</param> + /// <param name="consumerKey">The consumer key. Must be <c>null</c> if and only if <paramref name="scope"/> is null.</param> + /// <param name="scope">The approved access scope. Use <c>null</c> to indicate no access was granted. The empty string will be interpreted as some default level of access is granted.</param> + [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "We want to take IAuthenticationRequest because that's the only supported use case.")] + [Obsolete("Call the overload that doesn't take a consumerKey instead.")] + public void AttachAuthorizationResponse(IHostProcessedRequest openIdAuthenticationRequest, string consumerKey, string scope) { + Contract.Requires<ArgumentNullException>(openIdAuthenticationRequest != null); + Contract.Requires<ArgumentException>((consumerKey == null) == (scope == null)); + Contract.Requires<InvalidOperationException>(this.TokenManager is ICombinedOpenIdProviderTokenManager); + var openidTokenManager = (ICombinedOpenIdProviderTokenManager)this.TokenManager; + ErrorUtilities.VerifyArgument(consumerKey == null || consumerKey == openidTokenManager.GetConsumerKey(openIdAuthenticationRequest.Realm), "The consumer key and the realm did not match according to the token manager."); + + this.AttachAuthorizationResponse(openIdAuthenticationRequest, scope); + } + /// <summary> /// Attaches the authorization response to an OpenID authentication response. /// </summary> /// <param name="openIdAuthenticationRequest">The OpenID authentication request.</param> - /// <param name="consumerKey">The consumer key. May and should be <c>null</c> if and only if <paramref name="scope"/> is null.</param> /// <param name="scope">The approved access scope. Use <c>null</c> to indicate no access was granted. The empty string will be interpreted as some default level of access is granted.</param> [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "We want to take IAuthenticationRequest because that's the only supported use case.")] - public void AttachAuthorizationResponse(IAuthenticationRequest openIdAuthenticationRequest, string consumerKey, string scope) { - Contract.Requires(openIdAuthenticationRequest != null); - Contract.Requires((consumerKey == null) == (scope == null)); - Contract.Requires(this.TokenManager is IOpenIdOAuthTokenManager); - ErrorUtilities.VerifyArgumentNotNull(openIdAuthenticationRequest, "openIdAuthenticationRequest"); - var openidTokenManager = this.TokenManager as IOpenIdOAuthTokenManager; - ErrorUtilities.VerifyOperation(openidTokenManager != null, OAuthStrings.OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface, typeof(IOpenIdOAuthTokenManager).FullName); + public void AttachAuthorizationResponse(IHostProcessedRequest openIdAuthenticationRequest, string scope) { + Contract.Requires<ArgumentNullException>(openIdAuthenticationRequest != null); + Contract.Requires<InvalidOperationException>(this.TokenManager is ICombinedOpenIdProviderTokenManager); + var openidTokenManager = this.TokenManager as ICombinedOpenIdProviderTokenManager; IOpenIdMessageExtension response; if (scope != null) { // Generate an authorized request token to return to the relying party. + string consumerKey = openidTokenManager.GetConsumerKey(openIdAuthenticationRequest.Realm); var approvedResponse = new AuthorizationApprovedResponse { RequestToken = this.TokenGenerator.GenerateRequestToken(consumerKey), Scope = scope, @@ -367,8 +383,7 @@ namespace DotNetOpenAuth.OAuth { /// </returns> [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Consistent user experience with instance.")] public UserAuthorizationResponse PrepareAuthorizationResponse(UserAuthorizationRequest request) { - Contract.Requires(request != null); - ErrorUtilities.VerifyArgumentNotNull(request, "request"); + Contract.Requires<ArgumentNullException>(request != null); // It is very important for us to ignore the oauth_callback argument in the // UserAuthorizationRequest if the Consumer is a 1.0a consumer or else we @@ -405,10 +420,8 @@ namespace DotNetOpenAuth.OAuth { /// </returns> [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Consistent user experience with instance.")] public UserAuthorizationResponse PrepareAuthorizationResponse(UserAuthorizationRequest request, Uri callback) { - Contract.Requires(request != null); - Contract.Requires(callback != null); - ErrorUtilities.VerifyArgumentNotNull(request, "request"); - ErrorUtilities.VerifyArgumentNotNull(callback, "callback"); + Contract.Requires<ArgumentNullException>(request != null); + Contract.Requires<ArgumentNullException>(callback != null); var authorization = new UserAuthorizationResponse(callback, request.Version) { RequestToken = request.RequestToken, @@ -451,8 +464,7 @@ namespace DotNetOpenAuth.OAuth { /// <param name="request">The Consumer's message requesting an access token.</param> /// <returns>The HTTP response to actually send to the Consumer.</returns> public AuthorizedTokenResponse PrepareAccessTokenMessage(AuthorizedTokenRequest request) { - Contract.Requires(request != null); - ErrorUtilities.VerifyArgumentNotNull(request, "request"); + Contract.Requires<ArgumentNullException>(request != null); ErrorUtilities.VerifyProtocol(this.TokenManager.IsRequestTokenAuthorized(request.RequestToken), OAuthStrings.AccessTokenNotAuthorized, request.RequestToken); @@ -509,7 +521,7 @@ namespace DotNetOpenAuth.OAuth { /// </remarks> /// <exception cref="ProtocolException">Thrown if an unexpected message is attached to the request.</exception> public AccessProtectedResourceRequest ReadProtectedResourceAuthorization(HttpRequestInfo request) { - ErrorUtilities.VerifyArgumentNotNull(request, "request"); + Contract.Requires<ArgumentNullException>(request != null); AccessProtectedResourceRequest accessMessage; if (this.Channel.TryReadFromRequest<AccessProtectedResourceRequest>(request, out accessMessage)) { @@ -531,8 +543,7 @@ namespace DotNetOpenAuth.OAuth { /// <param name="request">The request.</param> /// <returns>The <see cref="IPrincipal"/> instance that can be used for access control of resources.</returns> public OAuthPrincipal CreatePrincipal(AccessProtectedResourceRequest request) { - Contract.Requires(request != null); - ErrorUtilities.VerifyArgumentNotNull(request, "request"); + Contract.Requires<ArgumentNullException>(request != null); IServiceProviderAccessToken accessToken = this.TokenManager.GetAccessToken(request.AccessToken); return new OAuthPrincipal(accessToken); diff --git a/src/DotNetOpenAuth/OAuth/WebConsumer.cs b/src/DotNetOpenAuth/OAuth/WebConsumer.cs index d86444d..de37b80 100644 --- a/src/DotNetOpenAuth/OAuth/WebConsumer.cs +++ b/src/DotNetOpenAuth/OAuth/WebConsumer.cs @@ -79,8 +79,7 @@ namespace DotNetOpenAuth.OAuth { /// <param name="openIdAuthenticationRequest">The OpenID authentication request.</param> /// <param name="scope">The scope of access that is requested of the service provider.</param> public void AttachAuthorizationRequest(IAuthenticationRequest openIdAuthenticationRequest, string scope) { - Contract.Requires(openIdAuthenticationRequest != null); - ErrorUtilities.VerifyArgumentNotNull(openIdAuthenticationRequest, "openIdAuthenticationRequest"); + Contract.Requires<ArgumentNullException>(openIdAuthenticationRequest != null); var authorizationRequest = new AuthorizationRequest { Consumer = this.ConsumerKey, @@ -102,9 +101,8 @@ namespace DotNetOpenAuth.OAuth { /// The token manager instance must implement <see cref="IOpenIdOAuthTokenManager"/>. /// </remarks> public AuthorizedTokenResponse ProcessUserAuthorization(IAuthenticationResponse openIdAuthenticationResponse) { - Contract.Requires(openIdAuthenticationResponse != null); - Contract.Requires(this.TokenManager is IOpenIdOAuthTokenManager); - ErrorUtilities.VerifyArgumentNotNull(openIdAuthenticationResponse, "openIdAuthenticationResponse"); + Contract.Requires<ArgumentNullException>(openIdAuthenticationResponse != null); + Contract.Requires<InvalidOperationException>(this.TokenManager is IOpenIdOAuthTokenManager); var openidTokenManager = this.TokenManager as IOpenIdOAuthTokenManager; ErrorUtilities.VerifyOperation(openidTokenManager != null, OAuthStrings.OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface, typeof(IOpenIdOAuthTokenManager).FullName); @@ -142,8 +140,7 @@ namespace DotNetOpenAuth.OAuth { /// <param name="request">The incoming HTTP request.</param> /// <returns>The access token, or null if no incoming authorization message was recognized.</returns> public AuthorizedTokenResponse ProcessUserAuthorization(HttpRequestInfo request) { - Contract.Requires(request != null); - ErrorUtilities.VerifyArgumentNotNull(request, "request"); + Contract.Requires<ArgumentNullException>(request != null); UserAuthorizationResponse authorizationMessage; if (this.Channel.TryReadFromRequest<UserAuthorizationResponse>(request, out authorizationMessage)) { diff --git a/src/DotNetOpenAuth/OpenId/Association.cs b/src/DotNetOpenAuth/OpenId/Association.cs index 5aeaaee..311ba58 100644 --- a/src/DotNetOpenAuth/OpenId/Association.cs +++ b/src/DotNetOpenAuth/OpenId/Association.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.OpenId { using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; using System.IO; using System.Security.Cryptography; using System.Text; @@ -23,6 +24,8 @@ namespace DotNetOpenAuth.OpenId { /// (dumb associations). /// </remarks> [DebuggerDisplay("Handle = {Handle}, Expires = {Expires}")] + [ContractVerification(true)] + [ContractClass(typeof(AssociationContract))] public abstract class Association { /// <summary> /// Initializes a new instance of the <see cref="Association"/> class. @@ -32,8 +35,12 @@ namespace DotNetOpenAuth.OpenId { /// <param name="totalLifeLength">How long the association will be useful.</param> /// <param name="issued">The UTC time of when this association was originally issued by the Provider.</param> protected Association(string handle, byte[] secret, TimeSpan totalLifeLength, DateTime issued) { - ErrorUtilities.VerifyNonZeroLength(handle, "handle"); - ErrorUtilities.VerifyArgumentNotNull(secret, "secret"); + Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(handle)); + Contract.Requires<ArgumentNullException>(secret != null); + Contract.Requires<ArgumentOutOfRangeException>(totalLifeLength > TimeSpan.Zero); + Contract.Requires<ArgumentException>(issued.Kind == DateTimeKind.Utc); + Contract.Requires<ArgumentOutOfRangeException>(issued <= DateTime.UtcNow); + Contract.Ensures(this.TotalLifeLength == totalLifeLength); this.Handle = handle; this.SecretKey = secret; @@ -85,7 +92,10 @@ namespace DotNetOpenAuth.OpenId { /// Never negative (counter runs to zero). /// </summary> protected internal long SecondsTillExpiration { - get { return Math.Max(0, (long)this.TimeTillExpiration.TotalSeconds); } + get { + Contract.Ensures(Contract.Result<long>() >= 0); + return Math.Max(0, (long)this.TimeTillExpiration.TotalSeconds); + } } /// <summary> @@ -98,7 +108,10 @@ namespace DotNetOpenAuth.OpenId { /// Gets the duration a secret key used for signing dumb client requests will be good for. /// </summary> protected static TimeSpan DumbSecretLifetime { - get { return DotNetOpenAuthSection.Configuration.OpenId.MaxAuthenticationTime; } + get { + Contract.Ensures(Contract.Result<TimeSpan>() > TimeSpan.Zero); + return DotNetOpenAuthSection.Configuration.OpenId.MaxAuthenticationTime; + } } /// <summary> @@ -113,7 +126,10 @@ namespace DotNetOpenAuth.OpenId { /// Associations that are not likely to last the duration of a user login are not worth using at all. /// </remarks> private static TimeSpan MinimumUsefulAssociationLifetime { - get { return DotNetOpenAuthSection.Configuration.OpenId.MaxAuthenticationTime; } + get { + Contract.Ensures(Contract.Result<TimeSpan>() > TimeSpan.Zero); + return DotNetOpenAuthSection.Configuration.OpenId.MaxAuthenticationTime; + } } /// <summary> @@ -143,12 +159,10 @@ namespace DotNetOpenAuth.OpenId { /// <see cref="IAssociationStore<TKey>.GetAssociation(TKey, SecuritySettings)"/> method. /// </returns> public static Association Deserialize(string handle, DateTime expiresUtc, byte[] privateData) { - if (string.IsNullOrEmpty(handle)) { - throw new ArgumentNullException("handle"); - } - if (privateData == null) { - throw new ArgumentNullException("privateData"); - } + Contract.Requires<ArgumentNullException>(!String.IsNullOrEmpty(handle)); + Contract.Requires<ArgumentNullException>(privateData != null); + Contract.Ensures(Contract.Result<Association>() != null); + expiresUtc = expiresUtc.ToUniversalTimeSafe(); TimeSpan remainingLifeLength = expiresUtc - DateTime.UtcNow; byte[] secret = privateData; // the whole of privateData is the secret key for now. @@ -174,12 +188,16 @@ namespace DotNetOpenAuth.OpenId { /// in this byte array, as they are useful for fast database lookup and are persisted separately. /// </remarks> public byte[] SerializePrivateData() { + Contract.Ensures(Contract.Result<byte[]>() != null); + // We may want to encrypt this secret using the machine.config private key, // and add data regarding which Association derivative will need to be // re-instantiated on deserialization. // For now, we just send out the secret key. We can derive the type from the length later. byte[] secretKeyCopy = new byte[this.SecretKey.Length]; - this.SecretKey.CopyTo(secretKeyCopy, 0); + if (this.SecretKey.Length > 0) { + this.SecretKey.CopyTo(secretKeyCopy, 0); + } return secretKeyCopy; } @@ -253,6 +271,7 @@ namespace DotNetOpenAuth.OpenId { /// <param name="data">The data to sign. This data will not be changed (the signature is the return value).</param> /// <returns>The calculated signature of the data.</returns> protected internal byte[] Sign(byte[] data) { + Contract.Requires<ArgumentNullException>(data != null); using (HashAlgorithm hasher = this.CreateHasher()) { return hasher.ComputeHash(data); } @@ -263,5 +282,19 @@ namespace DotNetOpenAuth.OpenId { /// </summary> /// <returns>The hash algorithm used for message signing.</returns> protected abstract HashAlgorithm CreateHasher(); + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + protected void ObjectInvariant() { + Contract.Invariant(!string.IsNullOrEmpty(this.Handle)); + Contract.Invariant(this.TotalLifeLength > TimeSpan.Zero); + Contract.Invariant(this.SecretKey != null); + } +#endif } } diff --git a/src/DotNetOpenAuth/OpenId/AssociationContract.cs b/src/DotNetOpenAuth/OpenId/AssociationContract.cs new file mode 100644 index 0000000..57f4fd9 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/AssociationContract.cs @@ -0,0 +1,65 @@ +//----------------------------------------------------------------------- +// <copyright file="AssociationContract.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId { + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.IO; + using System.Security.Cryptography; + using System.Text; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Code contract for the <see cref="Association"/> class. + /// </summary> + [ContractClassFor(typeof(Association))] + internal abstract class AssociationContract : Association { + /// <summary> + /// Prevents a default instance of the <see cref="AssociationContract"/> class from being created. + /// </summary> + private AssociationContract() + : base(null, null, TimeSpan.Zero, DateTime.Now) { + } + + /// <summary> + /// Gets the length (in bits) of the hash this association creates when signing. + /// </summary> + public override int HashBitLength { + get { + Contract.Ensures(Contract.Result<int>() > 0); + throw new NotImplementedException(); + } + } + + /// <summary> + /// The string to pass as the assoc_type value in the OpenID protocol. + /// </summary> + /// <param name="protocol">The protocol version of the message that the assoc_type value will be included in.</param> + /// <returns> + /// The value that should be used for the openid.assoc_type parameter. + /// </returns> + [Pure] + internal override string GetAssociationType(Protocol protocol) { + Contract.Requires<ArgumentNullException>(protocol != null); + throw new NotImplementedException(); + } + + /// <summary> + /// Returns the specific hash algorithm used for message signing. + /// </summary> + /// <returns> + /// The hash algorithm used for message signing. + /// </returns> + [Pure] + protected override HashAlgorithm CreateHasher() { + Contract.Ensures(Contract.Result<HashAlgorithm>() != null); + throw new NotImplementedException(); + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Associations.cs b/src/DotNetOpenAuth/OpenId/Associations.cs index fe2be42..4fd89c4 100644 --- a/src/DotNetOpenAuth/OpenId/Associations.cs +++ b/src/DotNetOpenAuth/OpenId/Associations.cs @@ -9,6 +9,8 @@ namespace DotNetOpenAuth.OpenId { using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; using System.Linq; using DotNetOpenAuth.Messaging; @@ -21,6 +23,7 @@ namespace DotNetOpenAuth.OpenId { /// can break if the collection is changed by another thread during enumeration. /// </remarks> [DebuggerDisplay("Count = {assocs.Count}")] + [ContractVerification(true)] internal class Associations { /// <summary> /// The lookup table where keys are the association handles and values are the associations themselves. @@ -44,6 +47,8 @@ namespace DotNetOpenAuth.OpenId { /// </remarks> public IEnumerable<Association> Best { get { + Contract.Ensures(Contract.Result<IEnumerable<Association>>() != null); + lock (this.associations) { return this.associations.OrderByDescending(assoc => assoc.Issued); } @@ -55,11 +60,14 @@ namespace DotNetOpenAuth.OpenId { /// </summary> /// <param name="association">The association to add to the collection.</param> public void Set(Association association) { - ErrorUtilities.VerifyArgumentNotNull(association, "association"); + Contract.Requires<ArgumentNullException>(association != null); + Contract.Ensures(this.Get(association.Handle) == association); lock (this.associations) { this.associations.Remove(association.Handle); // just in case one already exists. this.associations.Add(association); } + + Contract.Assume(this.Get(association.Handle) == association); } /// <summary> @@ -67,7 +75,10 @@ namespace DotNetOpenAuth.OpenId { /// </summary> /// <param name="handle">The handle to the required association.</param> /// <returns>The desired association, or null if none with the given handle could be found.</returns> + [Pure] public Association Get(string handle) { + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(handle)); + lock (this.associations) { if (this.associations.Contains(handle)) { return this.associations[handle]; @@ -83,6 +94,7 @@ namespace DotNetOpenAuth.OpenId { /// <param name="handle">The handle to the required association.</param> /// <returns>Whether an <see cref="Association"/> with the given handle was in the collection for removal.</returns> public bool Remove(string handle) { + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(handle)); lock (this.associations) { return this.associations.Remove(handle); } @@ -99,5 +111,17 @@ namespace DotNetOpenAuth.OpenId { } } } + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + protected void ObjectInvariant() { + Contract.Invariant(this.associations != null); + } +#endif } } diff --git a/src/DotNetOpenAuth/OpenId/Behaviors/BehaviorStrings.sr.resx b/src/DotNetOpenAuth/OpenId/Behaviors/BehaviorStrings.sr.resx new file mode 100644 index 0000000..2b1b911 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Behaviors/BehaviorStrings.sr.resx @@ -0,0 +1,123 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="PpidProviderNotGiven" xml:space="preserve"> + <value>Nijedan PPID provajder nije konfigurisan.</value> + </data> +</root>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/OpenId/Behaviors/GsaIcamProfile.cs b/src/DotNetOpenAuth/OpenId/Behaviors/GsaIcamProfile.cs index 8f3b78f..23377c8 100644 --- a/src/DotNetOpenAuth/OpenId/Behaviors/GsaIcamProfile.cs +++ b/src/DotNetOpenAuth/OpenId/Behaviors/GsaIcamProfile.cs @@ -71,8 +71,6 @@ namespace DotNetOpenAuth.OpenId.Behaviors { /// incompatible with each other. /// </remarks> void IRelyingPartyBehavior.ApplySecuritySettings(RelyingPartySecuritySettings securitySettings) { - ErrorUtilities.VerifyArgumentNotNull(securitySettings, "securitySettings"); - if (securitySettings.MaximumHashBitLength < 256) { securitySettings.MaximumHashBitLength = 256; } @@ -90,8 +88,6 @@ namespace DotNetOpenAuth.OpenId.Behaviors { /// </summary> /// <param name="request">The request.</param> void IRelyingPartyBehavior.OnOutgoingAuthenticationRequest(RelyingParty.IAuthenticationRequest request) { - ErrorUtilities.VerifyArgumentNotNull(request, "request"); - RelyingParty.AuthenticationRequest requestInternal = (RelyingParty.AuthenticationRequest)request; ErrorUtilities.VerifyProtocol(string.Equals(request.Realm.Scheme, Uri.UriSchemeHttps, StringComparison.Ordinal) || DisableSslRequirement, BehaviorStrings.RealmMustBeHttps); @@ -125,8 +121,6 @@ namespace DotNetOpenAuth.OpenId.Behaviors { /// </summary> /// <param name="assertion">The positive assertion.</param> void IRelyingPartyBehavior.OnIncomingPositiveAssertion(IAuthenticationResponse assertion) { - ErrorUtilities.VerifyArgumentNotNull(assertion, "assertion"); - PolicyResponse pape = assertion.GetExtension<PolicyResponse>(); ErrorUtilities.VerifyProtocol( pape != null && @@ -175,8 +169,6 @@ namespace DotNetOpenAuth.OpenId.Behaviors { /// itself as that instance may be shared across many requests. /// </remarks> bool IProviderBehavior.OnIncomingRequest(IRequest request) { - ErrorUtilities.VerifyArgumentNotNull(request, "request"); - var hostProcessedRequest = request as IHostProcessedRequest; if (hostProcessedRequest != null) { // Only apply our special policies if the RP requested it. @@ -206,8 +198,6 @@ namespace DotNetOpenAuth.OpenId.Behaviors { /// from handling it; <c>false</c> to allow other behaviors to process this request. /// </returns> bool IProviderBehavior.OnOutgoingResponse(Provider.IAuthenticationRequest request) { - ErrorUtilities.VerifyArgumentNotNull(request, "request"); - bool result = false; // Nothing to do for negative assertions. diff --git a/src/DotNetOpenAuth/OpenId/Behaviors/PpidGeneration.cs b/src/DotNetOpenAuth/OpenId/Behaviors/PpidGeneration.cs index f09e886..baef943 100644 --- a/src/DotNetOpenAuth/OpenId/Behaviors/PpidGeneration.cs +++ b/src/DotNetOpenAuth/OpenId/Behaviors/PpidGeneration.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OpenId.Behaviors { using System; using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; using System.Linq; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy; @@ -73,8 +74,6 @@ namespace DotNetOpenAuth.OpenId.Behaviors { /// from handling it; <c>false</c> to allow other behaviors to process this request. /// </returns> bool IProviderBehavior.OnOutgoingResponse(IAuthenticationRequest request) { - ErrorUtilities.VerifyArgumentNotNull(request, "request"); - // Nothing to do for negative assertions. if (!request.IsAuthenticated.Value) { return false; diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/BackwardCompatibilityBindingElement.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/BackwardCompatibilityBindingElement.cs index de9cda9..b730b1f 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/BackwardCompatibilityBindingElement.cs +++ b/src/DotNetOpenAuth/OpenId/ChannelElements/BackwardCompatibilityBindingElement.cs @@ -17,13 +17,13 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// </summary> internal class BackwardCompatibilityBindingElement : IChannelBindingElement { /// <summary> - /// The name of the callback parameter that stores the Provider Endpoint URL + /// The "dnoa.op_endpoint" callback parameter that stores the Provider Endpoint URL /// to tack onto the return_to URI. /// </summary> private const string ProviderEndpointParameterName = OpenIdUtilities.CustomParameterPrefix + "op_endpoint"; /// <summary> - /// The name of the callback parameter that stores the Claimed Identifier + /// The "dnoa.claimed_id" callback parameter that stores the Claimed Identifier /// to tack onto the return_to URI. /// </summary> private const string ClaimedIdentifierParameterName = OpenIdUtilities.CustomParameterPrefix + "claimed_id"; diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/ExtensionsBindingElement.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/ExtensionsBindingElement.cs index 6f9c79a..84adc59 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/ExtensionsBindingElement.cs +++ b/src/DotNetOpenAuth/OpenId/ChannelElements/ExtensionsBindingElement.cs @@ -33,8 +33,8 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// <param name="extensionFactory">The extension factory.</param> /// <param name="securitySettings">The security settings.</param> internal ExtensionsBindingElement(IOpenIdExtensionFactory extensionFactory, SecuritySettings securitySettings) { - ErrorUtilities.VerifyArgumentNotNull(extensionFactory, "extensionFactory"); - ErrorUtilities.VerifyArgumentNotNull(securitySettings, "securitySettings"); + Contract.Requires<ArgumentNullException>(extensionFactory != null); + Contract.Requires<ArgumentNullException>(securitySettings != null); this.ExtensionFactory = extensionFactory; this.relyingPartySecuritySettings = securitySettings as RelyingPartySecuritySettings; @@ -77,9 +77,6 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable. /// </remarks> public MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { - ErrorUtilities.VerifyArgumentNotNull(message, "message"); - ErrorUtilities.VerifyOperation(this.Channel != null, "Channel property has not been set."); - var extendableMessage = message as IProtocolMessageWithExtensions; if (extendableMessage != null) { Protocol protocol = Protocol.Lookup(message.Version); @@ -236,8 +233,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// A dictionary of message parts, including only signed parts when appropriate. /// </returns> private IDictionary<string, string> GetExtensionsDictionary(IProtocolMessage message, bool ignoreUnsigned) { - Contract.Requires(this.Channel != null); - ErrorUtilities.VerifyOperation(this.Channel != null, "Channel property has not been set."); + Contract.Requires<InvalidOperationException>(this.Channel != null); IndirectSignedResponse signedResponse = message as IndirectSignedResponse; if (signedResponse != null && ignoreUnsigned) { diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/KeyValueFormEncoding.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/KeyValueFormEncoding.cs index 03ba0f2..c363c11 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/KeyValueFormEncoding.cs +++ b/src/DotNetOpenAuth/OpenId/ChannelElements/KeyValueFormEncoding.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; using System.Globalization; using System.IO; using System.Text; @@ -99,7 +100,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// be used. /// </remarks> public static byte[] GetBytes(IEnumerable<KeyValuePair<string, string>> keysAndValues) { - ErrorUtilities.VerifyArgumentNotNull(keysAndValues, "keysAndValues"); + Contract.Requires<ArgumentNullException>(keysAndValues != null); MemoryStream ms = new MemoryStream(); using (StreamWriter sw = new StreamWriter(ms, textEncoding)) { diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs index 0f71ebc..b25e819 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs +++ b/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs @@ -51,7 +51,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// <param name="securitySettings">The security settings to apply.</param> internal OpenIdChannel(IAssociationStore<Uri> associationStore, INonceStore nonceStore, RelyingPartySecuritySettings securitySettings) : this(associationStore, nonceStore, new OpenIdMessageFactory(), securitySettings, false) { - Contract.Requires(securitySettings != null); + Contract.Requires<ArgumentNullException>(securitySettings != null); } /// <summary> @@ -63,7 +63,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// <param name="securitySettings">The security settings.</param> internal OpenIdChannel(IAssociationStore<AssociationRelyingPartyType> associationStore, INonceStore nonceStore, ProviderSecuritySettings securitySettings) : this(associationStore, nonceStore, new OpenIdMessageFactory(), securitySettings) { - Contract.Requires(securitySettings != null); + Contract.Requires<ArgumentNullException>(securitySettings != null); } /// <summary> @@ -77,8 +77,9 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// <param name="nonVerifying">A value indicating whether the channel is set up with no functional security binding elements.</param> private OpenIdChannel(IAssociationStore<Uri> associationStore, INonceStore nonceStore, IMessageFactory messageTypeProvider, RelyingPartySecuritySettings securitySettings, bool nonVerifying) : this(messageTypeProvider, InitializeBindingElements(associationStore, nonceStore, securitySettings, nonVerifying)) { - Contract.Requires(messageTypeProvider != null); - Contract.Requires(securitySettings != null); + Contract.Requires<ArgumentNullException>(messageTypeProvider != null); + Contract.Requires<ArgumentNullException>(securitySettings != null); + Contract.Requires<ArgumentException>(!nonVerifying || securitySettings is RelyingPartySecuritySettings); } /// <summary> @@ -91,8 +92,8 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// <param name="securitySettings">The security settings.</param> private OpenIdChannel(IAssociationStore<AssociationRelyingPartyType> associationStore, INonceStore nonceStore, IMessageFactory messageTypeProvider, ProviderSecuritySettings securitySettings) : this(messageTypeProvider, InitializeBindingElements(associationStore, nonceStore, securitySettings, false)) { - Contract.Requires(messageTypeProvider != null); - Contract.Requires(securitySettings != null); + Contract.Requires<ArgumentNullException>(messageTypeProvider != null); + Contract.Requires<ArgumentNullException>(securitySettings != null); } /// <summary> @@ -103,7 +104,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// <param name="bindingElements">The binding elements to use in sending and receiving messages.</param> private OpenIdChannel(IMessageFactory messageTypeProvider, IChannelBindingElement[] bindingElements) : base(messageTypeProvider, bindingElements) { - Contract.Requires(messageTypeProvider != null); + Contract.Requires<ArgumentNullException>(messageTypeProvider != null); // Customize the binding element order, since we play some tricks for higher // security and backward compatibility with older OpenID versions. @@ -207,10 +208,6 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// </returns> /// <exception cref="ProtocolException">Thrown when the response is not valid.</exception> protected override IDictionary<string, string> ReadFromResponseCore(IncomingWebResponse response) { - if (response == null) { - throw new ArgumentNullException("response"); - } - try { return this.keyValueForm.GetDictionary(response.ResponseStream); } catch (FormatException ex) { @@ -253,8 +250,6 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// This method implements spec V1.0 section 5.3. /// </remarks> protected override OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response) { - ErrorUtilities.VerifyArgumentNotNull(response, "response"); - var messageAccessor = this.MessageDescriptions.GetAccessor(response); var fields = messageAccessor.Serialize(); byte[] keyValueEncoding = KeyValueFormEncoding.GetBytes(fields); @@ -317,9 +312,8 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// </returns> [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Needed for code contracts.")] private static IChannelBindingElement[] InitializeBindingElements<T>(IAssociationStore<T> associationStore, INonceStore nonceStore, SecuritySettings securitySettings, bool nonVerifying) { - Contract.Requires(securitySettings != null); - Contract.Requires(!nonVerifying || securitySettings is RelyingPartySecuritySettings); - ErrorUtilities.VerifyArgumentNotNull(securitySettings, "securitySettings"); + Contract.Requires<ArgumentNullException>(securitySettings != null); + Contract.Requires<ArgumentException>(!nonVerifying || securitySettings is RelyingPartySecuritySettings); var rpSecuritySettings = securitySettings as RelyingPartySecuritySettings; var opSecuritySettings = securitySettings as ProviderSecuritySettings; diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdMessageFactory.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdMessageFactory.cs index 31a2da5..1e5ea4c 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdMessageFactory.cs +++ b/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdMessageFactory.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.Linq; using System.Text; using DotNetOpenAuth.Messaging; @@ -30,9 +31,6 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// deserialize to. Null if the request isn't recognized as a valid protocol message. /// </returns> public IDirectedProtocolMessage GetNewRequestMessage(MessageReceivingEndpoint recipient, IDictionary<string, string> fields) { - ErrorUtilities.VerifyArgumentNotNull(recipient, "recipient"); - ErrorUtilities.VerifyArgumentNotNull(fields, "fields"); - RequestBase message = null; // Discern the OpenID version of the message. @@ -97,9 +95,6 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// deserialize to. Null if the request isn't recognized as a valid protocol message. /// </returns> public IDirectResponseProtocolMessage GetNewResponseMessage(IDirectedProtocolMessage request, IDictionary<string, string> fields) { - ErrorUtilities.VerifyArgumentNotNull(request, "request"); - ErrorUtilities.VerifyArgumentNotNull(fields, "fields"); - DirectResponseBase message = null; // Discern the OpenID version of the message. diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToNonceBindingElement.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToNonceBindingElement.cs index 9c4c46d..9040404 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToNonceBindingElement.cs +++ b/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToNonceBindingElement.cs @@ -78,10 +78,8 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// <param name="nonceStore">The nonce store to use.</param> /// <param name="securitySettings">The security settings of the RP.</param> internal ReturnToNonceBindingElement(INonceStore nonceStore, RelyingPartySecuritySettings securitySettings) { - Contract.Requires(nonceStore != null); - Contract.Requires(securitySettings != null); - ErrorUtilities.VerifyArgumentNotNull(nonceStore, "nonceStore"); - ErrorUtilities.VerifyArgumentNotNull(securitySettings, "securitySettings"); + Contract.Requires<ArgumentNullException>(nonceStore != null); + Contract.Requires<ArgumentNullException>(securitySettings != null); this.nonceStore = nonceStore; this.securitySettings = securitySettings; @@ -144,6 +142,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { SignedResponseRequest request = message as SignedResponseRequest; if (this.UseRequestNonce(request)) { request.AddReturnToArguments(NonceParameter, CustomNonce.NewNonce().Serialize()); + request.SignReturnTo = true; // a nonce without a signature is completely pointless return MessageProtections.ReplayProtection; } @@ -171,9 +170,13 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { public MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { IndirectSignedResponse response = message as IndirectSignedResponse; if (this.UseRequestNonce(response)) { + if (!response.ReturnToParametersSignatureValidated) { + Logger.OpenId.Error("Incoming message is expected to have a nonce, but the return_to parameter is not signed."); + } + string nonceValue = response.GetReturnToArgument(NonceParameter); ErrorUtilities.VerifyProtocol( - nonceValue != null, + nonceValue != null && response.ReturnToParametersSignatureValidated, this.securitySettings.RejectUnsolicitedAssertions ? OpenIdStrings.UnsolicitedAssertionsNotAllowed : OpenIdStrings.UnsolicitedAssertionsNotAllowedFrom1xOPs); CustomNonce nonce = CustomNonce.Deserialize(nonceValue); @@ -254,7 +257,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// <param name="value">The base64-encoded value of the nonce.</param> /// <returns>The instantiated and initialized nonce.</returns> internal static CustomNonce Deserialize(string value) { - ErrorUtilities.VerifyNonZeroLength(value, "value"); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(value)); byte[] nonce = Convert.FromBase64String(value); Contract.Assume(nonce != null); diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs index a760c0d..438c496 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs +++ b/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { using System; using System.Collections.Generic; using System.Collections.Specialized; + using System.Diagnostics.Contracts; using System.Security.Cryptography; using System.Web; using DotNetOpenAuth.Messaging; @@ -52,7 +53,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// <param name="secretStore">The secret store from which to retrieve the secret used for signing.</param> /// <param name="securitySettings">The security settings.</param> internal ReturnToSignatureBindingElement(IAssociationStore<Uri> secretStore, RelyingPartySecuritySettings securitySettings) { - ErrorUtilities.VerifyArgumentNotNull(secretStore, "secretStore"); + Contract.Requires<ArgumentNullException>(secretStore != null); this.secretManager = new PrivateSecretManager(securitySettings, secretStore); } @@ -94,7 +95,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// </remarks> public MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { SignedResponseRequest request = message as SignedResponseRequest; - if (request != null && request.ReturnTo != null) { + if (request != null && request.ReturnTo != null && request.SignReturnTo) { request.AddReturnToArguments(ReturnToSignatureHandleParameterName, this.secretManager.CurrentHandle); request.AddReturnToArguments(ReturnToSignatureParameterName, this.GetReturnToSignature(request.ReturnTo)); @@ -161,7 +162,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// or other minor changes do not invalidate the signature. /// </remarks> private string GetReturnToSignature(Uri returnTo) { - ErrorUtilities.VerifyArgumentNotNull(returnTo, "returnTo"); + Contract.Requires<ArgumentNullException>(returnTo != null); // Assemble the dictionary to sign, taking care to remove the signature itself // in order to accurately reproduce the original signature (which of course didn't include diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/SigningBindingElement.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/SigningBindingElement.cs index 6544d49..3f4a998 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/SigningBindingElement.cs +++ b/src/DotNetOpenAuth/OpenId/ChannelElements/SigningBindingElement.cs @@ -55,8 +55,8 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// <param name="associationStore">The association store used to look up the secrets needed for signing.</param> /// <param name="securitySettings">The security settings.</param> internal SigningBindingElement(IAssociationStore<AssociationRelyingPartyType> associationStore, ProviderSecuritySettings securitySettings) { - ErrorUtilities.VerifyArgumentNotNull(associationStore, "associationStore"); - ErrorUtilities.VerifyArgumentNotNull(securitySettings, "securitySettings"); + Contract.Requires<ArgumentNullException>(associationStore != null); + Contract.Requires<ArgumentNullException>(securitySettings != null); this.opAssociations = associationStore; this.opSecuritySettings = securitySettings; @@ -190,7 +190,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// <c>true</c> if the relying party is vulnerable; otherwise, <c>false</c>. /// </returns> private static bool IsRelyingPartyVulnerableToReplays(SignedResponseRequest request, IndirectSignedResponse response) { - ErrorUtilities.VerifyArgumentNotNull(response, "response"); + Contract.Requires<ArgumentNullException>(response != null); // OpenID 2.0 includes replay protection as part of the protocol. if (response.Version.Major >= 2) { @@ -250,9 +250,9 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// <param name="association">The association to use to sign the message.</param> /// <returns>The calculated signature of the method.</returns> private string GetSignature(ITamperResistantOpenIdMessage signedMessage, Association association) { - ErrorUtilities.VerifyArgumentNotNull(signedMessage, "signedMessage"); - ErrorUtilities.VerifyNonZeroLength(signedMessage.SignedParameterOrder, "signedMessage.SignedParameterOrder"); - ErrorUtilities.VerifyArgumentNotNull(association, "association"); + Contract.Requires<ArgumentNullException>(signedMessage != null); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(signedMessage.SignedParameterOrder)); + Contract.Requires<ArgumentNullException>(association != null); // Prepare the parts to sign, taking care to replace an openid.mode value // of check_authentication with its original id_res so the signature matches. @@ -285,9 +285,8 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// the inclusion and order of message parts that will be signed. /// </returns> private string GetSignedParameterOrder(ITamperResistantOpenIdMessage signedMessage) { - Contract.Requires(this.Channel != null); - ErrorUtilities.VerifyArgumentNotNull(signedMessage, "signedMessage"); - ErrorUtilities.VerifyOperation(this.Channel != null, "Channel property has not been set."); + Contract.Requires<InvalidOperationException>(this.Channel != null); + Contract.Requires<ArgumentNullException>(signedMessage != null); Protocol protocol = Protocol.Lookup(signedMessage.Version); @@ -323,7 +322,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// <param name="signedMessage">The message to sign or verify.</param> /// <returns>The association to use to sign or verify the message.</returns> private Association GetAssociation(ITamperResistantOpenIdMessage signedMessage) { - Contract.Requires(signedMessage != null); + Contract.Requires<ArgumentNullException>(signedMessage != null); if (this.IsOnProvider) { // We're on a Provider to either sign (smart/dumb) or verify a dumb signature. diff --git a/src/DotNetOpenAuth/OpenId/DiffieHellmanUtilities.cs b/src/DotNetOpenAuth/OpenId/DiffieHellmanUtilities.cs index e4fea46..249f1f3 100644 --- a/src/DotNetOpenAuth/OpenId/DiffieHellmanUtilities.cs +++ b/src/DotNetOpenAuth/OpenId/DiffieHellmanUtilities.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OpenId { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.Globalization; using System.Linq; using System.Security.Cryptography; @@ -36,8 +37,8 @@ namespace DotNetOpenAuth.OpenId { /// <returns>The hashing algorithm to use.</returns> /// <exception cref="ProtocolException">Thrown if no match could be found for the given <paramref name="sessionType"/>.</exception> public static HashAlgorithm Lookup(Protocol protocol, string sessionType) { - ErrorUtilities.VerifyArgumentNotNull(protocol, "protocol"); - ErrorUtilities.VerifyArgumentNotNull(sessionType, "sessionType"); + Contract.Requires<ArgumentNullException>(protocol != null); + Contract.Requires<ArgumentNullException>(sessionType != null); // We COULD use just First instead of FirstOrDefault, but we want to throw ProtocolException instead of InvalidOperationException. DHSha match = diffieHellmanSessionTypes.FirstOrDefault(dhsha => String.Equals(dhsha.GetName(protocol), sessionType, StringComparison.Ordinal)); @@ -52,7 +53,7 @@ namespace DotNetOpenAuth.OpenId { /// <param name="hashSizeInBits">The hash size (in bits) that the DH session must have.</param> /// <returns>The value to be used for the openid.session_type parameter, or null if no match was found.</returns> internal static string GetNameForSize(Protocol protocol, int hashSizeInBits) { - ErrorUtilities.VerifyArgumentNotNull(protocol, "protocol"); + Contract.Requires<ArgumentNullException>(protocol != null); DHSha match = diffieHellmanSessionTypes.FirstOrDefault(dhsha => dhsha.Algorithm.HashSize == hashSizeInBits); return match != null ? match.GetName(protocol) : null; } @@ -72,10 +73,10 @@ namespace DotNetOpenAuth.OpenId { /// The secret itself if the encrypted version of the secret was given in <paramref name="remotePublicKey"/>. /// </returns> internal static byte[] SHAHashXorSecret(HashAlgorithm hasher, DiffieHellman dh, byte[] remotePublicKey, byte[] plainOrEncryptedSecret) { - ErrorUtilities.VerifyArgumentNotNull(hasher, "hasher"); - ErrorUtilities.VerifyArgumentNotNull(dh, "dh"); - ErrorUtilities.VerifyArgumentNotNull(remotePublicKey, "remotePublicKey"); - ErrorUtilities.VerifyArgumentNotNull(plainOrEncryptedSecret, "plainOrEncryptedSecret"); + Contract.Requires<ArgumentNullException>(hasher != null); + Contract.Requires<ArgumentNullException>(dh != null); + Contract.Requires<ArgumentNullException>(remotePublicKey != null); + Contract.Requires<ArgumentNullException>(plainOrEncryptedSecret != null); byte[] sharedBlock = dh.DecryptKeyExchange(remotePublicKey); byte[] sharedBlockHash = hasher.ComputeHash(EnsurePositive(sharedBlock)); @@ -101,7 +102,7 @@ namespace DotNetOpenAuth.OpenId { /// This is to be consistent with OpenID spec section 4.2. /// </remarks> internal static byte[] EnsurePositive(byte[] inputBytes) { - ErrorUtilities.VerifyArgumentNotNull(inputBytes, "inputBytes"); + Contract.Requires<ArgumentNullException>(inputBytes != null); if (inputBytes.Length == 0) { throw new ArgumentException(MessagingStrings.UnexpectedEmptyArray, "inputBytes"); } @@ -127,8 +128,8 @@ namespace DotNetOpenAuth.OpenId { /// <param name="algorithm">The hashing algorithm used in this particular Diffie-Hellman session type.</param> /// <param name="getName">A function that will return the value of the openid.session_type parameter for a given version of OpenID.</param> public DHSha(HashAlgorithm algorithm, Func<Protocol, string> getName) { - ErrorUtilities.VerifyArgumentNotNull(algorithm, "algorithm"); - ErrorUtilities.VerifyArgumentNotNull(getName, "getName"); + Contract.Requires<ArgumentNullException>(algorithm != null); + Contract.Requires<ArgumentNullException>(getName != null); this.GetName = getName; this.Algorithm = algorithm; diff --git a/src/DotNetOpenAuth/OpenId/Extensions/AliasManager.cs b/src/DotNetOpenAuth/OpenId/Extensions/AliasManager.cs index e16f9a4..0a84266 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/AliasManager.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/AliasManager.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.OpenId.Extensions { using System; using System.Collections.Generic; using System.Diagnostics; + using System.Diagnostics.Contracts; using System.Globalization; using System.Text; using DotNetOpenAuth.Messaging; @@ -19,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. @@ -44,7 +45,7 @@ namespace DotNetOpenAuth.OpenId.Extensions { /// <param name="typeUri">The type URI.</param> /// <returns>The alias assigned to this type URI. Never null.</returns> public string GetAlias(string typeUri) { - ErrorUtilities.VerifyNonZeroLength(typeUri, "typeUri"); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(typeUri)); string alias; return this.typeUriToAliasMap.TryGetValue(typeUri, out alias) ? alias : this.AssignNewAlias(typeUri); } @@ -55,8 +56,8 @@ namespace DotNetOpenAuth.OpenId.Extensions { /// <param name="alias">The alias.</param> /// <param name="typeUri">The type URI.</param> public void SetAlias(string alias, string typeUri) { - ErrorUtilities.VerifyNonZeroLength(alias, "alias"); - ErrorUtilities.VerifyNonZeroLength(typeUri, "typeUri"); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(alias)); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(typeUri)); this.aliasToTypeUriMap.Add(alias, typeUri); this.typeUriToAliasMap.Add(typeUri, alias); } @@ -67,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) { @@ -100,7 +103,7 @@ namespace DotNetOpenAuth.OpenId.Extensions { /// </summary> /// <param name="preferredTypeUriToAliases">A dictionary of type URI keys and alias values.</param> public void SetPreferredAliasesWhereNotSet(IDictionary<string, string> preferredTypeUriToAliases) { - ErrorUtilities.VerifyArgumentNotNull(preferredTypeUriToAliases, "preferredTypeUriToAliases"); + Contract.Requires<ArgumentNullException>(preferredTypeUriToAliases != null); foreach (var pair in preferredTypeUriToAliases) { if (this.typeUriToAliasMap.ContainsKey(pair.Key)) { @@ -125,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"); @@ -138,7 +142,7 @@ namespace DotNetOpenAuth.OpenId.Extensions { /// <param name="alias">The alias.</param> /// <returns>The Type URI for the given alias, or null if none for that alias exist.</returns> public string TryResolveAlias(string alias) { - ErrorUtilities.VerifyNonZeroLength(alias, "alias"); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(alias)); string typeUri = null; this.aliasToTypeUriMap.TryGetValue(alias, out typeUri); return typeUri; @@ -150,7 +154,7 @@ namespace DotNetOpenAuth.OpenId.Extensions { /// <param name="alias">The alias in question.</param> /// <returns>True if the alias has already been assigned. False otherwise.</returns> public bool IsAliasUsed(string alias) { - ErrorUtilities.VerifyNonZeroLength(alias, "alias"); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(alias)); return this.aliasToTypeUriMap.ContainsKey(alias); } @@ -162,7 +166,7 @@ namespace DotNetOpenAuth.OpenId.Extensions { /// <c>true</c> if the given type URI already has an alias assigned; <c>false</c> otherwise. /// </returns> public bool IsAliasAssignedTo(string typeUri) { - ErrorUtilities.VerifyArgumentNotNull(typeUri, "typeUri"); + Contract.Requires<ArgumentNullException>(typeUri != null); return this.typeUriToAliasMap.ContainsKey(typeUri); } @@ -172,9 +176,9 @@ namespace DotNetOpenAuth.OpenId.Extensions { /// <param name="typeUri">The type URI to assign a new alias to.</param> /// <returns>The newly generated alias.</returns> private string AssignNewAlias(string typeUri) { - ErrorUtilities.VerifyNonZeroLength(typeUri, "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/Extensions/AttributeExchange/AXUtilities.cs b/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AXUtilities.cs index a3f64ab..2b947f7 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AXUtilities.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AXUtilities.cs @@ -21,7 +21,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange { /// <param name="collection">The attribute request collection.</param> /// <param name="typeUri">The type URI of the required attribute.</param> public static void AddRequired(this ICollection<AttributeRequest> collection, string typeUri) { - ErrorUtilities.VerifyArgumentNotNull(collection, "collection"); + Contract.Requires<ArgumentNullException>(collection != null); collection.Add(new AttributeRequest(typeUri, true)); } @@ -31,7 +31,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange { /// <param name="collection">The attribute request collection.</param> /// <param name="typeUri">The type URI of the requested attribute.</param> public static void AddOptional(this ICollection<AttributeRequest> collection, string typeUri) { - ErrorUtilities.VerifyArgumentNotNull(collection, "collection"); + Contract.Requires<ArgumentNullException>(collection != null); collection.Add(new AttributeRequest(typeUri, false)); } @@ -43,8 +43,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange { /// <param name="typeUri">The type URI of the attribute.</param> /// <param name="values">The attribute values.</param> public static void Add(this ICollection<AttributeValues> collection, string typeUri, params string[] values) { - Contract.Requires(collection != null); - ErrorUtilities.VerifyArgumentNotNull(collection, "collection"); + Contract.Requires<ArgumentNullException>(collection != null); collection.Add(new AttributeValues(typeUri, values)); } @@ -54,8 +53,8 @@ namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange { /// <param name="fields">The dictionary to fill with serialized attributes.</param> /// <param name="attributes">The attributes.</param> internal static void SerializeAttributes(IDictionary<string, string> fields, IEnumerable<AttributeValues> attributes) { - ErrorUtilities.VerifyArgumentNotNull(fields, "fields"); - ErrorUtilities.VerifyArgumentNotNull(attributes, "attributes"); + Contract.Requires<ArgumentNullException>(fields != null); + Contract.Requires<ArgumentNullException>(attributes != null); AliasManager aliasManager = new AliasManager(); foreach (var att in attributes) { @@ -124,7 +123,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange { /// <param name="fields">The data included in the extension message.</param> /// <returns>The alias manager that provides lookup between aliases and type URIs.</returns> private static AliasManager ParseAliases(IDictionary<string, string> fields) { - ErrorUtilities.VerifyArgumentNotNull(fields, "fields"); + Contract.Requires<ArgumentNullException>(fields != null); AliasManager aliasManager = new AliasManager(); const string TypePrefix = "type."; diff --git a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AttributeRequest.cs b/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AttributeRequest.cs index e508233..358db9b 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AttributeRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AttributeRequest.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange { using System; using System.Diagnostics; + using System.Diagnostics.Contracts; using DotNetOpenAuth.Messaging; /// <summary> @@ -34,7 +35,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange { /// </summary> /// <param name="typeUri">The unique TypeURI for that describes the attribute being sought.</param> public AttributeRequest(string typeUri) { - ErrorUtilities.VerifyNonZeroLength(typeUri, "typeUri"); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(typeUri)); this.TypeUri = typeUri; } @@ -82,7 +83,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange { } set { - ErrorUtilities.VerifyArgumentInRange(value > 0, "value"); + Contract.Requires<ArgumentOutOfRangeException>(value > 0); this.count = value; } } @@ -97,8 +98,8 @@ namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange { /// the <see cref="FetchResponse"/> object. /// </returns> public AttributeValues Respond(params string[] values) { - ErrorUtilities.VerifyArgumentNotNull(values, "values"); - ErrorUtilities.VerifyArgument(values.Length <= this.Count, OpenIdStrings.AttributeTooManyValues, this.Count, this.TypeUri, values.Length); + Contract.Requires<ArgumentNullException>(values != null); + Contract.Requires<ArgumentException>(values.Length <= this.Count); return new AttributeValues(this.TypeUri, values); } diff --git a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AttributeValues.cs b/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AttributeValues.cs index e87e188..6466cda 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AttributeValues.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AttributeValues.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using DotNetOpenAuth.Messaging; /// <summary> @@ -22,7 +23,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange { /// <param name="typeUri">The TypeURI that uniquely identifies the attribute.</param> /// <param name="values">The values for the attribute.</param> public AttributeValues(string typeUri, params string[] values) { - ErrorUtilities.VerifyNonZeroLength(typeUri, "typeUri"); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(typeUri)); this.TypeUri = typeUri; this.Values = (IList<string>)values ?? EmptyList<string>.Instance; @@ -44,7 +45,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange { /// </summary> /// <param name="typeUri">The TypeURI of the attribute whose values are being provided.</param> internal AttributeValues(string typeUri) { - ErrorUtilities.VerifyNonZeroLength(typeUri, "typeUri"); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(typeUri)); this.TypeUri = typeUri; this.Values = new List<string>(1); diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ExtensionArgumentsManager.cs b/src/DotNetOpenAuth/OpenId/Extensions/ExtensionArgumentsManager.cs index e33be0f..0a78df1 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/ExtensionArgumentsManager.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/ExtensionArgumentsManager.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OpenId.Extensions { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.Globalization; using System.Text; using DotNetOpenAuth.Messaging; @@ -51,12 +52,19 @@ namespace DotNetOpenAuth.OpenId.Extensions { private ExtensionArgumentsManager() { } /// <summary> + /// Gets a value indicating whether the extensions are being read (as opposed to written). + /// </summary> + internal bool ReadMode { + get { return this.isReadMode; } + } + + /// <summary> /// Creates a <see cref="ExtensionArgumentsManager"/> instance to process incoming extensions. /// </summary> /// <param name="query">The parameters in the OpenID message.</param> /// <returns>The newly created instance of <see cref="ExtensionArgumentsManager"/>.</returns> public static ExtensionArgumentsManager CreateIncomingExtensions(IDictionary<string, string> query) { - ErrorUtilities.VerifyArgumentNotNull(query, "query"); + Contract.Requires<ArgumentNullException>(query != null); var mgr = new ExtensionArgumentsManager(); mgr.protocol = Protocol.Detect(query); mgr.isReadMode = true; @@ -120,6 +128,31 @@ namespace DotNetOpenAuth.OpenId.Extensions { } /// <summary> + /// Adds query parameters for OpenID extensions to the request directed + /// at the OpenID provider. + /// </summary> + /// <param name="extensionTypeUri">The extension type URI.</param> + /// <param name="arguments">The arguments for this extension to add to the message.</param> + public void AddExtensionArguments(string extensionTypeUri, IDictionary<string, string> arguments) { + Contract.Requires<InvalidOperationException>(!this.ReadMode); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(extensionTypeUri)); + Contract.Requires<ArgumentNullException>(arguments != null); + if (arguments.Count == 0) { + return; + } + + IDictionary<string, string> extensionArgs; + if (!this.extensions.TryGetValue(extensionTypeUri, out extensionArgs)) { + this.extensions.Add(extensionTypeUri, extensionArgs = new Dictionary<string, string>(arguments.Count)); + } + + ErrorUtilities.VerifyProtocol(extensionArgs.Count == 0, OpenIdStrings.ExtensionAlreadyAddedWithSameTypeURI, extensionTypeUri); + foreach (var pair in arguments) { + extensionArgs.Add(pair.Key, pair.Value); + } + } + + /// <summary> /// Gets the actual arguments to add to a querystring or other response, /// where type URI, alias, and actual key/values are all defined. /// </summary> @@ -128,10 +161,8 @@ namespace DotNetOpenAuth.OpenId.Extensions { /// This should be <c>true</c> for all but direct response messages. /// </param> /// <returns>A dictionary of key=value pairs to add to the message to carry the extension.</returns> - public IDictionary<string, string> GetArgumentsToSend(bool includeOpenIdPrefix) { - if (this.isReadMode) { - throw new InvalidOperationException(); - } + internal IDictionary<string, string> GetArgumentsToSend(bool includeOpenIdPrefix) { + Contract.Requires<InvalidOperationException>(!this.ReadMode); Dictionary<string, string> args = new Dictionary<string, string>(); foreach (var typeUriAndExtension in this.extensions) { string typeUri = typeUriAndExtension.Key; @@ -157,44 +188,15 @@ namespace DotNetOpenAuth.OpenId.Extensions { } /// <summary> - /// Adds query parameters for OpenID extensions to the request directed - /// at the OpenID provider. - /// </summary> - /// <param name="extensionTypeUri">The extension type URI.</param> - /// <param name="arguments">The arguments for this extension to add to the message.</param> - public void AddExtensionArguments(string extensionTypeUri, IDictionary<string, string> arguments) { - if (this.isReadMode) { - throw new InvalidOperationException(); - } - ErrorUtilities.VerifyNonZeroLength(extensionTypeUri, "extensionTypeUri"); - ErrorUtilities.VerifyArgumentNotNull(arguments, "arguments"); - if (arguments.Count == 0) { - return; - } - - IDictionary<string, string> extensionArgs; - if (!this.extensions.TryGetValue(extensionTypeUri, out extensionArgs)) { - this.extensions.Add(extensionTypeUri, extensionArgs = new Dictionary<string, string>(arguments.Count)); - } - - ErrorUtilities.VerifyProtocol(extensionArgs.Count == 0, OpenIdStrings.ExtensionAlreadyAddedWithSameTypeURI, extensionTypeUri); - foreach (var pair in arguments) { - extensionArgs.Add(pair.Key, pair.Value); - } - } - - /// <summary> /// Gets the fields carried by a given OpenId extension. /// </summary> /// <param name="extensionTypeUri">The type URI of the extension whose fields are being queried for.</param> /// <returns> /// The fields included in the given extension, or null if the extension is not present. /// </returns> - public IDictionary<string, string> GetExtensionArguments(string extensionTypeUri) { - ErrorUtilities.VerifyNonZeroLength(extensionTypeUri, "extensionTypeUri"); - if (!this.isReadMode) { - throw new InvalidOperationException(); - } + internal IDictionary<string, string> GetExtensionArguments(string extensionTypeUri) { + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(extensionTypeUri)); + Contract.Requires<InvalidOperationException>(this.ReadMode); IDictionary<string, string> extensionArgs; this.extensions.TryGetValue(extensionTypeUri, out extensionArgs); @@ -206,7 +208,7 @@ namespace DotNetOpenAuth.OpenId.Extensions { /// </summary> /// <param name="extensionTypeUri">The extension Type URI in question.</param> /// <returns><c>true</c> if this extension is present; <c>false</c> otherwise.</returns> - public bool ContainsExtension(string extensionTypeUri) { + internal bool ContainsExtension(string extensionTypeUri) { return this.extensions.ContainsKey(extensionTypeUri); } @@ -214,7 +216,7 @@ namespace DotNetOpenAuth.OpenId.Extensions { /// Gets the type URIs of all discovered extensions in the message. /// </summary> /// <returns>A sequence of the type URIs.</returns> - public IEnumerable<string> GetExtensionTypeUris() { + internal IEnumerable<string> GetExtensionTypeUris() { return this.extensions.Keys; } } diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ExtensionsInteropHelper.cs b/src/DotNetOpenAuth/OpenId/Extensions/ExtensionsInteropHelper.cs index 36358a7..cd7575e 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/ExtensionsInteropHelper.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/ExtensionsInteropHelper.cs @@ -42,8 +42,7 @@ namespace DotNetOpenAuth.OpenId.Extensions { /// </remarks> [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sreg", Justification = "Abbreviation")] public static void SpreadSregToAX(this RelyingParty.IAuthenticationRequest request, AXAttributeFormats attributeFormats) { - Contract.Requires(request != null); - ErrorUtilities.VerifyArgumentNotNull(request, "request"); + Contract.Requires<ArgumentNullException>(request != null); var req = (RelyingParty.AuthenticationRequest)request; var sreg = req.AppliedExtensions.OfType<ClaimsRequest>().SingleOrDefault(); @@ -99,8 +98,7 @@ namespace DotNetOpenAuth.OpenId.Extensions { /// Never <c>null</c>.</returns> [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sreg", Justification = "Abbreviation")] public static ClaimsResponse UnifyExtensionsAsSreg(this RelyingParty.IAuthenticationResponse response, bool allowUnsigned) { - Contract.Requires(response != null); - ErrorUtilities.VerifyArgumentNotNull(response, "response"); + Contract.Requires<ArgumentNullException>(response != null); var resp = (RelyingParty.IAuthenticationResponse)response; var sreg = allowUnsigned ? resp.GetUntrustedExtension<ClaimsResponse>() : resp.GetExtension<ClaimsResponse>(); @@ -140,8 +138,7 @@ namespace DotNetOpenAuth.OpenId.Extensions { /// or a fabricated one based on the Attribute Exchange extension if found, /// or <c>null</c> if no attribute extension request is found.</returns> internal static ClaimsRequest UnifyExtensionsAsSreg(this Provider.IHostProcessedRequest request) { - Contract.Requires(request != null); - ErrorUtilities.VerifyArgumentNotNull(request, "request"); + Contract.Requires<ArgumentNullException>(request != null); var req = (Provider.AuthenticationRequest)request; var sreg = req.GetExtension<ClaimsRequest>(); @@ -258,8 +255,8 @@ namespace DotNetOpenAuth.OpenId.Extensions { /// <param name="typeUri">The type URI of the attribute in axschema.org format.</param> /// <returns>The demand level for the attribute.</returns> private static DemandLevel GetDemandLevelFor(FetchRequest ax, string typeUri) { - Contract.Requires(ax != null); - Contract.Requires(!String.IsNullOrEmpty(typeUri)); + Contract.Requires<ArgumentNullException>(ax != null); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(typeUri)); foreach (AXAttributeFormats format in ForEachFormat(AXAttributeFormats.All)) { string typeUriInFormat = TransformAXFormat(typeUri, format); @@ -278,7 +275,7 @@ namespace DotNetOpenAuth.OpenId.Extensions { /// <param name="attributeFormat">The attribute formats the RP will try if this discovery fails.</param> /// <returns>The AX format(s) to use based on the Provider's advertised AX support.</returns> private static bool TryDetectOPAttributeFormat(RelyingParty.IAuthenticationRequest request, out AXAttributeFormats attributeFormat) { - Contract.Requires(request != null); + Contract.Requires<ArgumentNullException>(request != null); var provider = (RelyingParty.ServiceEndpoint)request.Provider; attributeFormat = DetectAXFormat(provider.ProviderDescription.Capabilities); return attributeFormat != AXAttributeFormats.None; @@ -290,7 +287,7 @@ namespace DotNetOpenAuth.OpenId.Extensions { /// <param name="typeURIs">The type URIs to scan for recognized formats.</param> /// <returns>The first AX type URI format recognized in the list.</returns> private static AXAttributeFormats DetectAXFormat(IEnumerable<string> typeURIs) { - Contract.Requires(typeURIs != null); + Contract.Requires<ArgumentNullException>(typeURIs != null); if (typeURIs.Any(uri => uri.StartsWith("http://axschema.org/", StringComparison.Ordinal))) { return AXAttributeFormats.AXSchemaOrg; @@ -314,7 +311,7 @@ namespace DotNetOpenAuth.OpenId.Extensions { /// <param name="targetFormat">The target format. Only one flag should be set.</param> /// <returns>The AX attribute type URI in the target format.</returns> private static string TransformAXFormat(string axSchemaOrgFormatTypeUri, AXAttributeFormats targetFormat) { - Contract.Requires(!String.IsNullOrEmpty(axSchemaOrgFormatTypeUri)); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(axSchemaOrgFormatTypeUri)); switch (targetFormat) { case AXAttributeFormats.AXSchemaOrg: @@ -355,8 +352,8 @@ namespace DotNetOpenAuth.OpenId.Extensions { /// <param name="axSchemaOrgFormatAttribute">The attribute in axschema.org format.</param> /// <param name="demandLevel">The demand level.</param> private static void FetchAttribute(FetchRequest ax, AXAttributeFormats format, string axSchemaOrgFormatAttribute, DemandLevel demandLevel) { - Contract.Requires(ax != null); - Contract.Requires(!String.IsNullOrEmpty(axSchemaOrgFormatAttribute)); + Contract.Requires<ArgumentNullException>(ax != null); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(axSchemaOrgFormatAttribute)); string typeUri = TransformAXFormat(axSchemaOrgFormatAttribute, format); if (!ax.Attributes.Contains(typeUri)) { diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PapeUtilities.cs b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PapeUtilities.cs index 0f365ac..eeaea31 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PapeUtilities.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PapeUtilities.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.Globalization; using System.Linq; using System.Text; @@ -45,7 +46,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy { /// <returns>The concatenated string of elements.</returns> /// <exception cref="FormatException">Thrown if any element in the sequence includes a space.</exception> internal static string ConcatenateListOfElements(IEnumerable<string> values) { - ErrorUtilities.VerifyArgumentNotNull(values, "values"); + Contract.Requires<ArgumentNullException>(values != null); StringBuilder valuesList = new StringBuilder(); foreach (string value in values.Distinct()) { diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs index b476cf7..246ec07 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy { using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; using System.Globalization; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.Messages; @@ -82,12 +83,10 @@ namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy { } set { + Contract.Requires<ArgumentException>(!value.HasValue || value.Value.Kind != DateTimeKind.Unspecified, OpenIdStrings.UnspecifiedDateTimeKindNotAllowed); + // Make sure that whatever is set here, it becomes UTC time. if (value.HasValue) { - if (value.Value.Kind == DateTimeKind.Unspecified) { - throw new ArgumentException(OpenIdStrings.UnspecifiedDateTimeKindNotAllowed, "value"); - } - // Convert to UTC and cut to the second, since the protocol only allows for // that level of precision. this.authenticationTimeUtc = OpenIdUtilities.CutToSecond(value.Value.ToUniversalTimeSafe()); diff --git a/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs b/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs index 10622bf..f775492 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs @@ -9,6 +9,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration { using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; using System.Globalization; using System.Text; using DotNetOpenAuth.Messaging; @@ -58,7 +59,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration { /// <param name="typeUri">The type URI this extension was recognized by in the OpenID message.</param> internal ClaimsRequest(string typeUri) : this() { - ErrorUtilities.VerifyNonZeroLength(typeUri, "typeUri"); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(typeUri)); this.typeUriDeserializedFrom = typeUri; } diff --git a/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs b/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs index a58c754..7db6d45 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/ClaimsResponse.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration { using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; using System.Globalization; using System.Net.Mail; using System.Text; @@ -69,7 +70,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration { /// </param> internal ClaimsResponse(string typeUriToUse) : base(new Version(1, 0), typeUriToUse, EmptyList<string>.Instance) { - ErrorUtilities.VerifyNonZeroLength(typeUriToUse, "typeUriToUse"); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(typeUriToUse)); } /// <summary> @@ -121,11 +122,8 @@ namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration { } set { + ErrorUtilities.VerifyArgument(value == null || birthDateValidator.IsMatch(value), OpenIdStrings.SregInvalidBirthdate); if (value != null) { - if (!birthDateValidator.IsMatch(value)) { - throw new ArgumentException(OpenIdStrings.SregInvalidBirthdate, "value"); - } - // Update the BirthDate property, if possible. // Don't use property accessor for peer property to avoid infinite loop between the two proeprty accessors. // Some valid sreg dob values like "2000-00-00" will not work as a DateTime struct, @@ -316,7 +314,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration { sreg[Constants.language] = this.Language; sreg[Constants.timezone] = this.TimeZone; - return MessagingUtilities.CreateJsonObject(sreg); + return MessagingUtilities.CreateJsonObject(sreg, false); } #endregion diff --git a/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs b/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs index bee675d..61e6f85 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs @@ -71,6 +71,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.UI { /// <remarks> /// The user's preferred languages as a [BCP 47] language priority list, represented as a comma-separated list of BCP 47 basic language ranges in descending priority order. For instance, the value "fr-CA,fr-FR,en-CA" represents the preference for French spoken in Canada, French spoken in France, followed by English spoken in Canada. /// </remarks> + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "By design")] [MessagePart("lang", AllowEmpty = false)] public CultureInfo[] LanguagePreference { get; set; } diff --git a/src/DotNetOpenAuth/OpenId/Extensions/UI/UIUtilities.cs b/src/DotNetOpenAuth/OpenId/Extensions/UI/UIUtilities.cs index 088404b..cee6882 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/UI/UIUtilities.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/UI/UIUtilities.cs @@ -18,7 +18,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.UI { /// <summary> /// The required width of the popup window the relying party creates for the provider. /// </summary> - public const int PopupWidth = 450; + public const int PopupWidth = 500; // UI extension calls for 450px, but Yahoo needs 500px /// <summary> /// The required height of the popup window the relying party creates for the provider. @@ -34,9 +34,9 @@ namespace DotNetOpenAuth.OpenId.Extensions.UI { /// <param name="windowName">The name to assign to the popup window.</param> /// <returns>A string starting with 'window.open' and forming just that one method call.</returns> internal static string GetWindowPopupScript(OpenIdRelyingParty relyingParty, IAuthenticationRequest request, string windowName) { - Contract.Requires(relyingParty != null); - Contract.Requires(request != null); - Contract.Requires(!string.IsNullOrEmpty(windowName)); + Contract.Requires<ArgumentNullException>(relyingParty != null); + Contract.Requires<ArgumentNullException>(request != null); + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(windowName)); Uri popupUrl = request.RedirectingResponse.GetDirectUriRequest(relyingParty.Channel); diff --git a/src/DotNetOpenAuth/OpenId/HmacShaAssociation.cs b/src/DotNetOpenAuth/OpenId/HmacShaAssociation.cs index 4c31100..e0317db 100644 --- a/src/DotNetOpenAuth/OpenId/HmacShaAssociation.cs +++ b/src/DotNetOpenAuth/OpenId/HmacShaAssociation.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.OpenId { using System; using System.Collections.Generic; using System.Diagnostics; + using System.Diagnostics.Contracts; using System.Globalization; using System.Linq; using System.Security.Cryptography; @@ -19,6 +20,7 @@ namespace DotNetOpenAuth.OpenId { /// <summary> /// An association that uses the HMAC-SHA family of algorithms for message signing. /// </summary> + [ContractVerification(true)] internal class HmacShaAssociation : Association { /// <summary> /// The default lifetime of a shared association when no lifetime is given @@ -66,9 +68,11 @@ namespace DotNetOpenAuth.OpenId { /// <param name="totalLifeLength">The time duration the association will be good for.</param> private HmacShaAssociation(HmacSha typeIdentity, string handle, byte[] secret, TimeSpan totalLifeLength) : base(handle, secret, totalLifeLength, DateTime.UtcNow) { - ErrorUtilities.VerifyArgumentNotNull(typeIdentity, "typeIdentity"); - ErrorUtilities.VerifyNonZeroLength(handle, "handle"); - ErrorUtilities.VerifyArgumentNotNull(secret, "secret"); + Contract.Requires<ArgumentNullException>(typeIdentity != null); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(handle)); + Contract.Requires<ArgumentNullException>(secret != null); + Contract.Requires<ArgumentOutOfRangeException>(totalLifeLength > TimeSpan.Zero); + Contract.Ensures(this.TotalLifeLength == totalLifeLength); ErrorUtilities.VerifyProtocol(secret.Length == typeIdentity.SecretLength, OpenIdStrings.AssociationSecretAndTypeLengthMismatch, secret.Length, typeIdentity.GetAssociationType(Protocol.Default)); this.typeIdentity = typeIdentity; @@ -94,9 +98,10 @@ namespace DotNetOpenAuth.OpenId { /// <param name="totalLifeLength">How long the association will be good for.</param> /// <returns>The newly created association.</returns> public static HmacShaAssociation Create(Protocol protocol, string associationType, string handle, byte[] secret, TimeSpan totalLifeLength) { - ErrorUtilities.VerifyArgumentNotNull(protocol, "protocol"); - ErrorUtilities.VerifyNonZeroLength(associationType, "associationType"); - ErrorUtilities.VerifyArgumentNotNull(secret, "secret"); + Contract.Requires<ArgumentNullException>(protocol != null); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(associationType)); + Contract.Requires<ArgumentNullException>(secret != null); + Contract.Ensures(Contract.Result<HmacShaAssociation>() != null); HmacSha match = hmacShaAssociationTypes.FirstOrDefault(sha => String.Equals(sha.GetAssociationType(protocol), associationType, StringComparison.Ordinal)); ErrorUtilities.VerifyProtocol(match != null, OpenIdStrings.NoAssociationTypeFoundByName, associationType); @@ -111,8 +116,9 @@ namespace DotNetOpenAuth.OpenId { /// <param name="totalLifeLength">Total lifetime.</param> /// <returns>The newly created association.</returns> public static HmacShaAssociation Create(string handle, byte[] secret, TimeSpan totalLifeLength) { - ErrorUtilities.VerifyNonZeroLength(handle, "handle"); - ErrorUtilities.VerifyArgumentNotNull(secret, "secret"); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(handle)); + Contract.Requires<ArgumentNullException>(secret != null); + Contract.Ensures(Contract.Result<HmacShaAssociation>() != null); HmacSha shaType = hmacShaAssociationTypes.FirstOrDefault(sha => sha.SecretLength == secret.Length); ErrorUtilities.VerifyProtocol(shaType != null, OpenIdStrings.NoAssociationTypeFoundByLength, secret.Length); @@ -145,9 +151,10 @@ namespace DotNetOpenAuth.OpenId { /// The new association is NOT automatically put into an association store. This must be done by the caller. /// </remarks> internal static HmacShaAssociation Create(Protocol protocol, string associationType, AssociationRelyingPartyType associationUse, ProviderSecuritySettings securitySettings) { - ErrorUtilities.VerifyArgumentNotNull(protocol, "protocol"); - ErrorUtilities.VerifyNonZeroLength(associationType, "associationType"); - ErrorUtilities.VerifyArgumentNotNull(securitySettings, "securitySettings"); + Contract.Requires<ArgumentNullException>(protocol != null); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(associationType)); + Contract.Requires<ArgumentNullException>(securitySettings != null); + Contract.Ensures(Contract.Result<HmacShaAssociation>() != null); int secretLength = GetSecretLength(protocol, associationType); @@ -173,6 +180,9 @@ namespace DotNetOpenAuth.OpenId { lifetime = DumbSecretLifetime; } + Contract.Assert(protocol != null); // All the way up to the method call, the condition holds, yet we get a Requires failure next + Contract.Assert(secret != null); + Contract.Assert(!String.IsNullOrEmpty(associationType)); return Create(protocol, associationType, handle, secret, lifetime); } @@ -191,8 +201,8 @@ namespace DotNetOpenAuth.OpenId { /// True if a qualifying association could be found; false otherwise. /// </returns> internal static bool TryFindBestAssociation(Protocol protocol, bool highSecurityIsBetter, SecuritySettings securityRequirements, bool requireMatchingDHSessionType, out string associationType, out string sessionType) { - ErrorUtilities.VerifyArgumentNotNull(protocol, "protocol"); - ErrorUtilities.VerifyArgumentNotNull(securityRequirements, "securityRequirements"); + Contract.Requires<ArgumentNullException>(protocol != null); + Contract.Requires<ArgumentNullException>(securityRequirements != null); associationType = null; sessionType = null; @@ -232,9 +242,9 @@ namespace DotNetOpenAuth.OpenId { /// <c>true</c> if the named association and session types are compatible; otherwise, <c>false</c>. /// </returns> internal static bool IsDHSessionCompatible(Protocol protocol, string associationType, string sessionType) { - ErrorUtilities.VerifyArgumentNotNull(protocol, "protocol"); - ErrorUtilities.VerifyNonZeroLength(associationType, "associationType"); - ErrorUtilities.VerifyArgumentNotNull(sessionType, "sessionType"); + Contract.Requires<ArgumentNullException>(protocol != null); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(associationType)); + Contract.Requires<ArgumentNullException>(sessionType != null); // All association types can work when no DH session is used at all. if (string.Equals(sessionType, protocol.Args.SessionType.NoEncryption, StringComparison.Ordinal)) { @@ -254,6 +264,7 @@ namespace DotNetOpenAuth.OpenId { /// <returns> /// The value that should be used for the openid.assoc_type parameter. /// </returns> + [Pure] internal override string GetAssociationType(Protocol protocol) { return this.typeIdentity.GetAssociationType(protocol); } @@ -264,8 +275,11 @@ namespace DotNetOpenAuth.OpenId { /// <returns> /// The hash algorithm used for message signing. /// </returns> + [Pure] protected override HashAlgorithm CreateHasher() { - return this.typeIdentity.CreateHasher(SecretKey); + var result = this.typeIdentity.CreateHasher(SecretKey); + Contract.Assume(result != null); + return result; } /// <summary> diff --git a/src/DotNetOpenAuth/OpenId/IAssociationStore.cs b/src/DotNetOpenAuth/OpenId/IAssociationStore.cs index 2376b0d..71d8652 100644 --- a/src/DotNetOpenAuth/OpenId/IAssociationStore.cs +++ b/src/DotNetOpenAuth/OpenId/IAssociationStore.cs @@ -5,6 +5,9 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.OpenId { + using System; + using System.Diagnostics.Contracts; + /// <summary> /// An enumeration that can specify how a given <see cref="Association"/> is used. /// </summary> @@ -33,6 +36,7 @@ namespace DotNetOpenAuth.OpenId { /// <see cref="System.Uri"/> for consumers (to distinguish associations across servers) or /// <see cref="AssociationRelyingPartyType"/> for providers (to distinguish dumb and smart client associations). /// </typeparam> + ////[ContractClass(typeof(IAssociationStoreContract<>))] public interface IAssociationStore<TKey> { /// <summary> /// Saves an <see cref="Association"/> for later recall. @@ -92,4 +96,99 @@ namespace DotNetOpenAuth.OpenId { /// </remarks> void ClearExpiredAssociations(); } + + // For some odd reason, having this next class causes our test project to fail to build with this error: + // Error 42 Method 'StoreAssociation' in type 'DotNetOpenAuth.OpenId.IAssociationStoreContract_Accessor`1' from assembly 'DotNetOpenAuth_Accessor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation. DotNetOpenAuth.Test + /////// <summary> + /////// Code Contract for the <see cref="IAssociationStore<TKey>"/> class. + /////// </summary> + /////// <typeparam name="TKey">The type of the key.</typeparam> + ////[ContractClassFor(typeof(IAssociationStore<>))] + ////internal abstract class IAssociationStoreContract<TKey> : IAssociationStore<TKey> { + //// #region IAssociationStore<TKey> 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> + //// void IAssociationStore<TKey>.StoreAssociation(TKey distinguishingFactor, Association association) { + //// Contract.Requires<ArgumentNullException>(distinguishingFactor != null); + //// Contract.Requires<ArgumentNullException>(association != null); + //// throw new NotImplementedException(); + //// } + + //// /// <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> + //// Association IAssociationStore<TKey>.GetAssociation(TKey distinguishingFactor, SecuritySettings securityRequirements) { + //// Contract.Requires<ArgumentNullException>(distinguishingFactor != null); + //// Contract.Requires<ArgumentNullException>(securityRequirements != null); + //// throw new NotImplementedException(); + //// } + + //// /// <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> + //// Association IAssociationStore<TKey>.GetAssociation(TKey distinguishingFactor, string handle) { + //// Contract.Requires<ArgumentNullException>(distinguishingFactor != null); + //// Contract.Requires(!String.IsNullOrEmpty(handle)); + //// throw new NotImplementedException(); + //// } + + //// /// <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> + //// bool IAssociationStore<TKey>.RemoveAssociation(TKey distinguishingFactor, string handle) { + //// Contract.Requires<ArgumentNullException>(distinguishingFactor != null); + //// Contract.Requires(!String.IsNullOrEmpty(handle)); + //// throw new NotImplementedException(); + //// } + + //// /// <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> + //// void IAssociationStore<TKey>.ClearExpiredAssociations() { + //// throw new NotImplementedException(); + //// } + + //// #endregion + ////} } diff --git a/src/DotNetOpenAuth/OpenId/Identifier.cs b/src/DotNetOpenAuth/OpenId/Identifier.cs index 6e71b0a..e32251b 100644 --- a/src/DotNetOpenAuth/OpenId/Identifier.cs +++ b/src/DotNetOpenAuth/OpenId/Identifier.cs @@ -24,15 +24,21 @@ namespace DotNetOpenAuth.OpenId { /// <summary> /// Initializes a new instance of the <see cref="Identifier"/> class. /// </summary> - /// <param name="isDiscoverySecureEndToEnd"> - /// Whether the derived class is prepared to guarantee end-to-end discovery - /// and initial redirect for authentication is performed using SSL. - /// </param> - protected Identifier(bool isDiscoverySecureEndToEnd) { + /// <param name="originalString">The original string before any normalization.</param> + /// <param name="isDiscoverySecureEndToEnd">Whether the derived class is prepared to guarantee end-to-end discovery + /// and initial redirect for authentication is performed using SSL.</param> + [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "string", Justification = "Emphasis on string instead of the strong-typed Identifier.")] + protected Identifier(string originalString, bool isDiscoverySecureEndToEnd) { + this.OriginalString = originalString; this.IsDiscoverySecureEndToEnd = isDiscoverySecureEndToEnd; } /// <summary> + /// Gets the original string that was normalized to create this Identifier. + /// </summary> + public string OriginalString { get; private set; } + + /// <summary> /// Gets or sets a value indicating whether <see cref="Identifier"/> instances are considered equal /// based solely on their string reprsentations. /// </summary> @@ -61,7 +67,7 @@ namespace DotNetOpenAuth.OpenId { [SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "Our named alternate is Parse.")] [DebuggerStepThrough] public static implicit operator Identifier(string identifier) { - Contract.Requires(identifier == null || identifier.Length > 0); + Contract.Requires<ArgumentException>(identifier == null || identifier.Length > 0); if (identifier == null) { return null; } @@ -106,8 +112,7 @@ namespace DotNetOpenAuth.OpenId { /// <returns>An <see cref="Identifier"/> instance for the given value.</returns> [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Some of these identifiers are not properly formatted to be Uris at this stage.")] public static Identifier Parse(string identifier) { - Contract.Requires((identifier != null && identifier.Length > 0) || !string.IsNullOrEmpty(identifier)); - ErrorUtilities.VerifyArgumentNotNull(identifier, "identifier"); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(identifier)); if (XriIdentifier.IsValidXri(identifier)) { return new XriIdentifier(identifier); } else { @@ -147,7 +152,7 @@ namespace DotNetOpenAuth.OpenId { /// </returns> [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Some of these identifiers are not properly formatted to be Uris at this stage.")] public static bool IsValid(string identifier) { - Contract.Requires(!string.IsNullOrEmpty(identifier)); + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(identifier)); return XriIdentifier.IsValidXri(identifier) || UriIdentifier.IsValidUri(identifier); } @@ -208,6 +213,7 @@ namespace DotNetOpenAuth.OpenId { /// <returns> /// An initialized structure containing the discovered provider endpoint information. /// </returns> + [Pure] internal abstract IEnumerable<ServiceEndpoint> Discover(IDirectWebRequestHandler requestHandler); /// <summary> @@ -217,6 +223,7 @@ namespace DotNetOpenAuth.OpenId { /// </summary> /// <returns>A new <see cref="Identifier"/> instance if there was a /// fragment to remove, otherwise this same instance..</returns> + [Pure] internal abstract Identifier TrimFragment(); /// <summary> diff --git a/src/DotNetOpenAuth/OpenId/IdentifierContract.cs b/src/DotNetOpenAuth/OpenId/IdentifierContract.cs index 1758f06..498ae45 100644 --- a/src/DotNetOpenAuth/OpenId/IdentifierContract.cs +++ b/src/DotNetOpenAuth/OpenId/IdentifierContract.cs @@ -20,7 +20,7 @@ namespace DotNetOpenAuth.OpenId { /// Prevents a default instance of the IdentifierContract class from being created. /// </summary> private IdentifierContract() - : base(false) { + : base(null, false) { } /// <summary> @@ -31,7 +31,7 @@ namespace DotNetOpenAuth.OpenId { /// An initialized structure containing the discovered provider endpoint information. /// </returns> internal override IEnumerable<ServiceEndpoint> Discover(IDirectWebRequestHandler requestHandler) { - Contract.Requires(requestHandler != null); + Contract.Requires<ArgumentNullException>(requestHandler != null); Contract.Ensures(Contract.Result<IEnumerable<ServiceEndpoint>>() != null); throw new NotImplementedException(); } diff --git a/src/DotNetOpenAuth/OpenId/Interop/AuthenticationResponseShim.cs b/src/DotNetOpenAuth/OpenId/Interop/AuthenticationResponseShim.cs index a5926d9..6319c02 100644 --- a/src/DotNetOpenAuth/OpenId/Interop/AuthenticationResponseShim.cs +++ b/src/DotNetOpenAuth/OpenId/Interop/AuthenticationResponseShim.cs @@ -30,8 +30,7 @@ namespace DotNetOpenAuth.OpenId.Interop { /// </summary> /// <param name="response">The response.</param> internal AuthenticationResponseShim(IAuthenticationResponse response) { - Contract.Requires(response != null); - ErrorUtilities.VerifyArgumentNotNull(response, "response"); + Contract.Requires<ArgumentNullException>(response != null); this.response = response; var claimsResponse = this.response.GetExtension<ClaimsResponse>(); diff --git a/src/DotNetOpenAuth/OpenId/Interop/ClaimsResponseShim.cs b/src/DotNetOpenAuth/OpenId/Interop/ClaimsResponseShim.cs index ef5e79d..689777b 100644 --- a/src/DotNetOpenAuth/OpenId/Interop/ClaimsResponseShim.cs +++ b/src/DotNetOpenAuth/OpenId/Interop/ClaimsResponseShim.cs @@ -31,8 +31,7 @@ namespace DotNetOpenAuth.OpenId.Interop { /// <param name="response">The Simple Registration response to wrap.</param> internal ClaimsResponseShim(ClaimsResponse response) { - Contract.Requires(response != null); - ErrorUtilities.VerifyArgumentNotNull(response, "response"); + Contract.Requires<ArgumentNullException>(response != null); this.response = response; } diff --git a/src/DotNetOpenAuth/OpenId/Messages/AssociateDiffieHellmanResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/AssociateDiffieHellmanResponse.cs index 99e413d..ccfb1b8 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/AssociateDiffieHellmanResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/AssociateDiffieHellmanResponse.cs @@ -6,6 +6,7 @@ namespace DotNetOpenAuth.OpenId.Messages { using System; + using System.Diagnostics.Contracts; using System.Security.Cryptography; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Reflection; @@ -51,7 +52,6 @@ namespace DotNetOpenAuth.OpenId.Messages { /// The resulting association is <i>not</i> added to the association store and must be done by the caller. /// </remarks> protected override Association CreateAssociationAtRelyingParty(AssociateRequest request) { - ErrorUtilities.VerifyArgumentNotNull(request, "request"); var diffieHellmanRequest = request as AssociateDiffieHellmanRequest; ErrorUtilities.VerifyArgument(diffieHellmanRequest != null, "request"); @@ -73,10 +73,8 @@ namespace DotNetOpenAuth.OpenId.Messages { /// but the resulting association is <i>not</i> added to the association store and must be done by the caller. /// </remarks> protected override Association CreateAssociationAtProvider(AssociateRequest request, ProviderSecuritySettings securitySettings) { - ErrorUtilities.VerifyArgumentNotNull(request, "request"); var diffieHellmanRequest = request as AssociateDiffieHellmanRequest; - ErrorUtilities.VerifyArgument(diffieHellmanRequest != null, "request"); - ErrorUtilities.VerifyArgumentNotNull(securitySettings, "securitySettings"); + ErrorUtilities.VerifyInternal(diffieHellmanRequest != null, "Expected a DH request type."); this.SessionType = this.SessionType ?? request.SessionType; diff --git a/src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs b/src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs index 6a5c0a8..5215022 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.OpenId.Messages { using System; using System.Collections.Generic; using System.Diagnostics; + using System.Diagnostics.Contracts; using System.Linq; using System.Text; using DotNetOpenAuth.Messaging; @@ -76,8 +77,8 @@ namespace DotNetOpenAuth.OpenId.Messages { /// and the provider OpenID version. /// </returns> internal static AssociateRequest Create(SecuritySettings securityRequirements, ProviderEndpointDescription provider) { - ErrorUtilities.VerifyArgumentNotNull(securityRequirements, "securityRequirements"); - ErrorUtilities.VerifyArgumentNotNull(provider, "provider"); + Contract.Requires<ArgumentNullException>(securityRequirements != null); + Contract.Requires<ArgumentNullException>(provider != null); // Apply our knowledge of the endpoint's transport, OpenID version, and // security requirements to decide the best association. @@ -106,10 +107,10 @@ namespace DotNetOpenAuth.OpenId.Messages { /// and the provider OpenID version. /// </returns> internal static AssociateRequest Create(SecuritySettings securityRequirements, ProviderEndpointDescription provider, string associationType, string sessionType) { - ErrorUtilities.VerifyArgumentNotNull(securityRequirements, "securityRequirements"); - ErrorUtilities.VerifyArgumentNotNull(provider, "provider"); - ErrorUtilities.VerifyNonZeroLength(associationType, "associationType"); - ErrorUtilities.VerifyArgumentNotNull(sessionType, "sessionType"); + Contract.Requires<ArgumentNullException>(securityRequirements != null); + Contract.Requires<ArgumentNullException>(provider != null); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(associationType)); + Contract.Requires<ArgumentNullException>(sessionType != null); bool unencryptedAllowed = provider.Endpoint.IsTransportSecure(); if (unencryptedAllowed) { @@ -140,8 +141,8 @@ namespace DotNetOpenAuth.OpenId.Messages { /// Failed association response messages will derive from <see cref="AssociateUnsuccessfulResponse"/>.</para> /// </remarks> internal IProtocolMessage CreateResponse(IAssociationStore<AssociationRelyingPartyType> associationStore, ProviderSecuritySettings securitySettings) { - ErrorUtilities.VerifyArgumentNotNull(associationStore, "associationStore"); - ErrorUtilities.VerifyArgumentNotNull(securitySettings, "securitySettings"); + Contract.Requires<ArgumentNullException>(associationStore != null); + Contract.Requires<ArgumentNullException>(securitySettings != null); IProtocolMessage response; if (securitySettings.IsAssociationInPermittedRange(Protocol, this.AssociationType) && @@ -184,7 +185,7 @@ namespace DotNetOpenAuth.OpenId.Messages { /// <param name="securitySettings">The security settings that apply to this Provider.</param> /// <returns>The response to send to the Relying Party.</returns> private AssociateUnsuccessfulResponse CreateUnsuccessfulResponse(ProviderSecuritySettings securitySettings) { - ErrorUtilities.VerifyArgumentNotNull(securitySettings, "securitySettings"); + Contract.Requires<ArgumentNullException>(securitySettings != null); var unsuccessfulResponse = new AssociateUnsuccessfulResponse(this.Version, this); diff --git a/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponse.cs index d2ca70a..137cd60 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponse.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.OpenId.Messages { using System; using System.Collections.Generic; using System.Diagnostics; + using System.Diagnostics.Contracts; using System.Linq; using System.Text; using DotNetOpenAuth.Messaging; @@ -20,6 +21,7 @@ namespace DotNetOpenAuth.OpenId.Messages { /// Association response messages are described in OpenID 2.0 section 8.2. This type covers section 8.2.1. /// </remarks> [DebuggerDisplay("OpenID {Version} associate response {AssociationHandle} {AssociationType} {SessionType}")] + [ContractClass(typeof(AssociateSuccessfulResponseContract))] internal abstract class AssociateSuccessfulResponse : DirectResponseBase { /// <summary> /// A flag indicating whether an association has already been created. @@ -106,7 +108,7 @@ namespace DotNetOpenAuth.OpenId.Messages { /// quite different operations in either scenario.</para> /// </remarks> internal Association CreateAssociation(AssociateRequest request, ProviderSecuritySettings securitySettings) { - ErrorUtilities.VerifyArgumentNotNull(request, "request"); + Contract.Requires<ArgumentNullException>(request != null); ErrorUtilities.VerifyInternal(!this.associationCreated, "The association has already been created."); Association association; diff --git a/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponseContract.cs b/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponseContract.cs new file mode 100644 index 0000000..dd37da6 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponseContract.cs @@ -0,0 +1,29 @@ +// <auto-generated /> + +namespace DotNetOpenAuth.OpenId.Messages { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Provider; + + [ContractClassFor(typeof(AssociateSuccessfulResponse))] + internal abstract class AssociateSuccessfulResponseContract : AssociateSuccessfulResponse { + protected AssociateSuccessfulResponseContract() : base(null, null) { + } + + protected override Association CreateAssociationAtProvider(AssociateRequest request, ProviderSecuritySettings securitySettings) { + Contract.Requires<ArgumentNullException>(request != null); + Contract.Requires<ArgumentNullException>(securitySettings != null); + throw new NotImplementedException(); + } + + protected override Association CreateAssociationAtRelyingParty(AssociateRequest request) { + Contract.Requires<ArgumentNullException>(request != null); + throw new NotImplementedException(); + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationRequest.cs index a10f073..5306c54 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationRequest.cs @@ -41,8 +41,7 @@ namespace DotNetOpenAuth.OpenId.Messages { /// <param name="channel">The channel. This is used only within the constructor and is not stored in a field.</param> internal CheckAuthenticationRequest(IndirectSignedResponse message, Channel channel) : base(message.Version, message.ProviderEndpoint, GetProtocolConstant(message.Version, p => p.Args.Mode.check_authentication), MessageTransport.Direct) { - Contract.Requires(channel != null); - ErrorUtilities.VerifyArgumentNotNull(channel, "channel"); + Contract.Requires<ArgumentNullException>(channel != null); // Copy all message parts from the id_res message into this one, // except for the openid.mode parameter. diff --git a/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationResponse.cs index c34d2b4..61825e8 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationResponse.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; @@ -36,7 +37,7 @@ namespace DotNetOpenAuth.OpenId.Messages { /// <param name="provider">The OpenID Provider that is preparing to send this response.</param> internal CheckAuthenticationResponse(CheckAuthenticationRequest request, OpenIdProvider provider) : base(request.Version, request) { - ErrorUtilities.VerifyArgumentNotNull(provider, "provider"); + Contract.Requires<ArgumentNullException>(provider != null); // The channel's binding elements have already set the request's IsValid property // appropriately. We just copy it into the response message. diff --git a/src/DotNetOpenAuth/OpenId/Messages/DirectResponseBase.cs b/src/DotNetOpenAuth/OpenId/Messages/DirectResponseBase.cs index dd9c927..e7619bc 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/DirectResponseBase.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/DirectResponseBase.cs @@ -9,6 +9,7 @@ namespace DotNetOpenAuth.OpenId.Messages { using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; using DotNetOpenAuth.Messaging; /// <summary> @@ -53,7 +54,7 @@ namespace DotNetOpenAuth.OpenId.Messages { /// <param name="responseVersion">The OpenID version of the response message.</param> /// <param name="originatingRequest">The originating request. May be null in case the request is unrecognizable and this is an error response.</param> protected DirectResponseBase(Version responseVersion, IDirectedProtocolMessage originatingRequest) { - ErrorUtilities.VerifyArgumentNotNull(responseVersion, "responseVersion"); + Contract.Requires<ArgumentNullException>(responseVersion != null); this.Version = responseVersion; this.originatingRequest = originatingRequest; 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/Messages/IndirectResponseBase.cs b/src/DotNetOpenAuth/OpenId/Messages/IndirectResponseBase.cs index 1147790..d53b9d0 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/IndirectResponseBase.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/IndirectResponseBase.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; @@ -23,7 +24,7 @@ namespace DotNetOpenAuth.OpenId.Messages { /// <param name="mode">The value of the openid.mode parameter.</param> protected IndirectResponseBase(SignedResponseRequest request, string mode) : base(GetVersion(request), GetReturnTo(request), mode, MessageTransport.Indirect) { - ErrorUtilities.VerifyArgumentNotNull(request, "request"); + Contract.Requires<ArgumentNullException>(request != null); this.OriginatingRequest = request; } @@ -56,7 +57,7 @@ namespace DotNetOpenAuth.OpenId.Messages { /// instead of a <see cref="NullReferenceException"/>. /// </remarks> internal static Version GetVersion(IProtocolMessage message) { - ErrorUtilities.VerifyArgumentNotNull(message, "message"); + Contract.Requires<ArgumentNullException>(message != null); return message.Version; } @@ -70,7 +71,7 @@ namespace DotNetOpenAuth.OpenId.Messages { /// instead of a <see cref="NullReferenceException"/>. /// </remarks> private static Uri GetReturnTo(SignedResponseRequest message) { - ErrorUtilities.VerifyArgumentNotNull(message, "message"); + Contract.Requires<ArgumentNullException>(message != null); ErrorUtilities.VerifyProtocol(message.ReturnTo != null, OpenIdStrings.ReturnToRequiredForResponse); return message.ReturnTo; } diff --git a/src/DotNetOpenAuth/OpenId/Messages/IndirectSignedResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/IndirectSignedResponse.cs index 9462d21..2f02974 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/IndirectSignedResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/IndirectSignedResponse.cs @@ -62,7 +62,7 @@ namespace DotNetOpenAuth.OpenId.Messages { /// </param> internal IndirectSignedResponse(SignedResponseRequest request) : base(request, Protocol.Lookup(GetVersion(request)).Args.Mode.id_res) { - ErrorUtilities.VerifyArgumentNotNull(request, "request"); + Contract.Requires<ArgumentNullException>(request != null); this.ReturnTo = request.ReturnTo; this.ProviderEndpoint = request.Recipient.StripQueryArgumentsWithPrefix(Protocol.openid.Prefix); @@ -77,8 +77,7 @@ namespace DotNetOpenAuth.OpenId.Messages { /// <param name="channel">The channel. This is used only within the constructor and is not stored in a field.</param> internal IndirectSignedResponse(CheckAuthenticationRequest previouslySignedMessage, Channel channel) : base(GetVersion(previouslySignedMessage), previouslySignedMessage.ReturnTo, Protocol.Lookup(GetVersion(previouslySignedMessage)).Args.Mode.id_res) { - Contract.Requires(channel != null); - ErrorUtilities.VerifyArgumentNotNull(channel, "channel"); + Contract.Requires<ArgumentNullException>(channel != null); // Copy all message parts from the check_authentication message into this one, // except for the openid.mode parameter. @@ -332,7 +331,7 @@ namespace DotNetOpenAuth.OpenId.Messages { /// cannot verify the private signature made by the relying party. /// </remarks> internal string GetReturnToArgument(string key) { - ErrorUtilities.VerifyNonZeroLength(key, "key"); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(key)); ErrorUtilities.VerifyInternal(this.ReturnTo != null, "ReturnTo was expected to be required but is null."); string value; @@ -358,8 +357,7 @@ namespace DotNetOpenAuth.OpenId.Messages { /// A dictionary of the signed message parts. /// </returns> internal IDictionary<string, string> GetSignedMessageParts(Channel channel) { - Contract.Requires(channel != null); - ErrorUtilities.VerifyArgumentNotNull(channel, "channel"); + Contract.Requires<ArgumentNullException>(channel != null); ITamperResistantOpenIdMessage signedSelf = this; if (signedSelf.SignedParameterOrder == null) { diff --git a/src/DotNetOpenAuth/OpenId/Messages/NegativeAssertionResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/NegativeAssertionResponse.cs index 99a7c5a..52ff884 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/NegativeAssertionResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/NegativeAssertionResponse.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; @@ -114,8 +115,8 @@ namespace DotNetOpenAuth.OpenId.Messages { /// <param name="channel">The channel to use to simulate construction of the message.</param> /// <returns>The value to use for the user_setup_url parameter.</returns> private static Uri ConstructUserSetupUrl(CheckIdRequest immediateRequest, Channel channel) { - ErrorUtilities.VerifyArgumentNotNull(immediateRequest, "immediateRequest"); - ErrorUtilities.VerifyArgumentNotNull(channel, "channel"); + Contract.Requires<ArgumentNullException>(immediateRequest != null); + Contract.Requires<ArgumentNullException>(channel != null); ErrorUtilities.VerifyInternal(immediateRequest.Immediate, "Only immediate requests should be sent here."); var setupRequest = new CheckIdRequest(immediateRequest.Version, immediateRequest.Recipient, AuthenticationRequestMode.Setup); @@ -132,7 +133,7 @@ namespace DotNetOpenAuth.OpenId.Messages { /// <param name="request">The request that we're responding to.</param> /// <returns>The value of the openid.mode parameter to use.</returns> private static string GetMode(SignedResponseRequest request) { - ErrorUtilities.VerifyArgumentNotNull(request, "request"); + Contract.Requires<ArgumentNullException>(request != null); Protocol protocol = Protocol.Lookup(request.Version); return request.Immediate ? protocol.Args.Mode.setup_needed : protocol.Args.Mode.cancel; diff --git a/src/DotNetOpenAuth/OpenId/Messages/RequestBase.cs b/src/DotNetOpenAuth/OpenId/Messages/RequestBase.cs index 7189583..6b89e92 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/RequestBase.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/RequestBase.cs @@ -9,6 +9,7 @@ namespace DotNetOpenAuth.OpenId.Messages { using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; using DotNetOpenAuth.Messaging; /// <summary> @@ -48,12 +49,8 @@ namespace DotNetOpenAuth.OpenId.Messages { /// <param name="mode">The value for the openid.mode parameter.</param> /// <param name="transport">A value indicating whether the message will be transmitted directly or indirectly.</param> protected RequestBase(Version version, Uri providerEndpoint, string mode, MessageTransport transport) { - if (providerEndpoint == null) { - throw new ArgumentNullException("providerEndpoint"); - } - if (String.IsNullOrEmpty(mode)) { - throw new ArgumentNullException("mode"); - } + Contract.Requires<ArgumentNullException>(providerEndpoint != null); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(mode)); this.Recipient = providerEndpoint; this.Mode = mode; @@ -181,7 +178,7 @@ namespace DotNetOpenAuth.OpenId.Messages { /// instead of a <see cref="NullReferenceException"/>. /// </remarks> protected static string GetProtocolConstant(Version protocolVersion, Func<Protocol, string> mode) { - ErrorUtilities.VerifyArgumentNotNull(protocolVersion, "protocolVersion"); + Contract.Requires<ArgumentNullException>(protocolVersion != null); return mode(Protocol.Lookup(protocolVersion)); } } diff --git a/src/DotNetOpenAuth/OpenId/Messages/SignedResponseRequest.cs b/src/DotNetOpenAuth/OpenId/Messages/SignedResponseRequest.cs index 1096468..7eb5407 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/SignedResponseRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/SignedResponseRequest.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.OpenId.Messages { using System; using System.Collections.Generic; using System.Diagnostics; + using System.Diagnostics.Contracts; using System.Linq; using System.Text; using DotNetOpenAuth.Messaging; @@ -107,6 +108,11 @@ namespace DotNetOpenAuth.OpenId.Messages { internal Realm Realm { get; set; } /// <summary> + /// Gets or sets a value indicating whether the return_to value should be signed. + /// </summary> + internal bool SignReturnTo { get; set; } + + /// <summary> /// Checks the message state for conformity to the protocol specification /// and throws an exception if the message is invalid. /// </summary> @@ -139,7 +145,7 @@ namespace DotNetOpenAuth.OpenId.Messages { /// when and if a positive assertion comes back from the Provider. /// </remarks> internal void AddReturnToArguments(IEnumerable<KeyValuePair<string, string>> keysValues) { - ErrorUtilities.VerifyArgumentNotNull(keysValues, "keysValues"); + Contract.Requires<ArgumentNullException>(keysValues != null); ErrorUtilities.VerifyOperation(this.ReturnTo != null, OpenIdStrings.ReturnToRequiredForOperation); UriBuilder returnToBuilder = new UriBuilder(this.ReturnTo); returnToBuilder.AppendAndReplaceQueryArgs(keysValues); @@ -170,7 +176,7 @@ namespace DotNetOpenAuth.OpenId.Messages { /// </param> /// <returns>checkid_immediate or checkid_setup</returns> private static string GetMode(Version version, AuthenticationRequestMode mode) { - ErrorUtilities.VerifyArgumentNotNull(version, "version"); + Contract.Requires<ArgumentNullException>(version != null); Protocol protocol = Protocol.Lookup(version); return mode == AuthenticationRequestMode.Immediate ? protocol.Args.Mode.checkid_immediate : protocol.Args.Mode.checkid_setup; diff --git a/src/DotNetOpenAuth/OpenId/NoDiscoveryIdentifier.cs b/src/DotNetOpenAuth/OpenId/NoDiscoveryIdentifier.cs index 2c8e865..636df67 100644 --- a/src/DotNetOpenAuth/OpenId/NoDiscoveryIdentifier.cs +++ b/src/DotNetOpenAuth/OpenId/NoDiscoveryIdentifier.cs @@ -5,6 +5,7 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.OpenId { + using System; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; @@ -28,9 +29,8 @@ namespace DotNetOpenAuth.OpenId { /// <param name="wrappedIdentifier">The ordinary Identifier whose discovery is being masked.</param> /// <param name="claimSsl">Whether this Identifier should claim to be SSL-secure, although no discovery will never generate service endpoints anyway.</param> internal NoDiscoveryIdentifier(Identifier wrappedIdentifier, bool claimSsl) - : base(claimSsl) { - Contract.Requires(wrappedIdentifier != null); - ErrorUtilities.VerifyArgumentNotNull(wrappedIdentifier, "wrappedIdentifier"); + : base(wrappedIdentifier.OriginalString, claimSsl) { + Contract.Requires<ArgumentNullException>(wrappedIdentifier != null); this.wrappedIdentifier = wrappedIdentifier; } diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs index cca41a0..33a16f8 100644 --- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs +++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs @@ -286,6 +286,15 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> + /// Looks up a localized string similar to The HTML head tag must include runat="server".. + /// </summary> + internal static string HeadTagMustIncludeRunatServer { + get { + return ResourceManager.GetString("HeadTagMustIncludeRunatServer", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to ClaimedIdentifier and LocalIdentifier must be the same when IsIdentifierSelect is true.. /// </summary> internal static string IdentifierSelectRequiresMatchingIdentifiers { @@ -358,7 +367,7 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> - /// Looks up a localized string similar to Not a recognized XRI format: '{0}'.. + /// Looks up a localized string similar to Not a recognized XRI format.. /// </summary> internal static string InvalidXri { get { @@ -515,6 +524,15 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> + /// Looks up a localized string similar to The {0} property must be set first.. + /// </summary> + internal static string PropertyNotSet { + get { + return ResourceManager.GetString("PropertyNotSet", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to This property value is not supported by this control.. /// </summary> internal static string PropertyValueNotSupported { diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx index f47e512..c5f506d 100644 --- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx +++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx @@ -178,7 +178,7 @@ <value>The value '{0}' is not a valid URI.</value> </data> <data name="InvalidXri" xml:space="preserve"> - <value>Not a recognized XRI format: '{0}'.</value> + <value>Not a recognized XRI format.</value> </data> <data name="IssuedAssertionFailsIdentifierDiscovery" xml:space="preserve"> <value>The OpenID Provider issued an assertion for an Identifier whose discovery information did not match. @@ -340,4 +340,10 @@ Discovered endpoint info: <data name="PositiveAssertionFromNonWhitelistedProvider" xml:space="preserve"> <value>An positive OpenID assertion was received from OP endpoint {0} that is not on this relying party's whitelist.</value> </data> -</root> + <data name="HeadTagMustIncludeRunatServer" xml:space="preserve"> + <value>The HTML head tag must include runat="server".</value> + </data> + <data name="PropertyNotSet" xml:space="preserve"> + <value>The {0} property must be set first.</value> + </data> +</root>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.sr.resx b/src/DotNetOpenAuth/OpenId/OpenIdStrings.sr.resx new file mode 100644 index 0000000..0df62c0 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.sr.resx @@ -0,0 +1,340 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="AssociationSecretAndTypeLengthMismatch" xml:space="preserve"> + <value>Dužina deljene tajne ({0}) ne slaže se sa dužinom zahtevanom od povezujućeg tipa ('{1}').</value> + </data> + <data name="AssociationSecretHashLengthMismatch" xml:space="preserve"> + <value>Dužina šifrovane deljene tajne ({0}) ne slaže se sa dužinom hashing algoritma ({1}).</value> + </data> + <data name="AssociationStoreRequiresNonceStore" xml:space="preserve"> + <value>Ako je dato povezujuće skladište, jedinstveni identifikator skladišta takođe mora biti prisutan.</value> + </data> + <data name="BadAssociationPrivateData" xml:space="preserve"> + <value>Dostavljeni privatni podaci ne slažu se ni sa jednim poznatim Association tipom. Dužina im je možda prekratka ili su podaci neispravni.</value> + </data> + <data name="CallbackArgumentsRequireSecretStore" xml:space="preserve"> + <value>Callback argumenti su podržani jedino kada je {0} dustupan za {1}.</value> + </data> + <data name="CallDeserializeBeforeCreateResponse" xml:space="preserve"> + <value>Simple Registration zahtev može generisati odgovor jedino na prijemnoj strani.</value> + </data> + <data name="ClaimedIdAndLocalIdMustBothPresentOrAbsent" xml:space="preserve"> + <value>openid.claimed_id i openid.identity parametri moraju istovremeno biti prisutni ili nedostajati.</value> + </data> + <data name="ClaimedIdentifierCannotBeSetOnDelegatedAuthentication" xml:space="preserve"> + <value>ClaimedIdentifier svojstvo ne može se podesiti kada IsDelegatedIdentifier ima vrednost true da bi se izbeglo narušavanje OpenID URL delegiranja.</value> + </data> + <data name="ClaimedIdentifierMustBeSetFirst" xml:space="preserve"> + <value>ClaimedIdentifier svojstvo se najpre mora podesiti.</value> + </data> + <data name="DiffieHellmanRequiredPropertiesNotSet" xml:space="preserve"> + <value>Sledeća svojstva moraju biti podešena pre nego što Diffie-Hellmanov algoritam može generisati javni ključ: {0}</value> + </data> + <data name="ExplicitHttpUriSuppliedWithSslRequirement" xml:space="preserve"> + <value>URI nije SSL a svojstvo requireSslDiscovery je podešeno na true.</value> + </data> + <data name="ExtensionAlreadyAddedWithSameTypeURI" xml:space="preserve"> + <value>Prostor deljenja proširenja '{0}' je već dodat. Samo jedno proširenje po prostoru je dozvoljeno u datom zahtevu.</value> + </data> + <data name="ExtensionLookupSupportUnavailable" xml:space="preserve"> + <value>Ne može se tražiti podrška za proširenja na rehidriranom ServiceEndpoint.</value> + </data> + <data name="FragmentNotAllowedOnXRIs" xml:space="preserve"> + <value>Segmenti fragmenta se ne primenjuju na XRI identifikatore.</value> + </data> + <data name="IdentifierSelectRequiresMatchingIdentifiers" xml:space="preserve"> + <value>ClaimedIdentifier i LocalIdentifier moraju biti isti kada IsIdentifierSelect ima vrednost true.</value> + </data> + <data name="IndirectErrorFormattedMessage" xml:space="preserve"> + <value>{0} (Kontakt: {1}, Referenca: {2})</value> + </data> + <data name="InvalidCharacterInKeyValueFormInput" xml:space="preserve"> + <value>Ne može se enkodovati '{0}' jer sadrži nevalidan znak za Key-Value Form enkodiranje. (linija {1}: '{2}')</value> + </data> + <data name="InvalidKeyValueFormCharacterMissing" xml:space="preserve"> + <value>Ne može se dekodovati Key-Value Form jer je pronađena linija bez '{0}' znaka. (linija {1}: '{2}')</value> + </data> + <data name="InvalidScheme" xml:space="preserve"> + <value>Šema mora biti http ili https a bila je '{0}'.</value> + </data> + <data name="InvalidUri" xml:space="preserve"> + <value>Vrednost '{0}' nije validan URI.</value> + </data> + <data name="InvalidXri" xml:space="preserve"> + <value>Nije prepoznat XRI format: '{0}'.</value> + </data> + <data name="IssuedAssertionFailsIdentifierDiscovery" xml:space="preserve"> + <value>OpenID Provider dao je iskaz za Identifier čije se informacije o pronalaženju ne slažu. +Informacije o krajnoj tački iskaza: +{0} +Informacije o otkrivenoj krajnjoj tački: +{1}</value> + </data> + <data name="KeysListAndDictionaryDoNotMatch" xml:space="preserve"> + <value>Lista ključeva se ne slaže sa ponuđenim rečnikom.</value> + </data> + <data name="MatchingArgumentsExpected" xml:space="preserve"> + <value>Parametri '{0}' i '{1}' moraju oba biti ili ne smeju oba biti '{2}'.</value> + </data> + <data name="NoAssociationTypeFoundByLength" xml:space="preserve"> + <value>Ni jedan prepoznati tip asociranja se ne uklapa sa zahtevanom dužinom {0}.</value> + </data> + <data name="NoAssociationTypeFoundByName" xml:space="preserve"> + <value>Ni jedan prepoznati tip asociranja se ne uklapa sa zahtevanim imenom '{0}'.</value> + </data> + <data name="NoEncryptionSessionRequiresHttps" xml:space="preserve"> + <value>Osim ako se ne koristi enkripcija transportnog sloja, "no-encryption" NE SME biti korišćen.</value> + </data> + <data name="NoSessionTypeFound" xml:space="preserve"> + <value>Diffie-Hellman sesija tipa '{0}' nije pronađena za OpenID {1}.</value> + </data> + <data name="OpenIdEndpointNotFound" xml:space="preserve"> + <value>Nijedna OpenID krajnja tačka nije pronađena.</value> + </data> + <data name="OperationOnlyValidForSetupRequiredState" xml:space="preserve"> + <value>Ova operacija je jedino dozvoljena kada je IAuthenticationResponse.State == AuthenticationStatus.SetupRequired.</value> + </data> + <data name="ProviderVersionUnrecognized" xml:space="preserve"> + <value>Nije moguće utvrditi verziju OpenID protokola implementiranog od strane Provider-a na krajnjoj tački '{0}'.</value> + </data> + <data name="RealmCausedRedirectUponDiscovery" xml:space="preserve"> + <value>HTTP zahtev ka URL-u domena ({0}) rezultovao je redirekcijom, koja nije dozvoljena u togu pronalaženja Relying Party.</value> + </data> + <data name="ReturnToNotUnderRealm" xml:space="preserve"> + <value>return_to '{0}' nije unutar domena '{1}'.</value> + </data> + <data name="ReturnToParamDoesNotMatchRequestUrl" xml:space="preserve"> + <value>{0} parametar ({1}) se ne slaže sa trenutnim URL ({2}) sa kojim je zahtev napravljen.</value> + </data> + <data name="ReturnToRequiredForResponse" xml:space="preserve"> + <value>openid.return_to parametar je neophodan u poruci zahteva da bi se konstruisao odgovor, ali ovaj parametar nedostaje.</value> + </data> + <data name="SignatureDoesNotIncludeMandatoryParts" xml:space="preserve"> + <value>Sledeći parametri nisu uključeni u potpis a moraju da budu: {0}</value> + </data> + <data name="SregInvalidBirthdate" xml:space="preserve"> + <value>Neispravna vrednost za datum rođenja. Mora biti u formi gggg-MM-dd.</value> + </data> + <data name="TypeMustImplementX" xml:space="preserve"> + <value>Tip mora implementirati {0}.</value> + </data> + <data name="UnsolicitedAssertionsNotAllowedFrom1xOPs" xml:space="preserve"> + <value>Nezahtevani iskazi nisu dozvoljeni od strane 1.0 OpenID Providers.</value> + </data> + <data name="UserSetupUrlRequiredInImmediateNegativeResponse" xml:space="preserve"> + <value>openid.user_setup_url parametar je neophodan prilikom slanja negativnih poruka sa iskazima prilikom odgovaranja na zahteve u trenutnom modu.</value> + </data> + <data name="XriResolutionFailed" xml:space="preserve"> + <value>XRI razrešivanje neuspešno.</value> + </data> + <data name="StoreRequiredWhenNoHttpContextAvailable" xml:space="preserve"> + <value>Tekući HttpContext nije detektovan, tako da {0} instanca mora biti eksplicitno postavljena ili specificirana u .config fajlu. Pozvati preklopljeni konstruktor koji uzima parametar {0}.</value> + </data> + <data name="AttributeAlreadyAdded" xml:space="preserve"> + <value>Atribut sa URI tipom '{0}' je već dodat.</value> + </data> + <data name="AttributeTooManyValues" xml:space="preserve"> + <value>Samo {0} vrednosti za atribut '{1}' su zahtevane, ali {2} su ponuđene.</value> + </data> + <data name="UnspecifiedDateTimeKindNotAllowed" xml:space="preserve"> + <value>Prosleđivanje objekta tipa DateTime čije svojstvo Kind ima vrednost Unspecified nije dozvoljeno.</value> + </data> + <data name="AssociationOrSessionTypeUnrecognizedOrNotSupported" xml:space="preserve"> + <value>Zahtevani tip asocijacije '{0}' sa sesijom tipa '{1}' nije prepoznat ili nije podržan od strane ovog Provider-a zbog bezbedonosnih zahteva.</value> + </data> + <data name="IncompatibleAssociationAndSessionTypes" xml:space="preserve"> + <value>Provider je zahtevao asocijaciju tipa '{0}' i sesiju tipa '{1}', koje nisu međusobno kompatibilne.</value> + </data> + <data name="CreateRequestAlreadyCalled" xml:space="preserve"> + <value>Zahtev za autentifikacijom je već kreiran korišćenjem CreateRequest().</value> + </data> + <data name="OpenIdTextBoxEmpty" xml:space="preserve"> + <value>Nijedan OpenID url nije ponuđen.</value> + </data> + <data name="ClientScriptExtensionPropertyNameCollision" xml:space="preserve"> + <value>Ekstenzija sa svojstvom ovog imena ('{0}') je već registrovana.</value> + </data> + <data name="ClientScriptExtensionTypeCollision" xml:space="preserve"> + <value>Ekstenzija '{0}' je već registrovana.</value> + </data> + <data name="UnexpectedHttpStatusCode" xml:space="preserve"> + <value>Neočekivani HTTP statusni kod {0} {1} primljen u direktnom odgovoru.</value> + </data> + <data name="NotSupportedByAuthenticationSnapshot" xml:space="preserve"> + <value>Ova operacija nije podržana od strane serijalizovanih odgovora za autentifikaciju. Pokušati ovu operaciju iz LoggedIn handler-a događaja.</value> + </data> + <data name="NoRelyingPartyEndpointDiscovered" xml:space="preserve"> + <value>Nijedan XRDS dokument koji sadrži informaciju o OpenID Relying Party krajnjoj tački nije pronadjen u {0}.</value> + </data> + <data name="AbsoluteUriRequired" xml:space="preserve"> + <value>Absolutni URI je zahtevan za ovu vrednost.</value> + </data> + <data name="UnsolicitedAssertionForUnrelatedClaimedIdentifier" xml:space="preserve"> + <value>Nezahtevani iskaz ne može biti poslat za navedeni identifikator {0} jer ovo nije autorizovani Provider za taj identifikator.</value> + </data> + <data name="MaximumAuthenticationTimeExpired" xml:space="preserve"> + <value>Maksimalno dozvoljeno vreme za kompletiranje autentifikacije je isteklo. Molimo pokušajte ponovo.</value> + </data> + <data name="PrivateRPSecretNotFound" xml:space="preserve"> + <value>Ne može se pronaći tajna za potpisivanje od strane handle-a '{0}'.</value> + </data> + <data name="ResponseNotReady" xml:space="preserve"> + <value>Odgovor nije spreman. Koristiti najpre IsResponseReady za proveru da li je odgovor spreman.</value> + </data> + <data name="UnsupportedChannelConfiguration" xml:space="preserve"> + <value>Ovo svojstvo nije dostupno zbog nepoznate konfiguracije kanala.</value> + </data> + <data name="IdentityAndClaimedIdentifierMustBeBothPresentOrAbsent" xml:space="preserve"> + <value>openid.identity i openid.claimed_id parametri moraju istovremeno biti prisutna ili istovremeno odsutna u poruci.</value> + </data> + <data name="ReturnToRequiredForOperation" xml:space="preserve"> + <value>Svojstvo ReturnTo ne sme biti null da bi se podržala ova operacija.</value> + </data> + <data name="UnsolicitedAssertionRejectionRequiresNonceStore" xml:space="preserve"> + <value>Odbijanje nezahtevanih iskaza zahteva skladište jedinstvenih identifikatora i skladište asocijacija.</value> + </data> + <data name="UnsolicitedAssertionsNotAllowed" xml:space="preserve"> + <value>Nezahtevani iskazi nisu dozvoljeni od strane ovog Relying Party.</value> + </data> + <data name="DelegatingIdentifiersNotAllowed" xml:space="preserve"> + <value>Samo OpenID-jevi izdati direktno od strane njihovog OpenID Provider-a su ovde dozvoljeni.</value> + </data> + <data name="XriResolutionDisabled" xml:space="preserve"> + <value>XRI podrška je onemogućena na ovom sajtu.</value> + </data> + <data name="AssociationStoreRequired" xml:space="preserve"> + <value>Skladište asocijacija nije dato a zahtevano je za trenutnu konfiguraciju.</value> + </data> + <data name="UnexpectedEnumPropertyValue" xml:space="preserve"> + <value>Svojstvo {0} je imalo neočekivanu vrednost {1}.</value> + </data> + <data name="NoIdentifierSet" xml:space="preserve"> + <value>Ni jedan identifikator nije podešen.</value> + </data> + <data name="PropertyValueNotSupported" xml:space="preserve"> + <value>Ova vrednost svojstva nije podržana od strane ove kontrole.</value> + </data> + <data name="ArgumentIsPpidIdentifier" xml:space="preserve"> + <value>Ovo je već PPID Identifier.</value> + </data> + <data name="RequireSslNotSatisfiedByAssertedClaimedId" xml:space="preserve"> + <value>Žao nam je. Ovaj sajt jedino prihvata OpenID-jeve koji su HTTPS-bezbedni, a {0} nije bezbedni Identifier.</value> + </data> +</root>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/OpenId/OpenIdUtilities.cs b/src/DotNetOpenAuth/OpenId/OpenIdUtilities.cs index 3cee968..3e75e61 100644 --- a/src/DotNetOpenAuth/OpenId/OpenIdUtilities.cs +++ b/src/DotNetOpenAuth/OpenId/OpenIdUtilities.cs @@ -34,7 +34,7 @@ namespace DotNetOpenAuth.OpenId { /// <param name="message">The message.</param> /// <returns>The OpenID protocol instance.</returns> internal static Protocol GetProtocol(this IProtocolMessage message) { - ErrorUtilities.VerifyArgumentNotNull(message, "message"); + Contract.Requires<ArgumentNullException>(message != null); return Protocol.Lookup(message.Version); } @@ -103,10 +103,8 @@ namespace DotNetOpenAuth.OpenId { /// <returns>The fully-qualified realm.</returns> [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "DotNetOpenAuth.OpenId.Realm", Justification = "Using ctor for validation.")] internal static UriBuilder GetResolvedRealm(Page page, string realm, HttpRequestInfo requestContext) { - Contract.Requires(page != null); - Contract.Requires(requestContext != null); - ErrorUtilities.VerifyArgumentNotNull(page, "page"); - ErrorUtilities.VerifyArgumentNotNull(requestContext, "requestContext"); + Contract.Requires<ArgumentNullException>(page != null); + Contract.Requires<ArgumentNullException>(requestContext != null); // Allow for *. realm notation, as well as ASP.NET ~/ shortcuts. @@ -148,8 +146,7 @@ namespace DotNetOpenAuth.OpenId { /// can plug in. /// </remarks> internal static IList<IOpenIdExtensionFactory> GetExtensionFactories(this Channel channel) { - Contract.Requires(channel != null); - ErrorUtilities.VerifyArgumentNotNull(channel, "channel"); + Contract.Requires<ArgumentNullException>(channel != null); var extensionsBindingElement = channel.BindingElements.OfType<ExtensionsBindingElement>().SingleOrDefault(); ErrorUtilities.VerifyOperation(extensionsBindingElement != null, OpenIdStrings.UnsupportedChannelConfiguration); diff --git a/src/DotNetOpenAuth/OpenId/OpenIdXrdsHelper.cs b/src/DotNetOpenAuth/OpenId/OpenIdXrdsHelper.cs index fd83061..664a127 100644 --- a/src/DotNetOpenAuth/OpenId/OpenIdXrdsHelper.cs +++ b/src/DotNetOpenAuth/OpenId/OpenIdXrdsHelper.cs @@ -57,7 +57,7 @@ namespace DotNetOpenAuth.OpenId { /// </returns> internal static IEnumerable<ServiceEndpoint> CreateServiceEndpoints(this XrdsDocument xrds, UriIdentifier claimedIdentifier, UriIdentifier userSuppliedIdentifier) { var endpoints = new List<ServiceEndpoint>(); - endpoints.AddRange(xrds.GenerateOPIdentifierServiceEndpoints(claimedIdentifier)); + endpoints.AddRange(xrds.GenerateOPIdentifierServiceEndpoints(userSuppliedIdentifier)); // If any OP Identifier service elements were found, we must not proceed // to return any Claimed Identifier services. @@ -77,8 +77,8 @@ namespace DotNetOpenAuth.OpenId { /// <param name="userSuppliedIdentifier">The user-supplied i-name that was used to discover this XRDS document.</param> /// <returns>A sequence of OpenID Providers that can assert ownership of the canonical ID given in this document.</returns> internal static IEnumerable<ServiceEndpoint> CreateServiceEndpoints(this XrdsDocument xrds, XriIdentifier userSuppliedIdentifier) { - Contract.Requires(xrds != null); - Contract.Requires(userSuppliedIdentifier != null); + Contract.Requires<ArgumentNullException>(xrds != null); + Contract.Requires<ArgumentNullException>(userSuppliedIdentifier != null); Contract.Ensures(Contract.Result<IEnumerable<ServiceEndpoint>>() != null); var endpoints = new List<ServiceEndpoint>(); endpoints.AddRange(xrds.GenerateOPIdentifierServiceEndpoints(userSuppliedIdentifier)); @@ -97,11 +97,11 @@ namespace DotNetOpenAuth.OpenId { /// Generates OpenID Providers that can authenticate using directed identity. /// </summary> /// <param name="xrds">The XrdsDocument instance to use in this process.</param> - /// <param name="opIdentifier">The OP Identifier entered (and resolved) by the user.</param> + /// <param name="opIdentifier">The OP Identifier entered (and resolved) by the user. Essentially the user-supplied identifier.</param> /// <returns>A sequence of the providers that can offer directed identity services.</returns> private static IEnumerable<ServiceEndpoint> GenerateOPIdentifierServiceEndpoints(this XrdsDocument xrds, Identifier opIdentifier) { - Contract.Requires(xrds != null); - Contract.Requires(opIdentifier != null); + Contract.Requires<ArgumentNullException>(xrds != null); + Contract.Requires<ArgumentNullException>(opIdentifier != null); Contract.Ensures(Contract.Result<IEnumerable<ServiceEndpoint>>() != null); return from service in xrds.FindOPIdentifierServices() from uri in service.UriElements diff --git a/src/DotNetOpenAuth/OpenId/Protocol.cs b/src/DotNetOpenAuth/OpenId/Protocol.cs index 7b8a2f1..5aacfd2 100644 --- a/src/DotNetOpenAuth/OpenId/Protocol.cs +++ b/src/DotNetOpenAuth/OpenId/Protocol.cs @@ -11,6 +11,7 @@ namespace DotNetOpenAuth.OpenId { using DotNetOpenAuth.Messaging; using System.Globalization; using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; using System.Diagnostics; /// <summary> @@ -36,7 +37,7 @@ namespace DotNetOpenAuth.OpenId { /// constants to each version used in the protocol. /// </summary> [DebuggerDisplay("OpenID {Version}")] - internal class Protocol { + internal sealed class Protocol { /// <summary> /// The value of the openid.ns parameter in the OpenID 2.0 specification. /// </summary> @@ -156,7 +157,7 @@ namespace DotNetOpenAuth.OpenId { /// of an incoming OpenID <i>indirect</i> message or <i>direct request</i>. /// </summary> internal static Protocol Detect(IDictionary<string, string> query) { - if (query == null) throw new ArgumentNullException("query"); + Contract.Requires<ArgumentNullException>(query != null); return query.ContainsKey(V20.openid.ns) ? V20 : V11; } /// <summary> @@ -164,7 +165,7 @@ namespace DotNetOpenAuth.OpenId { /// of an incoming OpenID <i>direct</i> response message. /// </summary> internal static Protocol DetectFromDirectResponse(IDictionary<string, string> query) { - if (query == null) throw new ArgumentNullException("query"); + Contract.Requires<ArgumentNullException>(query != null); return query.ContainsKey(V20.openidnp.ns) ? V20 : V11; } /// <summary> @@ -172,7 +173,7 @@ namespace DotNetOpenAuth.OpenId { /// of XRDS Service Type URIs included for some service. /// </summary> internal static Protocol Detect(IEnumerable<string> serviceTypeURIs) { - if (serviceTypeURIs == null) throw new ArgumentNullException("serviceTypeURIs"); + Contract.Requires<ArgumentNullException>(serviceTypeURIs != null); return FindBestVersion(p => p.OPIdentifierServiceTypeURI, serviceTypeURIs) ?? FindBestVersion(p => p.ClaimedIdentifierServiceTypeURI, serviceTypeURIs) ?? FindBestVersion(p => p.RPReturnToTypeURI, serviceTypeURIs); @@ -253,7 +254,7 @@ namespace DotNetOpenAuth.OpenId { /// </summary> public QueryArguments Args = new QueryArguments(); - internal class QueryParameters { + internal sealed class QueryParameters { /// <summary> /// The value "openid." /// </summary> @@ -319,18 +320,31 @@ namespace DotNetOpenAuth.OpenId { public string dh_server_public = "dh_server_public"; public string enc_mac_key = "enc_mac_key"; public string mac_key = "mac_key"; + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(!string.IsNullOrEmpty(this.Prefix)); + } +#endif } - internal class QueryArguments { + + internal sealed class QueryArguments { public ErrorCodes ErrorCode = new ErrorCodes(); public SessionTypes SessionType = new SessionTypes(); public SignatureAlgorithms SignatureAlgorithm = new SignatureAlgorithms(); public Modes Mode = new Modes(); public IsValidValues IsValid = new IsValidValues(); - internal class ErrorCodes { + internal sealed class ErrorCodes { public string UnsupportedType = "unsupported-type"; } - internal class SessionTypes { + internal sealed class SessionTypes { /// <summary> /// A preference order list of all supported session types. /// </summary> @@ -352,7 +366,7 @@ namespace DotNetOpenAuth.OpenId { } } } - internal class SignatureAlgorithms { + internal sealed class SignatureAlgorithms { /// <summary> /// A preference order list of signature algorithms we support. /// </summary> @@ -372,7 +386,7 @@ namespace DotNetOpenAuth.OpenId { } } } - internal class Modes { + internal sealed class Modes { public string cancel = "cancel"; public string error = "error"; public string id_res = "id_res"; @@ -382,7 +396,7 @@ namespace DotNetOpenAuth.OpenId { public string associate = "associate"; public string setup_needed = "id_res"; // V2 overrides this } - internal class IsValidValues { + internal sealed class IsValidValues { public string True = "true"; public string False = "false"; } diff --git a/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequest.cs index a500e3b..24f84d6 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequest.cs @@ -30,9 +30,8 @@ namespace DotNetOpenAuth.OpenId.Provider { [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Code contracts require it.")] internal AnonymousRequest(OpenIdProvider provider, SignedResponseRequest request) : base(provider, request) { - Contract.Requires(provider != null); - Contract.Requires(!(request is CheckIdRequest), "Instantiate " + typeof(AuthenticationRequest).Name + " to handle this kind of message."); - ErrorUtilities.VerifyInternal(!(request is CheckIdRequest), "Instantiate {0} to handle this kind of message.", typeof(AuthenticationRequest).Name); + Contract.Requires<ArgumentNullException>(provider != null); + Contract.Requires<ArgumentException>(!(request is CheckIdRequest), "Instantiate " + typeof(AuthenticationRequest).Name + " to handle this kind of message."); this.positiveResponse = new IndirectSignedResponse(request); } diff --git a/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequestEventArgs.cs b/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequestEventArgs.cs index cdd5311..9ffaa55 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequestEventArgs.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequestEventArgs.cs @@ -18,8 +18,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// </summary> /// <param name="request">The incoming OpenID request.</param> internal AnonymousRequestEventArgs(IAnonymousRequest request) { - Contract.Requires(request != null); - ErrorUtilities.VerifyArgumentNotNull(request, "request"); + Contract.Requires<ArgumentNullException>(request != null); this.Request = request; } diff --git a/src/DotNetOpenAuth/OpenId/Provider/AuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/AuthenticationRequest.cs index a5d936b..a229488 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/AuthenticationRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/AuthenticationRequest.cs @@ -29,8 +29,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <param name="request">The incoming authentication request message.</param> internal AuthenticationRequest(OpenIdProvider provider, CheckIdRequest request) : base(provider, request) { - Contract.Requires(provider != null); - ErrorUtilities.VerifyArgumentNotNull(provider, "provider"); + Contract.Requires<ArgumentNullException>(provider != null); this.positiveResponse = new PositiveAssertionResponse(request); @@ -150,11 +149,9 @@ namespace DotNetOpenAuth.OpenId.Provider { set { // Keep LocalIdentifier and ClaimedIdentifier in sync for directed identity. if (this.IsDirectedIdentity) { - ErrorUtilities.VerifyOperation(!(this.LocalIdentifier != null && this.LocalIdentifier != value), OpenIdStrings.IdentifierSelectRequiresMatchingIdentifiers); this.positiveResponse.LocalIdentifier = value; } - ErrorUtilities.VerifyOperation(!this.IsDelegatedIdentifier, OpenIdStrings.ClaimedIdentifierCannotBeSetOnDelegatedAuthentication); this.positiveResponse.ClaimedIdentifier = value; } } @@ -207,9 +204,6 @@ namespace DotNetOpenAuth.OpenId.Provider { /// request before the <see cref="ClaimedIdentifier"/> property is set. /// </exception> public void SetClaimedIdentifierFragment(string fragment) { - ErrorUtilities.VerifyOperation(!(this.IsDirectedIdentity && this.ClaimedIdentifier == null), OpenIdStrings.ClaimedIdentifierMustBeSetFirst); - ErrorUtilities.VerifyOperation(!(this.ClaimedIdentifier is XriIdentifier), OpenIdStrings.FragmentNotAllowedOnXRIs); - UriBuilder builder = new UriBuilder(this.ClaimedIdentifier); builder.Fragment = fragment; this.positiveResponse.ClaimedIdentifier = builder.Uri; @@ -220,8 +214,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// </summary> /// <param name="identifier">The value to set to the <see cref="ClaimedIdentifier"/> and <see cref="LocalIdentifier"/> properties.</param> internal void ResetClaimedAndLocalIdentifiers(Identifier identifier) { - Contract.Requires(identifier != null); - ErrorUtilities.VerifyArgumentNotNull(identifier, "identifier"); + Contract.Requires<ArgumentNullException>(identifier != null); this.positiveResponse.ClaimedIdentifier = identifier; this.positiveResponse.LocalIdentifier = identifier; diff --git a/src/DotNetOpenAuth/OpenId/Provider/AutoResponsiveRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/AutoResponsiveRequest.cs index d1d310e..e5988dd 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/AutoResponsiveRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/AutoResponsiveRequest.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OpenId.Provider { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.Linq; using System.Text; using DotNetOpenAuth.Messaging; @@ -31,7 +32,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <param name="securitySettings">The security settings.</param> internal AutoResponsiveRequest(IDirectedProtocolMessage request, IProtocolMessage response, ProviderSecuritySettings securitySettings) : base(request, securitySettings) { - ErrorUtilities.VerifyArgumentNotNull(response, "response"); + Contract.Requires<ArgumentNullException>(response != null); this.response = response; } @@ -44,7 +45,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <param name="securitySettings">The security settings.</param> internal AutoResponsiveRequest(IProtocolMessage response, ProviderSecuritySettings securitySettings) : base(IndirectResponseBase.GetVersion(response), securitySettings) { - ErrorUtilities.VerifyArgumentNotNull(response, "response"); + Contract.Requires<ArgumentNullException>(response != null); this.response = response; } diff --git a/src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs index 90dfa2f..38d1094 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs @@ -36,7 +36,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <param name="request">The incoming request message.</param> protected HostProcessedRequest(OpenIdProvider provider, SignedResponseRequest request) : base(request, provider.SecuritySettings) { - Contract.Requires(provider != null); + Contract.Requires<ArgumentNullException>(provider != null); this.negativeResponse = new NegativeAssertionResponse(request, provider.Channel); } @@ -113,8 +113,6 @@ namespace DotNetOpenAuth.OpenId.Provider { /// See OpenID Authentication 2.0 spec section 9.2.1. /// </remarks> public RelyingPartyDiscoveryResult IsReturnUrlDiscoverable(OpenIdProvider provider) { - ErrorUtilities.VerifyArgumentNotNull(provider, "provider"); - if (!this.realmDiscoveryResult.HasValue) { this.realmDiscoveryResult = this.IsReturnUrlDiscoverableCore(provider); } @@ -129,9 +127,10 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <param name="provider">The OpenIdProvider that is performing the RP discovery.</param> /// <returns>Result of realm discovery.</returns> private RelyingPartyDiscoveryResult IsReturnUrlDiscoverableCore(OpenIdProvider provider) { - Contract.Requires(provider != null); + Contract.Requires<ArgumentNullException>(provider != null); ErrorUtilities.VerifyInternal(this.Realm != null, "Realm should have been read or derived by now."); + try { if (this.SecuritySettings.RequireSsl && this.Realm.Scheme != Uri.UriSchemeHttps) { Logger.OpenId.WarnFormat("RP discovery failed because RequireSsl is true and RP discovery would begin at insecure URL {0}.", this.Realm); diff --git a/src/DotNetOpenAuth/OpenId/Provider/IAuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/IAuthenticationRequest.cs index bb837b5..077dcf1 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/IAuthenticationRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/IAuthenticationRequest.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OpenId.Provider { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.Text; using DotNetOpenAuth.Messaging; @@ -15,6 +16,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// This interface provides the details of the request and allows setting /// the response. /// </summary> + [ContractClass(typeof(IAuthenticationRequestContract))] public interface IAuthenticationRequest : IHostProcessedRequest { /// <summary> /// Gets a value indicating whether the Provider should help the user @@ -93,4 +95,263 @@ namespace DotNetOpenAuth.OpenId.Provider { /// </exception> void SetClaimedIdentifierFragment(string fragment); } + + /// <summary> + /// Code contract class for the <see cref="IAuthenticationRequest"/> type. + /// </summary> + [ContractClassFor(typeof(IAuthenticationRequest))] + internal abstract class IAuthenticationRequestContract : IAuthenticationRequest { + /// <summary> + /// Initializes a new instance of the <see cref="IAuthenticationRequestContract"/> class. + /// </summary> + protected IAuthenticationRequestContract() { + } + + #region IAuthenticationRequest Properties + + /// <summary> + /// Gets a value indicating whether the Provider should help the user + /// select a Claimed Identifier to send back to the relying party. + /// </summary> + bool IAuthenticationRequest.IsDirectedIdentity { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets a value indicating whether the requesting Relying Party is using a delegated URL. + /// </summary> + /// <remarks> + /// When delegated identifiers are used, the <see cref="IAuthenticationRequest.ClaimedIdentifier"/> should not + /// be changed at the Provider during authentication. + /// Delegation is only detectable on requests originating from OpenID 2.0 relying parties. + /// A relying party implementing only OpenID 1.x may use delegation and this property will + /// return false anyway. + /// </remarks> + bool IAuthenticationRequest.IsDelegatedIdentifier { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets or sets the Local Identifier to this OpenID Provider of the user attempting + /// to authenticate. Check <see cref="IAuthenticationRequest.IsDirectedIdentity"/> to see if + /// this value is valid. + /// </summary> + /// <remarks> + /// This may or may not be the same as the Claimed Identifier that the user agent + /// originally supplied to the relying party. The Claimed Identifier + /// endpoint may be delegating authentication to this provider using + /// this provider's local id, which is what this property contains. + /// Use this identifier when looking up this user in the provider's user account + /// list. + /// </remarks> + Identifier IAuthenticationRequest.LocalIdentifier { + get { + throw new NotImplementedException(); + } + + set { + throw new NotImplementedException(); + } + } + + /// <summary> + /// Gets or sets the identifier that the user agent is claiming at the relying party site. + /// Check <see cref="IAuthenticationRequest.IsDirectedIdentity"/> to see if this value is valid. + /// </summary> + /// <remarks> + /// <para>This property can only be set if <see cref="IAuthenticationRequest.IsDelegatedIdentifier"/> is + /// false, to prevent breaking URL delegation.</para> + /// <para>This will not be the same as this provider's local identifier for the user + /// if the user has set up his/her own identity page that points to this + /// provider for authentication.</para> + /// <para>The provider may use this identifier for displaying to the user when + /// asking for the user's permission to authenticate to the relying party.</para> + /// </remarks> + /// <exception cref="InvalidOperationException">Thrown from the setter + /// if <see cref="IAuthenticationRequest.IsDelegatedIdentifier"/> is true.</exception> + Identifier IAuthenticationRequest.ClaimedIdentifier { + get { + throw new NotImplementedException(); + } + + set { + IAuthenticationRequest req = this; + Contract.Requires<InvalidOperationException>(!req.IsDelegatedIdentifier, OpenIdStrings.ClaimedIdentifierCannotBeSetOnDelegatedAuthentication); + Contract.Requires<InvalidOperationException>(!req.IsDirectedIdentity || !(req.LocalIdentifier != null && req.LocalIdentifier != value), OpenIdStrings.IdentifierSelectRequiresMatchingIdentifiers); + } + } + + /// <summary> + /// Gets or sets a value indicating whether the provider has determined that the + /// <see cref="IAuthenticationRequest.ClaimedIdentifier"/> belongs to the currently logged in user + /// and wishes to share this information with the consumer. + /// </summary> + bool? IAuthenticationRequest.IsAuthenticated { + get { + throw new NotImplementedException(); + } + + set { + throw new NotImplementedException(); + } + } + + #endregion + + #region IHostProcessedRequest Properties + + /// <summary> + /// Gets the version of OpenID being used by the relying party that sent the request. + /// </summary> + ProtocolVersion IHostProcessedRequest.RelyingPartyVersion { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets the URL the consumer site claims to use as its 'base' address. + /// </summary> + Realm IHostProcessedRequest.Realm { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets a value indicating whether the consumer demands an immediate response. + /// If false, the consumer is willing to wait for the identity provider + /// to authenticate the user. + /// </summary> + bool IHostProcessedRequest.Immediate { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets or sets the provider endpoint claimed in the positive assertion. + /// </summary> + /// <value> + /// The default value is the URL that the request came in on from the relying party. + /// This value MUST match the value for the OP Endpoint in the discovery results for the + /// claimed identifier being asserted in a positive response. + /// </value> + Uri IHostProcessedRequest.ProviderEndpoint { + get { + throw new NotImplementedException(); + } + + set { + throw new NotImplementedException(); + } + } + + #endregion + + #region IRequest Properties + + /// <summary> + /// Gets a value indicating whether the response is ready to be sent to the user agent. + /// </summary> + /// <remarks> + /// This property returns false if there are properties that must be set on this + /// request instance before the response can be sent. + /// </remarks> + bool IRequest.IsResponseReady { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets or sets the security settings that apply to this request. + /// </summary> + /// <value> + /// Defaults to the <see cref="OpenIdProvider.SecuritySettings"/> on the <see cref="OpenIdProvider"/>. + /// </value> + ProviderSecuritySettings IRequest.SecuritySettings { + get { + throw new NotImplementedException(); + } + + set { + throw new NotImplementedException(); + } + } + + #endregion + + #region IAuthenticationRequest Methods + + /// <summary> + /// Adds an optional fragment (#fragment) portion to the ClaimedIdentifier. + /// Useful for identifier recycling. + /// </summary> + /// <param name="fragment">Should not include the # prefix character as that will be added internally. + /// May be null or the empty string to clear a previously set fragment.</param> + /// <remarks> + /// <para>Unlike the <see cref="IAuthenticationRequest.ClaimedIdentifier"/> property, which can only be set if + /// using directed identity, this method can be called on any URI claimed identifier.</para> + /// <para>Because XRI claimed identifiers (the canonical IDs) are never recycled, + /// this method should<i>not</i> be called for XRIs.</para> + /// </remarks> + /// <exception cref="InvalidOperationException"> + /// Thrown when this method is called on an XRI, or on a directed identity + /// request before the <see cref="IAuthenticationRequest.ClaimedIdentifier"/> property is set. + /// </exception> + void IAuthenticationRequest.SetClaimedIdentifierFragment(string fragment) { + Contract.Requires<InvalidOperationException>(!(((IAuthenticationRequest)this).IsDirectedIdentity && ((IAuthenticationRequest)this).ClaimedIdentifier == null), OpenIdStrings.ClaimedIdentifierMustBeSetFirst); + Contract.Requires<InvalidOperationException>(!(((IAuthenticationRequest)this).ClaimedIdentifier is XriIdentifier), OpenIdStrings.FragmentNotAllowedOnXRIs); + + throw new NotImplementedException(); + } + + #endregion + + #region IHostProcessedRequest Methods + + /// <summary> + /// Attempts to perform relying party discovery of the return URL claimed by the Relying Party. + /// </summary> + /// <param name="provider">The OpenIdProvider that is performing the RP discovery.</param> + /// <returns> + /// The details of how successful the relying party discovery was. + /// </returns> + /// <remarks> + /// <para>Return URL verification is only attempted if this method is called.</para> + /// <para>See OpenID Authentication 2.0 spec section 9.2.1.</para> + /// </remarks> + RelyingPartyDiscoveryResult IHostProcessedRequest.IsReturnUrlDiscoverable(OpenIdProvider provider) { + throw new NotImplementedException(); + } + + #endregion + + #region IRequest Methods + + /// <summary> + /// Adds an extension to the response to send to the relying party. + /// </summary> + /// <param name="extension">The extension to add to the response message.</param> + void IRequest.AddResponseExtension(DotNetOpenAuth.OpenId.Messages.IOpenIdMessageExtension extension) { + throw new NotImplementedException(); + } + + /// <summary> + /// Gets an extension sent from the relying party. + /// </summary> + /// <typeparam name="T">The type of the extension.</typeparam> + /// <returns> + /// An instance of the extension initialized with values passed in with the request. + /// </returns> + T IRequest.GetExtension<T>() { + throw new NotImplementedException(); + } + + /// <summary> + /// Gets an extension sent from the relying party. + /// </summary> + /// <param name="extensionType">The type of the extension.</param> + /// <returns> + /// An instance of the extension initialized with values passed in with the request. + /// </returns> + DotNetOpenAuth.OpenId.Messages.IOpenIdMessageExtension IRequest.GetExtension(Type extensionType) { + throw new NotImplementedException(); + } + + #endregion + } } diff --git a/src/DotNetOpenAuth/OpenId/Provider/IDirectedIdentityIdentifierProvider.cs b/src/DotNetOpenAuth/OpenId/Provider/IDirectedIdentityIdentifierProvider.cs index 00a3267..985bb54 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/IDirectedIdentityIdentifierProvider.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/IDirectedIdentityIdentifierProvider.cs @@ -37,6 +37,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <returns> /// <c>true</c> if the given identifier is the valid, unique identifier for some uesr (and NOT a PPID); otherwise, <c>false</c>. /// </returns> + [Pure] bool IsUserLocalIdentifier(Identifier identifier); } @@ -58,9 +59,9 @@ namespace DotNetOpenAuth.OpenId.Provider { /// openid.claimed_id and openid.local_id parameters. Must not be null. /// </returns> Uri IDirectedIdentityIdentifierProvider.GetIdentifier(Identifier localIdentifier, Realm relyingPartyRealm) { - Contract.Requires(localIdentifier != null); - Contract.Requires(relyingPartyRealm != null); - + Contract.Requires<ArgumentNullException>(localIdentifier != null); + Contract.Requires<ArgumentNullException>(relyingPartyRealm != null); + Contract.Requires<ArgumentException>(((IDirectedIdentityIdentifierProvider)this).IsUserLocalIdentifier(localIdentifier), OpenIdStrings.ArgumentIsPpidIdentifier); throw new NotImplementedException(); } @@ -72,7 +73,8 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <c>true</c> if the given identifier is the valid, unique identifier for some uesr (and NOT a PPID); otherwise, <c>false</c>. /// </returns> bool IDirectedIdentityIdentifierProvider.IsUserLocalIdentifier(Identifier identifier) { - Contract.Requires(identifier != null); + Contract.Requires<ArgumentNullException>(identifier != null); + throw new NotImplementedException(); } diff --git a/src/DotNetOpenAuth/OpenId/Provider/IHostProcessedRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/IHostProcessedRequest.cs index 345ba52..be809bd 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/IHostProcessedRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/IHostProcessedRequest.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.OpenId.Provider { using System; using System.Diagnostics.Contracts; using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; /// <summary> /// Interface exposing incoming messages to the OpenID Provider that @@ -57,10 +58,16 @@ namespace DotNetOpenAuth.OpenId.Provider { } /// <summary> - /// Contract class for the <see cref="IHostProcessedRequest"/> type. + /// Code contract for the <see cref="IHostProcessedRequest"/> type. /// </summary> [ContractClassFor(typeof(IHostProcessedRequest))] internal abstract class IHostProcessedRequestContract : IHostProcessedRequest { + /// <summary> + /// Initializes a new instance of the <see cref="IHostProcessedRequestContract"/> class. + /// </summary> + protected IHostProcessedRequestContract() { + } + #region IHostProcessedRequest Properties /// <summary> @@ -176,7 +183,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <para>See OpenID Authentication 2.0 spec section 9.2.1.</para> /// </remarks> RelyingPartyDiscoveryResult IHostProcessedRequest.IsReturnUrlDiscoverable(OpenIdProvider provider) { - Contract.Requires(provider != null); + Contract.Requires<ArgumentNullException>(provider != null); throw new System.NotImplementedException(); } diff --git a/src/DotNetOpenAuth/OpenId/Provider/IProviderBehavior.cs b/src/DotNetOpenAuth/OpenId/Provider/IProviderBehavior.cs index 4e3dc99..01b4ac8 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/IProviderBehavior.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/IProviderBehavior.cs @@ -5,11 +5,14 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Diagnostics.Contracts; using DotNetOpenAuth.OpenId.ChannelElements; /// <summary> /// Applies a custom security policy to certain OpenID security settings and behaviors. /// </summary> + [ContractClass(typeof(IProviderBehaviorContract))] public interface IProviderBehavior { /// <summary> /// Applies a well known set of security requirements to a default set of security settings. @@ -47,4 +50,65 @@ namespace DotNetOpenAuth.OpenId.Provider { /// </returns> bool OnOutgoingResponse(IAuthenticationRequest request); } + + /// <summary> + /// Code contract for the <see cref="IProviderBehavior"/> type. + /// </summary> + [ContractClassFor(typeof(IProviderBehavior))] + internal abstract class IProviderBehaviorContract : IProviderBehavior { + /// <summary> + /// Initializes a new instance of the <see cref="IProviderBehaviorContract"/> class. + /// </summary> + protected IProviderBehaviorContract() { + } + + #region IProviderBehavior Members + + /// <summary> + /// Applies a well known set of security requirements to a default set of security settings. + /// </summary> + /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param> + /// <remarks> + /// Care should be taken to never decrease security when applying a profile. + /// Profiles should only enhance security requirements to avoid being + /// incompatible with each other. + /// </remarks> + void IProviderBehavior.ApplySecuritySettings(ProviderSecuritySettings securitySettings) { + Contract.Requires<ArgumentNullException>(securitySettings != null); + throw new System.NotImplementedException(); + } + + /// <summary> + /// Called when a request is received by the Provider. + /// </summary> + /// <param name="request">The incoming request.</param> + /// <returns> + /// <c>true</c> if this behavior owns this request and wants to stop other behaviors + /// from handling it; <c>false</c> to allow other behaviors to process this request. + /// </returns> + /// <remarks> + /// Implementations may set a new value to <see cref="IRequest.SecuritySettings"/> but + /// should not change the properties on the instance of <see cref="ProviderSecuritySettings"/> + /// itself as that instance may be shared across many requests. + /// </remarks> + bool IProviderBehavior.OnIncomingRequest(IRequest request) { + Contract.Requires<ArgumentNullException>(request != null); + throw new System.NotImplementedException(); + } + + /// <summary> + /// Called when the Provider is preparing to send a response to an authentication request. + /// </summary> + /// <param name="request">The request that is configured to generate the outgoing response.</param> + /// <returns> + /// <c>true</c> if this behavior owns this request and wants to stop other behaviors + /// from handling it; <c>false</c> to allow other behaviors to process this request. + /// </returns> + bool IProviderBehavior.OnOutgoingResponse(IAuthenticationRequest request) { + Contract.Requires<ArgumentNullException>(request != null); + throw new System.NotImplementedException(); + } + + #endregion + } } diff --git a/src/DotNetOpenAuth/OpenId/Provider/IRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/IRequest.cs index 2ca96ca..0fcdc28 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/IRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/IRequest.cs @@ -64,7 +64,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// Code contract for the <see cref="IRequest"/> interface. /// </summary> [ContractClassFor(typeof(IRequest))] - internal class IRequestContract : IRequest { + internal abstract class IRequestContract : IRequest { /// <summary> /// Prevents a default instance of the <see cref="IRequestContract"/> class from being created. /// </summary> @@ -87,7 +87,6 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <summary> /// Gets a value indicating whether the response is ready to be sent to the user agent. /// </summary> - /// <value></value> /// <remarks> /// This property returns false if there are properties that must be set on this /// request instance before the response can be sent. @@ -101,7 +100,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// </summary> /// <param name="extension">The extension to add to the response message.</param> void IRequest.AddResponseExtension(IOpenIdMessageExtension extension) { - Contract.Requires(extension != null); + Contract.Requires<ArgumentNullException>(extension != null); throw new NotImplementedException(); } @@ -124,7 +123,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// An instance of the extension initialized with values passed in with the request. /// </returns> IOpenIdMessageExtension IRequest.GetExtension(Type extensionType) { - Contract.Requires(extensionType != null); + Contract.Requires<ArgumentNullException>(extensionType != null); throw new NotImplementedException(); } diff --git a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs index ae044aa..3eb24d4 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs @@ -58,7 +58,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <param name="applicationStore">The application store to use. Cannot be null.</param> public OpenIdProvider(IProviderApplicationStore applicationStore) : this(applicationStore, applicationStore) { - Contract.Requires(applicationStore != null); + Contract.Requires<ArgumentNullException>(applicationStore != null); Contract.Ensures(this.AssociationStore == applicationStore); Contract.Ensures(this.SecuritySettings != null); Contract.Ensures(this.Channel != null); @@ -70,13 +70,11 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <param name="associationStore">The association store to use. Cannot be null.</param> /// <param name="nonceStore">The nonce store to use. Cannot be null.</param> private OpenIdProvider(IAssociationStore<AssociationRelyingPartyType> associationStore, INonceStore nonceStore) { - Contract.Requires(associationStore != null); - Contract.Requires(nonceStore != null); + Contract.Requires<ArgumentNullException>(associationStore != null); + Contract.Requires<ArgumentNullException>(nonceStore != null); Contract.Ensures(this.AssociationStore == associationStore); Contract.Ensures(this.SecuritySettings != null); Contract.Ensures(this.Channel != null); - ErrorUtilities.VerifyArgumentNotNull(associationStore, "associationStore"); - ErrorUtilities.VerifyArgumentNotNull(nonceStore, "nonceStore"); this.AssociationStore = associationStore; this.SecuritySettings = DotNetOpenAuthSection.Configuration.OpenId.Provider.SecuritySettings.CreateSecuritySettings(); @@ -95,8 +93,8 @@ namespace DotNetOpenAuth.OpenId.Provider { [EditorBrowsable(EditorBrowsableState.Advanced)] public static IProviderApplicationStore HttpApplicationStore { get { + Contract.Requires<InvalidOperationException>(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); Contract.Ensures(Contract.Result<IProviderApplicationStore>() != null); - ErrorUtilities.VerifyHttpContext(); HttpContext context = HttpContext.Current; var store = (IProviderApplicationStore)context.Application[ApplicationStoreKey]; if (store == null) { @@ -130,8 +128,7 @@ namespace DotNetOpenAuth.OpenId.Provider { } internal set { - Contract.Requires(value != null); - ErrorUtilities.VerifyArgumentNotNull(value, "value"); + Contract.Requires<ArgumentNullException>(value != null); this.securitySettings = value; } } @@ -205,8 +202,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <exception cref="ProtocolException">Thrown if the incoming message is recognized /// but deviates from the protocol specification irrecoverably.</exception> public IRequest GetRequest(HttpRequestInfo httpRequestInfo) { - Contract.Requires(httpRequestInfo != null); - ErrorUtilities.VerifyArgumentNotNull(httpRequestInfo, "httpRequestInfo"); + Contract.Requires<ArgumentNullException>(httpRequestInfo != null); IDirectedProtocolMessage incomingMessage = null; try { @@ -285,10 +281,9 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <exception cref="InvalidOperationException">Thrown if <see cref="IRequest.IsResponseReady"/> is <c>false</c>.</exception> [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Code Contract requires that we cast early.")] public void SendResponse(IRequest request) { - Contract.Requires(HttpContext.Current != null); - Contract.Requires(request != null); - Contract.Requires(((Request)request).IsResponseReady); - ErrorUtilities.VerifyArgumentNotNull(request, "request"); + Contract.Requires<InvalidOperationException>(HttpContext.Current != null, MessagingStrings.CurrentHttpContextRequired); + Contract.Requires<ArgumentNullException>(request != null); + Contract.Requires<ArgumentException>(((Request)request).IsResponseReady); this.ApplyBehaviorsToResponse(request); Request requestInternal = (Request)request; @@ -303,9 +298,8 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <exception cref="InvalidOperationException">Thrown if <see cref="IRequest.IsResponseReady"/> is <c>false</c>.</exception> [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Code Contract requires that we cast early.")] public OutgoingWebResponse PrepareResponse(IRequest request) { - Contract.Requires(request != null); - Contract.Requires(((Request)request).IsResponseReady); - ErrorUtilities.VerifyArgumentNotNull(request, "request"); + Contract.Requires<ArgumentNullException>(request != null); + Contract.Requires<ArgumentException>(((Request)request).IsResponseReady); this.ApplyBehaviorsToResponse(request); Request requestInternal = (Request)request; @@ -328,12 +322,12 @@ namespace DotNetOpenAuth.OpenId.Provider { /// be the same as <paramref name="claimedIdentifier"/>.</param> /// <param name="extensions">The extensions.</param> public void SendUnsolicitedAssertion(Uri providerEndpoint, Realm relyingParty, Identifier claimedIdentifier, Identifier localIdentifier, params IExtensionMessage[] extensions) { - Contract.Requires(HttpContext.Current != null); - Contract.Requires(providerEndpoint != null); - Contract.Requires(providerEndpoint.IsAbsoluteUri); - Contract.Requires(relyingParty != null); - Contract.Requires(claimedIdentifier != null); - Contract.Requires(localIdentifier != null); + Contract.Requires<InvalidOperationException>(HttpContext.Current != null, MessagingStrings.HttpContextRequired); + Contract.Requires<ArgumentNullException>(providerEndpoint != null); + Contract.Requires<ArgumentException>(providerEndpoint.IsAbsoluteUri); + Contract.Requires<ArgumentNullException>(relyingParty != null); + Contract.Requires<ArgumentNullException>(claimedIdentifier != null); + Contract.Requires<ArgumentNullException>(localIdentifier != null); this.PrepareUnsolicitedAssertion(providerEndpoint, relyingParty, claimedIdentifier, localIdentifier, extensions).Send(); } @@ -358,16 +352,12 @@ namespace DotNetOpenAuth.OpenId.Provider { /// the user agent to allow the redirect with assertion to happen. /// </returns> public OutgoingWebResponse PrepareUnsolicitedAssertion(Uri providerEndpoint, Realm relyingParty, Identifier claimedIdentifier, Identifier localIdentifier, params IExtensionMessage[] extensions) { - Contract.Requires(providerEndpoint != null); - Contract.Requires(providerEndpoint.IsAbsoluteUri); - Contract.Requires(relyingParty != null); - Contract.Requires(claimedIdentifier != null); - Contract.Requires(localIdentifier != null); - ErrorUtilities.VerifyArgumentNotNull(providerEndpoint, "providerEndpoint"); - ErrorUtilities.VerifyArgumentNotNull(relyingParty, "relyingParty"); - ErrorUtilities.VerifyArgumentNotNull(claimedIdentifier, "claimedIdentifier"); - ErrorUtilities.VerifyArgumentNotNull(localIdentifier, "localIdentifier"); - ErrorUtilities.VerifyArgumentNamed(providerEndpoint.IsAbsoluteUri, "providerEndpoint", OpenIdStrings.AbsoluteUriRequired); + Contract.Requires<ArgumentNullException>(providerEndpoint != null); + Contract.Requires<ArgumentException>(providerEndpoint.IsAbsoluteUri); + Contract.Requires<ArgumentNullException>(relyingParty != null); + Contract.Requires<ArgumentNullException>(claimedIdentifier != null); + Contract.Requires<ArgumentNullException>(localIdentifier != null); + Contract.Requires<InvalidOperationException>(this.Channel.WebRequestHandler != null); // Although the RP should do their due diligence to make sure that this OP // is authorized to send an assertion for the given claimed identifier, @@ -466,10 +456,8 @@ namespace DotNetOpenAuth.OpenId.Provider { /// Either the <see cref="IRequest"/> to return to the host site or null to indicate no response could be reasonably created and that the caller should rethrow the exception. /// </returns> private IRequest GetErrorResponse(ProtocolException ex, HttpRequestInfo httpRequestInfo, IDirectedProtocolMessage incomingMessage) { - Contract.Requires(ex != null); - Contract.Requires(httpRequestInfo != null); - ErrorUtilities.VerifyArgumentNotNull(ex, "ex"); - ErrorUtilities.VerifyArgumentNotNull(httpRequestInfo, "httpRequestInfo"); + Contract.Requires<ArgumentNullException>(ex != null); + Contract.Requires<ArgumentNullException>(httpRequestInfo != null); Logger.OpenId.Error("An exception was generated while processing an incoming OpenID request.", ex); IErrorMessage errorMessage; @@ -500,7 +488,7 @@ namespace DotNetOpenAuth.OpenId.Provider { return null; } - errorMessage.ErrorMessage = ex.GetAllMessages(); + errorMessage.ErrorMessage = ex.ToStringDescriptive(); // Allow host to log this error and issue a ticket #. // We tear off the field to a local var for thread safety. diff --git a/src/DotNetOpenAuth/OpenId/Provider/PrivatePersonalIdentifierProviderBase.cs b/src/DotNetOpenAuth/OpenId/Provider/PrivatePersonalIdentifierProviderBase.cs index 399a84f..260fc85 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/PrivatePersonalIdentifierProviderBase.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/PrivatePersonalIdentifierProviderBase.cs @@ -35,8 +35,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// </summary> /// <param name="baseIdentifier">The base URI on which to append the anonymous part.</param> protected PrivatePersonalIdentifierProviderBase(Uri baseIdentifier) { - Contract.Requires(baseIdentifier != null); - ErrorUtilities.VerifyArgumentNotNull(baseIdentifier, "baseIdentifier"); + Contract.Requires<ArgumentNullException>(baseIdentifier != null); this.Hasher = HashAlgorithm.Create(HashAlgorithmName); this.Encoder = Encoding.UTF8; @@ -103,8 +102,7 @@ namespace DotNetOpenAuth.OpenId.Provider { } set { - Contract.Requires(value > 0); - ErrorUtilities.VerifyArgumentInRange(value > 0, "value"); + Contract.Requires<ArgumentOutOfRangeException>(value > 0); this.newSaltLength = value; } } @@ -122,10 +120,6 @@ namespace DotNetOpenAuth.OpenId.Provider { /// openid.claimed_id and openid.local_id parameters. Must not be null. /// </returns> public Uri GetIdentifier(Identifier localIdentifier, Realm relyingPartyRealm) { - ErrorUtilities.VerifyArgumentNotNull(localIdentifier, "localIdentifier"); - ErrorUtilities.VerifyArgumentNotNull(relyingPartyRealm, "relyingPartyRealm"); - ErrorUtilities.VerifyArgumentNamed(this.IsUserLocalIdentifier(localIdentifier), "localIdentifier", OpenIdStrings.ArgumentIsPpidIdentifier); - byte[] salt = this.GetHashSaltForLocalIdentifier(localIdentifier); string valueToHash = localIdentifier + "#"; switch (this.PairwiseUnique) { @@ -187,8 +181,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <returns>The full PPID Identifier.</returns> [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "NOT equivalent overload. The recommended one breaks on relative URIs.")] protected virtual Uri AppendIdentifiers(string uriHash) { - Contract.Requires(!String.IsNullOrEmpty(uriHash)); - ErrorUtilities.VerifyNonZeroLength(uriHash, "uriHash"); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(uriHash)); if (string.IsNullOrEmpty(this.BaseIdentifier.Query)) { // The uriHash will appear on the path itself. diff --git a/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs b/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs index 49d18e0..f778b76 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/ProviderEndpoint.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.OpenId.Provider { using System; using System.Collections.Generic; using System.ComponentModel; + using System.Diagnostics.Contracts; using System.Text; using System.Web; using System.Web.UI; @@ -67,7 +68,7 @@ namespace DotNetOpenAuth.OpenId.Provider { } set { - ErrorUtilities.VerifyArgumentNotNull(value, "value"); + Contract.Requires<ArgumentNullException>(value != null); provider = value; } } diff --git a/src/DotNetOpenAuth/OpenId/Provider/ProviderSecuritySettings.cs b/src/DotNetOpenAuth/OpenId/Provider/ProviderSecuritySettings.cs index 9590033..d5fa4a9 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/ProviderSecuritySettings.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/ProviderSecuritySettings.cs @@ -9,6 +9,7 @@ namespace DotNetOpenAuth.OpenId.Provider { using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; + using System.Diagnostics.CodeAnalysis; using System.Linq; using DotNetOpenAuth.Messaging; @@ -51,6 +52,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// The behavior a Provider takes when verifying that it is authoritative for an /// identifier it is about to send an unsolicited assertion for. /// </summary> + [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "By design")] public enum UnsolicitedAssertionVerificationLevel { /// <summary> /// Always verify that the Provider is authoritative for an identifier before diff --git a/src/DotNetOpenAuth/OpenId/Provider/Request.cs b/src/DotNetOpenAuth/OpenId/Provider/Request.cs index 43697b2..1c3eb86 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/Request.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/Request.cs @@ -54,10 +54,8 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <param name="request">The incoming request message.</param> /// <param name="securitySettings">The security settings from the channel.</param> protected Request(IDirectedProtocolMessage request, ProviderSecuritySettings securitySettings) { - Contract.Requires(request != null); - Contract.Requires(securitySettings != null); - ErrorUtilities.VerifyArgumentNotNull(request, "request"); - ErrorUtilities.VerifyArgumentNotNull(securitySettings, "securitySettings"); + Contract.Requires<ArgumentNullException>(request != null); + Contract.Requires<ArgumentNullException>(securitySettings != null); this.request = request; this.SecuritySettings = securitySettings; @@ -71,10 +69,8 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <param name="version">The version.</param> /// <param name="securitySettings">The security settings.</param> protected Request(Version version, ProviderSecuritySettings securitySettings) { - Contract.Requires(version != null); - Contract.Requires(securitySettings != null); - ErrorUtilities.VerifyArgumentNotNull(version, "version"); - ErrorUtilities.VerifyArgumentNotNull(securitySettings, "securitySettings"); + Contract.Requires<ArgumentNullException>(version != null); + Contract.Requires<ArgumentNullException>(securitySettings != null); this.protocolVersion = version; this.SecuritySettings = securitySettings; @@ -103,10 +99,9 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <exception cref="InvalidOperationException">Thrown if <see cref="IsResponseReady"/> is <c>false</c>.</exception> internal IProtocolMessage Response { get { - Contract.Requires(this.IsResponseReady); + Contract.Requires<InvalidOperationException>(this.IsResponseReady, OpenIdStrings.ResponseNotReady); Contract.Ensures(Contract.Result<IProtocolMessage>() != null); - ErrorUtilities.VerifyOperation(this.IsResponseReady, OpenIdStrings.ResponseNotReady); if (this.responseExtensions.Count > 0) { var extensibleResponse = this.ResponseMessage as IProtocolMessageWithExtensions; ErrorUtilities.VerifyOperation(extensibleResponse != null, MessagingStrings.MessageNotExtensible, this.ResponseMessage.GetType().Name); @@ -161,8 +156,6 @@ namespace DotNetOpenAuth.OpenId.Provider { /// </summary> /// <param name="extension">The extension to add to the response message.</param> public void AddResponseExtension(IOpenIdMessageExtension extension) { - ErrorUtilities.VerifyArgumentNotNull(extension, "extension"); - // Because the derived AuthenticationRequest class can swap out // one response message for another (auth vs. no-auth), and because // some response messages support extensions while others don't, @@ -194,7 +187,6 @@ namespace DotNetOpenAuth.OpenId.Provider { /// An instance of the extension initialized with values passed in with the request. /// </returns> public IOpenIdMessageExtension GetExtension(Type extensionType) { - ErrorUtilities.VerifyArgumentNotNull(extensionType, "extensionType"); if (this.extensibleMessage != null) { return this.extensibleMessage.Extensions.OfType<IOpenIdMessageExtension>().Where(ext => extensionType.IsInstanceOfType(ext)).SingleOrDefault(); } else { diff --git a/src/DotNetOpenAuth/OpenId/Provider/RequestContract.cs b/src/DotNetOpenAuth/OpenId/Provider/RequestContract.cs index b94b37d..dee140e 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/RequestContract.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/RequestContract.cs @@ -39,7 +39,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// </summary> protected override IProtocolMessage ResponseMessage { get { - Contract.Requires(this.IsResponseReady); + Contract.Requires<InvalidOperationException>(this.IsResponseReady); Contract.Ensures(Contract.Result<IProtocolMessage>() != null); throw new NotImplementedException(); } diff --git a/src/DotNetOpenAuth/OpenId/ProviderEndpointDescription.cs b/src/DotNetOpenAuth/OpenId/ProviderEndpointDescription.cs index 0751f51..4cfbac5 100644 --- a/src/DotNetOpenAuth/OpenId/ProviderEndpointDescription.cs +++ b/src/DotNetOpenAuth/OpenId/ProviderEndpointDescription.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.OpenId { using System; using System.Collections.Generic; using System.Collections.ObjectModel; + using System.Diagnostics.Contracts; using System.Linq; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.Messages; @@ -27,8 +28,8 @@ namespace DotNetOpenAuth.OpenId { /// <param name="providerEndpoint">The OpenID Provider endpoint URL.</param> /// <param name="openIdVersion">The OpenID version supported by this particular endpoint.</param> internal ProviderEndpointDescription(Uri providerEndpoint, Version openIdVersion) { - ErrorUtilities.VerifyArgumentNotNull(providerEndpoint, "providerEndpoint"); - ErrorUtilities.VerifyArgumentNotNull(openIdVersion, "version"); + Contract.Requires<ArgumentNullException>(providerEndpoint != null); + Contract.Requires<ArgumentNullException>(openIdVersion != null); this.Endpoint = providerEndpoint; this.ProtocolVersion = openIdVersion; @@ -40,8 +41,8 @@ namespace DotNetOpenAuth.OpenId { /// <param name="providerEndpoint">The URI the provider listens on for OpenID requests.</param> /// <param name="serviceTypeURIs">The set of services offered by this endpoint.</param> internal ProviderEndpointDescription(Uri providerEndpoint, IEnumerable<string> serviceTypeURIs) { - ErrorUtilities.VerifyArgumentNotNull(providerEndpoint, "providerEndpoint"); - ErrorUtilities.VerifyArgumentNotNull(serviceTypeURIs, "serviceTypeURIs"); + Contract.Requires<ArgumentNullException>(providerEndpoint != null); + Contract.Requires<ArgumentNullException>(serviceTypeURIs != null); this.Endpoint = providerEndpoint; this.Capabilities = new ReadOnlyCollection<string>(serviceTypeURIs.ToList()); @@ -111,6 +112,7 @@ namespace DotNetOpenAuth.OpenId { /// the extension in the request and see if a response comes back for that extension. /// </remarks> public bool IsExtensionSupported<T>() where T : IOpenIdMessageExtension, new() { + ErrorUtilities.VerifyOperation(this.Capabilities != null, OpenIdStrings.ExtensionLookupSupportUnavailable); T extension = new T(); return this.IsExtensionSupported(extension); } @@ -130,8 +132,7 @@ namespace DotNetOpenAuth.OpenId { /// the extension in the request and see if a response comes back for that extension. /// </remarks> public bool IsExtensionSupported(Type extensionType) { - ErrorUtilities.VerifyArgumentNotNull(extensionType, "extensionType"); - ErrorUtilities.VerifyArgument(typeof(IOpenIdMessageExtension).IsAssignableFrom(extensionType), OpenIdStrings.TypeMustImplementX, typeof(IOpenIdMessageExtension).FullName); + ErrorUtilities.VerifyOperation(this.Capabilities != null, OpenIdStrings.ExtensionLookupSupportUnavailable); var extension = (IOpenIdMessageExtension)Activator.CreateInstance(extensionType); return this.IsExtensionSupported(extension); } @@ -146,8 +147,8 @@ namespace DotNetOpenAuth.OpenId { /// <c>true</c> if the extension is supported; otherwise, <c>false</c>. /// </returns> protected internal bool IsExtensionSupported(string extensionUri) { - ErrorUtilities.VerifyNonZeroLength(extensionUri, "extensionUri"); - ErrorUtilities.VerifyOperation(this.Capabilities != null, OpenIdStrings.ExtensionLookupSupportUnavailable); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(extensionUri)); + Contract.Requires<InvalidOperationException>(this.Capabilities != null, OpenIdStrings.ExtensionLookupSupportUnavailable); return this.Capabilities.Contains(extensionUri); } @@ -159,7 +160,9 @@ namespace DotNetOpenAuth.OpenId { /// <c>true</c> if the extension is supported by this endpoint; otherwise, <c>false</c>. /// </returns> protected internal bool IsExtensionSupported(IOpenIdMessageExtension extension) { - ErrorUtilities.VerifyArgumentNotNull(extension, "extension"); + Contract.Requires<ArgumentNullException>(extension != null); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(extension.TypeUri)); + Contract.Requires<InvalidOperationException>(this.Capabilities != null, OpenIdStrings.ExtensionLookupSupportUnavailable); // Consider the primary case. if (this.IsExtensionSupported(extension.TypeUri)) { diff --git a/src/DotNetOpenAuth/OpenId/Realm.cs b/src/DotNetOpenAuth/OpenId/Realm.cs index 2859cf0..fb0fbfb 100644 --- a/src/DotNetOpenAuth/OpenId/Realm.cs +++ b/src/DotNetOpenAuth/OpenId/Realm.cs @@ -62,7 +62,7 @@ namespace DotNetOpenAuth.OpenId { /// <param name="realmUrl">The realm URL to use in the new instance.</param> [SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", Justification = "Not all realms are valid URLs (because of wildcards).")] public Realm(string realmUrl) { - ErrorUtilities.VerifyArgumentNotNull(realmUrl, "realmUrl"); // not non-zero check so we throw UriFormatException later + Contract.Requires<ArgumentNullException>(realmUrl != null); // not non-zero check so we throw UriFormatException later this.DomainWildcard = Regex.IsMatch(realmUrl, WildcardDetectionPattern); this.uri = new Uri(Regex.Replace(realmUrl, WildcardDetectionPattern, m => m.Groups[1].Value)); if (!this.uri.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase) && @@ -77,7 +77,7 @@ namespace DotNetOpenAuth.OpenId { /// </summary> /// <param name="realmUrl">The realm URL of the Relying Party.</param> public Realm(Uri realmUrl) { - ErrorUtilities.VerifyArgumentNotNull(realmUrl, "realmUrl"); + Contract.Requires<ArgumentNullException>(realmUrl != null); this.uri = realmUrl; if (!this.uri.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase) && !this.uri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase)) { @@ -418,6 +418,18 @@ namespace DotNetOpenAuth.OpenId { return null; } +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + protected void ObjectInvariant() { + Contract.Invariant(this.uri != null); + Contract.Invariant(this.uri.AbsoluteUri != null); + } +#endif + /// <summary> /// Calls <see cref="UriBuilder.ToString"/> if the argument is non-null. /// Otherwise throws <see cref="ArgumentNullException"/>. @@ -431,24 +443,12 @@ namespace DotNetOpenAuth.OpenId { /// when we should be throwing an <see cref="ArgumentNullException"/>. /// </remarks> private static string SafeUriBuilderToString(UriBuilder realmUriBuilder) { - ErrorUtilities.VerifyArgumentNotNull(realmUriBuilder, "realmUriBuilder"); + Contract.Requires<ArgumentNullException>(realmUriBuilder != null); // Note: we MUST use ToString. Uri property throws if wildcard is present. // Note that Uri.ToString() should generally be avoided, but UriBuilder.ToString() // is safe: http://blog.nerdbank.net/2008/04/uriabsoluteuri-and-uritostring-are-not.html return realmUriBuilder.ToString(); } - -#if CONTRACTS_FULL - /// <summary> - /// Verifies conditions that should be true for any valid state of this object. - /// </summary> - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] - [ContractInvariantMethod] - private void ObjectInvariant() { - Contract.Invariant(this.uri != null); - Contract.Invariant(this.uri.AbsoluteUri != null); - } -#endif } } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/AssociationManager.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/AssociationManager.cs index 85c0096..d3e0686 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/AssociationManager.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/AssociationManager.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.Linq; using System.Net; using System.Text; @@ -40,8 +41,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <param name="associationStore">The association store. May be null for dumb mode relying parties.</param> /// <param name="securitySettings">The security settings.</param> internal AssociationManager(Channel channel, IAssociationStore<Uri> associationStore, RelyingPartySecuritySettings securitySettings) { - ErrorUtilities.VerifyArgumentNotNull(channel, "channel"); - ErrorUtilities.VerifyArgumentNotNull(securitySettings, "securitySettings"); + Contract.Requires<ArgumentNullException>(channel != null); + Contract.Requires<ArgumentNullException>(securitySettings != null); this.channel = channel; this.associationStore = associationStore; @@ -58,7 +59,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } set { - ErrorUtilities.VerifyArgumentNotNull(value, "value"); + Contract.Requires<ArgumentNullException>(value != null); this.channel = value; } } @@ -72,7 +73,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } set { - ErrorUtilities.VerifyArgumentNotNull(value, "value"); + Contract.Requires<ArgumentNullException>(value != null); this.securitySettings = value; } } @@ -95,7 +96,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <param name="provider">The provider to create an association with.</param> /// <returns>The association if one exists and has useful life remaining. Otherwise <c>null</c>.</returns> internal Association GetExistingAssociation(ProviderEndpointDescription provider) { - ErrorUtilities.VerifyArgumentNotNull(provider, "provider"); + Contract.Requires<ArgumentNullException>(provider != null); // If the RP has no application store for associations, there's no point in creating one. if (this.associationStore == null) { @@ -140,7 +141,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// Any new association is automatically added to the <see cref="associationStore"/>. /// </remarks> private Association CreateNewAssociation(ProviderEndpointDescription provider) { - ErrorUtilities.VerifyArgumentNotNull(provider, "provider"); + Contract.Requires<ArgumentNullException>(provider != null); // If there is no association store, there is no point in creating an association. if (this.associationStore == null) { @@ -164,7 +165,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// the given Provider given the current security settings. /// </returns> private Association CreateNewAssociation(ProviderEndpointDescription provider, AssociateRequest associateRequest, int retriesRemaining) { - ErrorUtilities.VerifyArgumentNotNull(provider, "provider"); + Contract.Requires<ArgumentNullException>(provider != null); if (associateRequest == null || retriesRemaining < 0) { // this can happen if security requirements and protocol conflict diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs index cea7d21..f1851a0 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs @@ -55,6 +55,16 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { private Dictionary<string, string> returnToArgs = new Dictionary<string, string>(); /// <summary> + /// A value indicating whether the return_to callback arguments must be signed. + /// </summary> + /// <remarks> + /// This field defaults to false, but is set to true as soon as the first callback argument + /// is added that indicates it must be signed. At which point, all arguments are signed + /// even if individual ones did not need to be. + /// </remarks> + private bool returnToArgsMustBeSigned; + + /// <summary> /// Initializes a new instance of the <see cref="AuthenticationRequest"/> class. /// </summary> /// <param name="endpoint">The endpoint that describes the OpenID Identifier and Provider that will complete the authentication.</param> @@ -62,10 +72,10 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <param name="returnToUrl">The base return_to URL that the Provider should return the user to to complete authentication. This should not include callback parameters as these should be added using the <see cref="AddCallbackArguments(string, string)"/> method.</param> /// <param name="relyingParty">The relying party that created this instance.</param> private AuthenticationRequest(ServiceEndpoint endpoint, Realm realm, Uri returnToUrl, OpenIdRelyingParty relyingParty) { - ErrorUtilities.VerifyArgumentNotNull(endpoint, "endpoint"); - ErrorUtilities.VerifyArgumentNotNull(realm, "realm"); - ErrorUtilities.VerifyArgumentNotNull(returnToUrl, "returnToUrl"); - ErrorUtilities.VerifyArgumentNotNull(relyingParty, "relyingParty"); + Contract.Requires<ArgumentNullException>(endpoint != null); + Contract.Requires<ArgumentNullException>(realm != null); + Contract.Requires<ArgumentNullException>(returnToUrl != null); + Contract.Requires<ArgumentNullException>(relyingParty != null); this.endpoint = endpoint; this.RelyingParty = relyingParty; @@ -182,6 +192,13 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { get { return this.extensions; } } + /// <summary> + /// Gets the service endpoint. + /// </summary> + internal ServiceEndpoint Endpoint { + get { return this.endpoint; } + } + #region IAuthenticationRequest methods /// <summary> @@ -199,8 +216,9 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> /// </remarks> public void AddCallbackArguments(IDictionary<string, string> arguments) { - ErrorUtilities.VerifyArgumentNotNull(arguments, "arguments"); + ErrorUtilities.VerifyOperation(this.RelyingParty.CanSignCallbackArguments, OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IAssociationStore<Uri>).Name, typeof(OpenIdRelyingParty).Name); + this.returnToArgsMustBeSigned = true; foreach (var pair in arguments) { ErrorUtilities.VerifyArgument(!string.IsNullOrEmpty(pair.Key), MessagingStrings.UnexpectedNullOrEmptyKey); ErrorUtilities.VerifyArgument(pair.Value != null, MessagingStrings.UnexpectedNullValue, pair.Key); @@ -225,18 +243,57 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> /// </remarks> public void AddCallbackArguments(string key, string value) { - ErrorUtilities.VerifyNonZeroLength(key, "key"); - ErrorUtilities.VerifyArgumentNotNull(value, "value"); + ErrorUtilities.VerifyOperation(this.RelyingParty.CanSignCallbackArguments, OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IAssociationStore<Uri>).Name, typeof(OpenIdRelyingParty).Name); + this.returnToArgsMustBeSigned = true; this.returnToArgs.Add(key, value); } /// <summary> + /// Makes a key/value pair available when the authentication is completed. + /// </summary> + /// <param name="key">The parameter name.</param> + /// <param name="value">The value of the argument. Must not be null.</param> + /// <remarks> + /// <para>Note that these values are NOT protected against tampering in transit. No + /// security-sensitive data should be stored using this method.</para> + /// <para>The value stored here can be retrieved using + /// <see cref="IAuthenticationResponse.GetCallbackArgument"/>.</para> + /// <para>Since the data set here is sent in the querystring of the request and some + /// servers place limits on the size of a request URL, this data should be kept relatively + /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> + /// </remarks> + public void SetCallbackArgument(string key, string value) { + ErrorUtilities.VerifyOperation(this.RelyingParty.CanSignCallbackArguments, OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IAssociationStore<Uri>).Name, typeof(OpenIdRelyingParty).Name); + + this.returnToArgsMustBeSigned = true; + this.returnToArgs[key] = value; + } + + /// <summary> + /// Makes a key/value pair available when the authentication is completed without + /// requiring a return_to signature to protect against tampering of the callback argument. + /// </summary> + /// <param name="key">The parameter name.</param> + /// <param name="value">The value of the argument. Must not be null.</param> + /// <remarks> + /// <para>Note that these values are NOT protected against eavesdropping or tampering in transit. No + /// security-sensitive data should be stored using this method. </para> + /// <para>The value stored here can be retrieved using + /// <see cref="IAuthenticationResponse.GetCallbackArgument"/>.</para> + /// <para>Since the data set here is sent in the querystring of the request and some + /// servers place limits on the size of a request URL, this data should be kept relatively + /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> + /// </remarks> + public void SetUntrustedCallbackArgument(string key, string value) { + this.returnToArgs[key] = value; + } + + /// <summary> /// Adds an OpenID extension to the request directed at the OpenID provider. /// </summary> /// <param name="extension">The initialized extension to add to the request.</param> public void AddExtension(IOpenIdMessageExtension extension) { - ErrorUtilities.VerifyArgumentNotNull(extension, "extension"); this.extensions.Add(extension); } @@ -267,16 +324,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// Never null, but may be empty. /// </returns> internal static IEnumerable<AuthenticationRequest> Create(Identifier userSuppliedIdentifier, OpenIdRelyingParty relyingParty, Realm realm, Uri returnToUrl, bool createNewAssociationsAsNeeded) { - Contract.Requires(userSuppliedIdentifier != null); - Contract.Requires(relyingParty != null); - Contract.Requires(realm != null); + Contract.Requires<ArgumentNullException>(userSuppliedIdentifier != null); + Contract.Requires<ArgumentNullException>(relyingParty != null); + Contract.Requires<ArgumentNullException>(realm != null); Contract.Ensures(Contract.Result<IEnumerable<AuthenticationRequest>>() != null); - // We have a long data validation and preparation process - ErrorUtilities.VerifyArgumentNotNull(userSuppliedIdentifier, "userSuppliedIdentifier"); - ErrorUtilities.VerifyArgumentNotNull(relyingParty, "relyingParty"); - ErrorUtilities.VerifyArgumentNotNull(realm, "realm"); - // Normalize the portion of the return_to path that correlates to the realm for capitalization. // (so that if a web app base path is /MyApp/, but the URL of this request happens to be // /myapp/login.aspx, we bump up the return_to Url to use /MyApp/ so it matches the realm. @@ -345,11 +397,12 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// before calling this method. /// </remarks> private static IEnumerable<AuthenticationRequest> CreateInternal(Identifier userSuppliedIdentifier, OpenIdRelyingParty relyingParty, Realm realm, Uri returnToUrl, IEnumerable<ServiceEndpoint> serviceEndpoints, bool createNewAssociationsAsNeeded) { - Contract.Requires(userSuppliedIdentifier != null); - Contract.Requires(relyingParty != null); - Contract.Requires(realm != null); - Contract.Requires(serviceEndpoints != null); - Contract.Ensures(Contract.Result<IEnumerable<AuthenticationRequest>>() != null); + // DO NOT USE CODE CONTRACTS IN THIS METHOD, since it uses yield return + ErrorUtilities.VerifyArgumentNotNull(userSuppliedIdentifier, "userSuppliedIdentifier"); + ErrorUtilities.VerifyArgumentNotNull(relyingParty, "relyingParty"); + ErrorUtilities.VerifyArgumentNotNull(realm, "realm"); + ErrorUtilities.VerifyArgumentNotNull(serviceEndpoints, "serviceEndpoints"); + ////Contract.Ensures(Contract.Result<IEnumerable<AuthenticationRequest>>() != null); // If shared associations are required, then we had better have an association store. ErrorUtilities.VerifyOperation(!relyingParty.SecuritySettings.RequireAssociation || relyingParty.AssociationManager.HasAssociationStore, OpenIdStrings.AssociationStoreRequired); @@ -415,8 +468,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <param name="relyingParty">The relying party.</param> /// <returns>A filtered and sorted list of endpoints; may be empty if the input was empty or the filter removed all endpoints.</returns> private static List<ServiceEndpoint> FilterAndSortEndpoints(IEnumerable<ServiceEndpoint> endpoints, OpenIdRelyingParty relyingParty) { - ErrorUtilities.VerifyArgumentNotNull(endpoints, "endpoints"); - ErrorUtilities.VerifyArgumentNotNull(relyingParty, "relyingParty"); + Contract.Requires<ArgumentNullException>(endpoints != null); + Contract.Requires<ArgumentNullException>(relyingParty != null); // Construct the endpoints filters based on criteria given by the host web site. EndpointSelector versionFilter = ep => ((ServiceEndpoint)ep).Protocol.Version >= Protocol.Lookup(relyingParty.SecuritySettings.MinimumRequiredOpenIdVersion).Version; @@ -475,9 +528,10 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { request.Realm = this.Realm; request.ReturnTo = this.ReturnToUrl; request.AssociationHandle = association != null ? association.Handle : null; + request.SignReturnTo = this.returnToArgsMustBeSigned; request.AddReturnToArguments(this.returnToArgs); if (this.endpoint.UserSuppliedIdentifier != null) { - request.AddReturnToArguments(UserSuppliedIdentifierParameterName, this.endpoint.UserSuppliedIdentifier); + request.AddReturnToArguments(UserSuppliedIdentifierParameterName, this.endpoint.UserSuppliedIdentifier.OriginalString); } foreach (IOpenIdMessageExtension extension in this.extensions) { request.Extensions.Add(extension); diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/Controls.cd b/src/DotNetOpenAuth/OpenId/RelyingParty/Controls.cd new file mode 100644 index 0000000..f96db36 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/Controls.cd @@ -0,0 +1,112 @@ +<?xml version="1.0" encoding="utf-8"?> +<ClassDiagram MajorVersion="1" MinorVersion="1"> + <Class Name="DotNetOpenAuth.OpenId.RelyingParty.OpenIdRelyingPartyAjaxControlBase"> + <Position X="0.5" Y="9.75" Width="3" /> + <Compartments> + <Compartment Name="Fields" Collapsed="true" /> + </Compartments> + <TypeIdentifier> + <HashCode>BARAAAAAAAAAAACQAAAAAAAEAAAgAAAAAQAFAAAAAFk=</HashCode> + <FileName>OpenId\RelyingParty\OpenIdRelyingPartyAjaxControlBase.cs</FileName> + </TypeIdentifier> + <Lollipop Position="0.2" /> + </Class> + <Class Name="DotNetOpenAuth.OpenId.RelyingParty.OpenIdMobileTextBox"> + <Position X="8.5" Y="5.25" Width="2.5" /> + <Compartments> + <Compartment Name="Fields" Collapsed="true" /> + </Compartments> + <TypeIdentifier> + <HashCode>AI0JADgFQRQQDAIw4lAYSEIWCAMZhMVlELAASQIAgSI=</HashCode> + <FileName>OpenId\RelyingParty\OpenIdMobileTextBox.cs</FileName> + </TypeIdentifier> + </Class> + <Class Name="DotNetOpenAuth.OpenId.RelyingParty.OpenIdLogin"> + <Position X="6.25" Y="1.25" Width="1.75" /> + <Compartments> + <Compartment Name="Fields" Collapsed="true" /> + </Compartments> + <NestedTypes> + <Class Name="DotNetOpenAuth.OpenId.RelyingParty.OpenIdLogin.InPlaceControl" Collapsed="true"> + <TypeIdentifier> + <NewMemberFileName>OpenId\RelyingParty\OpenIdLogin.cs</NewMemberFileName> + </TypeIdentifier> + </Class> + </NestedTypes> + <TypeIdentifier> + <HashCode>gIMgADAIAQEQIJAYOQBSADiQBgiIECk0jQCggdAp4BQ=</HashCode> + <FileName>OpenId\RelyingParty\OpenIdLogin.cs</FileName> + </TypeIdentifier> + </Class> + <Class Name="DotNetOpenAuth.OpenId.RelyingParty.OpenIdAjaxTextBox"> + <Position X="3.75" Y="10" Width="2.25" /> + <Compartments> + <Compartment Name="Fields" Collapsed="true" /> + </Compartments> + <TypeIdentifier> + <HashCode>ACBEEbABZzOzAKCYJNOEwM3uSIR5AAOkUFANCQ7DsVs=</HashCode> + <FileName>OpenId\RelyingParty\OpenIdAjaxTextBox.cs</FileName> + </TypeIdentifier> + <Lollipop Position="0.2" /> + </Class> + <Class Name="DotNetOpenAuth.OpenId.RelyingParty.OpenIdButton"> + <Position X="8.75" Y="1" Width="1.75" /> + <Compartments> + <Compartment Name="Fields" Collapsed="true" /> + </Compartments> + <InheritanceLine Type="DotNetOpenAuth.OpenId.RelyingParty.OpenIdRelyingPartyControlBase" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true"> + <Path> + <Point X="2.875" Y="0.5" /> + <Point X="7.194" Y="0.5" /> + <Point X="7.194" Y="1" /> + <Point X="8.75" Y="1" /> + </Path> + </InheritanceLine> + <TypeIdentifier> + <HashCode>BAAEQAAAAAAAAAACAAAgAAAAAIAAAACQABAECABAAAA=</HashCode> + <FileName>OpenId\RelyingParty\OpenIdButton.cs</FileName> + </TypeIdentifier> + <Lollipop Position="0.2" /> + </Class> + <Class Name="DotNetOpenAuth.OpenId.RelyingParty.OpenIdTextBox"> + <Position X="3.5" Y="1.25" Width="2.25" /> + <Compartments> + <Compartment Name="Fields" Collapsed="true" /> + </Compartments> + <TypeIdentifier> + <HashCode>AIEVQjgBIxYITIARcAAACEc2CIAIlER1CBAQSQoEpCg=</HashCode> + <FileName>OpenId\RelyingParty\OpenIdTextBox.cs</FileName> + </TypeIdentifier> + <Lollipop Position="0.2" /> + </Class> + <Class Name="DotNetOpenAuth.OpenId.RelyingParty.OpenIdRelyingPartyControlBase"> + <Position X="0.5" Y="0.5" Width="2.5" /> + <Compartments> + <Compartment Name="Fields" Collapsed="true" /> + </Compartments> + <NestedTypes> + <Enum Name="DotNetOpenAuth.OpenId.RelyingParty.OpenIdRelyingPartyControlBase.LoginSiteNotification" Collapsed="true"> + <TypeIdentifier> + <NewMemberFileName>OpenId\RelyingParty\OpenIdRelyingPartyControlBase.cs</NewMemberFileName> + </TypeIdentifier> + </Enum> + <Enum Name="DotNetOpenAuth.OpenId.RelyingParty.OpenIdRelyingPartyControlBase.LoginPersistence" Collapsed="true"> + <TypeIdentifier> + <NewMemberFileName>OpenId\RelyingParty\OpenIdRelyingPartyControlBase.cs</NewMemberFileName> + </TypeIdentifier> + </Enum> + <Class Name="DotNetOpenAuth.OpenId.RelyingParty.OpenIdRelyingPartyControlBase.DuplicateRequestedHostsComparer" Collapsed="true"> + <TypeIdentifier> + <NewMemberFileName>OpenId\RelyingParty\OpenIdRelyingPartyControlBase.cs</NewMemberFileName> + </TypeIdentifier> + <Lollipop Position="0.2" /> + </Class> + </NestedTypes> + <TypeIdentifier> + <HashCode>BA0AAsAAQCAwQAJAoFAWwADSAgE5EIEEEbAGSAwAgfI=</HashCode> + <FileName>OpenId\RelyingParty\OpenIdRelyingPartyControlBase.cs</FileName> + </TypeIdentifier> + <Lollipop Position="0.2" /> + </Class> + <Font Name="Segoe UI" Size="9" /> +</ClassDiagram>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/FailedAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/FailedAuthenticationResponse.cs index 45f7f54..f899f03 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/FailedAuthenticationResponse.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/FailedAuthenticationResponse.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { using System; using System.Collections.Generic; using System.Diagnostics; + using System.Diagnostics.Contracts; using System.Globalization; using System.Text; using System.Web; @@ -27,7 +28,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> /// <param name="exception">The exception that resulted in the failed authentication.</param> internal FailedAuthenticationResponse(Exception exception) { - ErrorUtilities.VerifyArgumentNotNull(exception, "exception"); + Contract.Requires<ArgumentNullException>(exception != null); this.Exception = exception; } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequest.cs index c97654a..3808c85 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequest.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequest.cs @@ -7,6 +7,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; using System.Text; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.Messages; @@ -16,6 +18,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// requests that may be queried/modified in specific ways before being /// routed to the OpenID Provider. /// </summary> + [ContractClass(typeof(IAuthenticationRequestContract))] public interface IAuthenticationRequest { /// <summary> /// Gets or sets the mode the Provider should use during authentication. @@ -127,6 +130,39 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { void AddCallbackArguments(string key, string value); /// <summary> + /// Makes a key/value pair available when the authentication is completed. + /// </summary> + /// <param name="key">The parameter name.</param> + /// <param name="value">The value of the argument. Must not be null.</param> + /// <remarks> + /// <para>Note that these values are NOT protected against eavesdropping in transit. No + /// security-sensitive data should be stored using this method.</para> + /// <para>The value stored here can be retrieved using + /// <see cref="IAuthenticationResponse.GetCallbackArgument"/>.</para> + /// <para>Since the data set here is sent in the querystring of the request and some + /// servers place limits on the size of a request URL, this data should be kept relatively + /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> + /// </remarks> + void SetCallbackArgument(string key, string value); + + /// <summary> + /// Makes a key/value pair available when the authentication is completed without + /// requiring a return_to signature to protect against tampering of the callback argument. + /// </summary> + /// <param name="key">The parameter name.</param> + /// <param name="value">The value of the argument. Must not be null.</param> + /// <remarks> + /// <para>Note that these values are NOT protected against eavesdropping or tampering in transit. No + /// security-sensitive data should be stored using this method. </para> + /// <para>The value stored here can be retrieved using + /// <see cref="IAuthenticationResponse.GetCallbackArgument"/>.</para> + /// <para>Since the data set here is sent in the querystring of the request and some + /// servers place limits on the size of a request URL, this data should be kept relatively + /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> + /// </remarks> + void SetUntrustedCallbackArgument(string key, string value); + + /// <summary> /// Adds an OpenID extension to the request directed at the OpenID provider. /// </summary> /// <param name="extension">The initialized extension to add to the request.</param> diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequestContract.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequestContract.cs new file mode 100644 index 0000000..41cc4e9 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequestContract.cs @@ -0,0 +1,96 @@ +// <auto-generated /> + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Messages; + + [ContractClassFor(typeof(IAuthenticationRequest))] + internal abstract class IAuthenticationRequestContract : IAuthenticationRequest { + #region IAuthenticationRequest Members + + AuthenticationRequestMode IAuthenticationRequest.Mode { + get { + throw new NotImplementedException(); + } + + set { + throw new NotImplementedException(); + } + } + + OutgoingWebResponse IAuthenticationRequest.RedirectingResponse { + get { throw new NotImplementedException(); } + } + + Uri IAuthenticationRequest.ReturnToUrl { + get { throw new NotImplementedException(); } + } + + Realm IAuthenticationRequest.Realm { + get { throw new NotImplementedException(); } + } + + Identifier IAuthenticationRequest.ClaimedIdentifier { + get { throw new NotImplementedException(); } + } + + bool IAuthenticationRequest.IsDirectedIdentity { + get { throw new NotImplementedException(); } + } + + bool IAuthenticationRequest.IsExtensionOnly { + get { + throw new NotImplementedException(); + } + + set { + throw new NotImplementedException(); + } + } + + IProviderEndpoint IAuthenticationRequest.Provider { + get { throw new NotImplementedException(); } + } + + void IAuthenticationRequest.AddCallbackArguments(IDictionary<string, string> arguments) { + Contract.Requires<ArgumentNullException>(arguments != null); + Contract.Requires<ArgumentException>(arguments.Keys.All(k => !String.IsNullOrEmpty(k))); + Contract.Requires<ArgumentException>(arguments.Values.All(v => v != null)); + throw new NotImplementedException(); + } + + void IAuthenticationRequest.AddCallbackArguments(string key, string value) { + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(key)); + Contract.Requires<ArgumentNullException>(value != null); + throw new NotImplementedException(); + } + + void IAuthenticationRequest.SetCallbackArgument(string key, string value) { + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(key)); + Contract.Requires<ArgumentNullException>(value != null); + throw new NotImplementedException(); + } + + void IAuthenticationRequest.AddExtension(IOpenIdMessageExtension extension) { + Contract.Requires<ArgumentNullException>(extension != null); + throw new NotImplementedException(); + } + + void IAuthenticationRequest.RedirectToProvider() { + throw new NotImplementedException(); + } + + void IAuthenticationRequest.SetUntrustedCallbackArgument(string key, string value) { + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(key)); + Contract.Requires<ArgumentNullException>(value != null); + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationResponse.cs index fd35a6b..a24220f 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationResponse.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationResponse.cs @@ -8,6 +8,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; using System.Text; using System.Web; using DotNetOpenAuth.OpenId.Extensions; @@ -24,6 +26,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// assertions. This interface does not offer a way to discern between /// solicited and unsolicited assertions as they should be treated equally. /// </remarks> + [ContractClass(typeof(IAuthenticationResponseContract))] public interface IAuthenticationResponse { /// <summary> /// Gets the Identifier that the end user claims to own. For use with user database storage and lookup. @@ -251,4 +254,279 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </remarks> IOpenIdMessageExtension GetUntrustedExtension(Type extensionType); } + + /// <summary> + /// Code contract for the <see cref="IAuthenticationResponse"/> type. + /// </summary> + [ContractClassFor(typeof(IAuthenticationResponse))] + internal abstract class IAuthenticationResponseContract : IAuthenticationResponse { + /// <summary> + /// Initializes a new instance of the <see cref="IAuthenticationResponseContract"/> class. + /// </summary> + protected IAuthenticationResponseContract() { + } + + #region IAuthenticationResponse Members + + /// <summary> + /// Gets the Identifier that the end user claims to own. For use with user database storage and lookup. + /// May be null for some failed authentications (i.e. failed directed identity authentications). + /// </summary> + /// <value></value> + /// <remarks> + /// <para> + /// This is the secure identifier that should be used for database storage and lookup. + /// It is not always friendly (i.e. =Arnott becomes =!9B72.7DD1.50A9.5CCD), but it protects + /// user identities against spoofing and other attacks. + /// </para> + /// <para> + /// For user-friendly identifiers to display, use the + /// <see cref="IAuthenticationResponse.FriendlyIdentifierForDisplay"/> property. + /// </para> + /// </remarks> + Identifier IAuthenticationResponse.ClaimedIdentifier { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets a user-friendly OpenID Identifier for display purposes ONLY. + /// </summary> + /// <value></value> + /// <remarks> + /// <para> + /// This <i>should</i> be put through <see cref="HttpUtility.HtmlEncode(string)"/> before + /// sending to a browser to secure against javascript injection attacks. + /// </para> + /// <para> + /// This property retains some aspects of the user-supplied identifier that get lost + /// in the <see cref="IAuthenticationResponse.ClaimedIdentifier"/>. For example, XRIs used as user-supplied + /// identifiers (i.e. =Arnott) become unfriendly unique strings (i.e. =!9B72.7DD1.50A9.5CCD). + /// For display purposes, such as text on a web page that says "You're logged in as ...", + /// this property serves to provide the =Arnott string, or whatever else is the most friendly + /// string close to what the user originally typed in. + /// </para> + /// <para> + /// If the user-supplied identifier is a URI, this property will be the URI after all + /// redirects, and with the protocol and fragment trimmed off. + /// If the user-supplied identifier is an XRI, this property will be the original XRI. + /// If the user-supplied identifier is an OpenID Provider identifier (i.e. yahoo.com), + /// this property will be the Claimed Identifier, with the protocol stripped if it is a URI. + /// </para> + /// <para> + /// It is <b>very</b> important that this property <i>never</i> be used for database storage + /// or lookup to avoid identity spoofing and other security risks. For database storage + /// and lookup please use the <see cref="IAuthenticationResponse.ClaimedIdentifier"/> property. + /// </para> + /// </remarks> + string IAuthenticationResponse.FriendlyIdentifierForDisplay { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets the detailed success or failure status of the authentication attempt. + /// </summary> + /// <value></value> + AuthenticationStatus IAuthenticationResponse.Status { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets information about the OpenId Provider, as advertised by the + /// OpenID discovery documents found at the <see cref="IAuthenticationResponse.ClaimedIdentifier"/> + /// location, if available. + /// </summary> + /// <value> + /// The Provider endpoint that issued the positive assertion; + /// or <c>null</c> if information about the Provider is unavailable. + /// </value> + IProviderEndpoint IAuthenticationResponse.Provider { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets the details regarding a failed authentication attempt, if available. + /// This will be set if and only if <see cref="IAuthenticationResponse.Status"/> is <see cref="AuthenticationStatus.Failed"/>. + /// </summary> + /// <value></value> + Exception IAuthenticationResponse.Exception { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets a callback argument's value that was previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. + /// </summary> + /// <param name="key">The name of the parameter whose value is sought.</param> + /// <returns> + /// The value of the argument, or null if the named parameter could not be found. + /// </returns> + /// <remarks> + /// <para>This may return any argument on the querystring that came with the authentication response, + /// which may include parameters not explicitly added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.</para> + /// <para>Note that these values are NOT protected against tampering in transit.</para> + /// </remarks> + string IAuthenticationResponse.GetCallbackArgument(string key) { + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(key)); + throw new NotImplementedException(); + } + + /// <summary> + /// Gets all the callback arguments that were previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part + /// of the return_to URL. + /// </summary> + /// <returns>A name-value dictionary. Never null.</returns> + /// <remarks> + /// <para>This MAY return any argument on the querystring that came with the authentication response, + /// which may include parameters not explicitly added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.</para> + /// <para>Note that these values are NOT protected against tampering in transit.</para> + /// </remarks> + IDictionary<string, string> IAuthenticationResponse.GetCallbackArguments() { + throw new NotImplementedException(); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response. + /// </summary> + /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="IAuthenticationResponse.GetUntrustedExtension<T>"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + T IAuthenticationResponse.GetExtension<T>() { + throw new NotImplementedException(); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response. + /// </summary> + /// <param name="extensionType">Type of the extension to look for in the response.</param> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned only if the Provider signed them. + /// Relying parties that do not care if the values were modified in + /// transit should use the <see cref="IAuthenticationResponse.GetUntrustedExtension"/> method + /// in order to allow the Provider to not sign the extension. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + IOpenIdMessageExtension IAuthenticationResponse.GetExtension(Type extensionType) { + Contract.Requires<ArgumentNullException>(extensionType != null); + Contract.Requires<ArgumentException>(typeof(IOpenIdMessageExtension).IsAssignableFrom(extensionType)); + ////ErrorUtilities.VerifyArgument(typeof(IOpenIdMessageExtension).IsAssignableFrom(extensionType), string.Format(CultureInfo.CurrentCulture, OpenIdStrings.TypeMustImplementX, typeof(IOpenIdMessageExtension).FullName)); + throw new NotImplementedException(); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response, without + /// requiring it to be signed by the Provider. + /// </summary> + /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="IAuthenticationResponse.GetExtension<T>"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + T IAuthenticationResponse.GetUntrustedExtension<T>() { + throw new NotImplementedException(); + } + + /// <summary> + /// Tries to get an OpenID extension that may be present in the response, without + /// requiring it to be signed by the Provider. + /// </summary> + /// <param name="extensionType">Type of the extension to look for in the response.</param> + /// <returns> + /// The extension, if it is found. Null otherwise. + /// </returns> + /// <remarks> + /// <para>Extensions are returned whether they are signed or not. + /// Use the <see cref="IAuthenticationResponse.GetExtension"/> method to retrieve + /// extension responses only if they are signed by the Provider to + /// protect against tampering. </para> + /// <para>Unsigned extensions are completely unreliable and should be + /// used only to prefill user forms since the user or any other third + /// party may have tampered with the data carried by the extension.</para> + /// <para>Signed extensions are only reliable if the relying party + /// trusts the OpenID Provider that signed them. Signing does not mean + /// the relying party can trust the values -- it only means that the values + /// have not been tampered with since the Provider sent the message.</para> + /// </remarks> + IOpenIdMessageExtension IAuthenticationResponse.GetUntrustedExtension(Type extensionType) { + Contract.Requires<ArgumentNullException>(extensionType != null); + Contract.Requires<ArgumentException>(typeof(IOpenIdMessageExtension).IsAssignableFrom(extensionType)); + ////ErrorUtilities.VerifyArgument(typeof(IOpenIdMessageExtension).IsAssignableFrom(extensionType), string.Format(CultureInfo.CurrentCulture, OpenIdStrings.TypeMustImplementX, typeof(IOpenIdMessageExtension).FullName)); + throw new NotImplementedException(); + } + + /// <summary> + /// Gets a callback argument's value that was previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. + /// </summary> + /// <param name="key">The name of the parameter whose value is sought.</param> + /// <returns> + /// The value of the argument, or null if the named parameter could not be found. + /// </returns> + /// <remarks> + /// Callback parameters are only available even if the RP is in stateless mode, + /// or the callback parameters are otherwise unverifiable as untampered with. + /// Therefore, use this method only when the callback argument is not to be + /// used to make a security-sensitive decision. + /// </remarks> + string IAuthenticationResponse.GetUntrustedCallbackArgument(string key) { + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(key)); + throw new NotImplementedException(); + } + + /// <summary> + /// Gets all the callback arguments that were previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part + /// of the return_to URL. + /// </summary> + /// <returns>A name-value dictionary. Never null.</returns> + /// <remarks> + /// Callback parameters are only available even if the RP is in stateless mode, + /// or the callback parameters are otherwise unverifiable as untampered with. + /// Therefore, use this method only when the callback argument is not to be + /// used to make a security-sensitive decision. + /// </remarks> + IDictionary<string, string> IAuthenticationResponse.GetUntrustedCallbackArguments() { + Contract.Ensures(Contract.Result<IDictionary<string, string>>() != null); + throw new NotImplementedException(); + } + + #endregion + } } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IProviderEndpoint.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IProviderEndpoint.cs index ed9d65a..a90ddd4 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/IProviderEndpoint.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/IProviderEndpoint.cs @@ -7,6 +7,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { using System; using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.Messages; @@ -18,6 +20,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// Because information provided by this interface is suppplied by a /// user's individually published documents, it may be incomplete or inaccurate. /// </remarks> + [ContractClass(typeof(IProviderEndpointContract))] public interface IProviderEndpoint { /// <summary> /// Gets the detected version of OpenID implemented by the Provider. @@ -58,4 +61,67 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </remarks> bool IsExtensionSupported(Type extensionType); } + + /// <summary> + /// Code contract for the <see cref="IProviderEndpoint"/> type. + /// </summary> + [ContractClassFor(typeof(IProviderEndpoint))] + internal abstract class IProviderEndpointContract : IProviderEndpoint { + #region IProviderEndpoint Members + + /// <summary> + /// Gets the detected version of OpenID implemented by the Provider. + /// </summary> + Version IProviderEndpoint.Version { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Gets the URL that the OpenID Provider receives authentication requests at. + /// </summary> + Uri IProviderEndpoint.Uri { + get { throw new NotImplementedException(); } + } + + /// <summary> + /// Checks whether the OpenId Identifier claims support for a given extension. + /// </summary> + /// <typeparam name="T">The extension whose support is being queried.</typeparam> + /// <returns> + /// True if support for the extension is advertised. False otherwise. + /// </returns> + /// <remarks> + /// Note that a true or false return value is no guarantee of a Provider's + /// support for or lack of support for an extension. The return value is + /// determined by how the authenticating user filled out his/her XRDS document only. + /// The only way to be sure of support for a given extension is to include + /// the extension in the request and see if a response comes back for that extension. + /// </remarks> + bool IProviderEndpoint.IsExtensionSupported<T>() { + throw new NotImplementedException(); + } + + /// <summary> + /// Checks whether the OpenId Identifier claims support for a given extension. + /// </summary> + /// <param name="extensionType">The extension whose support is being queried.</param> + /// <returns> + /// True if support for the extension is advertised. False otherwise. + /// </returns> + /// <remarks> + /// Note that a true or false return value is no guarantee of a Provider's + /// support for or lack of support for an extension. The return value is + /// determined by how the authenticating user filled out his/her XRDS document only. + /// The only way to be sure of support for a given extension is to include + /// the extension in the request and see if a response comes back for that extension. + /// </remarks> + bool IProviderEndpoint.IsExtensionSupported(Type extensionType) { + Contract.Requires<ArgumentNullException>(extensionType != null); + Contract.Requires<ArgumentException>(typeof(IOpenIdMessageExtension).IsAssignableFrom(extensionType)); + ////ErrorUtilities.VerifyArgument(typeof(IOpenIdMessageExtension).IsAssignableFrom(extensionType), string.Format(CultureInfo.CurrentCulture, OpenIdStrings.TypeMustImplementX, typeof(IOpenIdMessageExtension).FullName)); + throw new NotImplementedException(); + } + + #endregion + } } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyBehavior.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyBehavior.cs index d0be768..300a15f 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyBehavior.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyBehavior.cs @@ -5,9 +5,13 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Diagnostics.Contracts; + /// <summary> /// Applies a custom security policy to certain OpenID security settings and behaviors. /// </summary> + [ContractClass(typeof(IRelyingPartyBehaviorContract))] public interface IRelyingPartyBehavior { /// <summary> /// Applies a well known set of security requirements to a default set of security settings. @@ -36,4 +40,53 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <param name="assertion">The positive assertion.</param> void OnIncomingPositiveAssertion(IAuthenticationResponse assertion); } + + /// <summary> + /// Contract class for the <see cref="IRelyingPartyBehavior"/> interface. + /// </summary> + [ContractClassFor(typeof(IRelyingPartyBehavior))] + internal class IRelyingPartyBehaviorContract : IRelyingPartyBehavior { + /// <summary> + /// Initializes a new instance of the <see cref="IRelyingPartyBehaviorContract"/> class. + /// </summary> + protected IRelyingPartyBehaviorContract() { + } + + #region IRelyingPartyBehavior Members + + /// <summary> + /// Applies a well known set of security requirements to a default set of security settings. + /// </summary> + /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param> + /// <remarks> + /// Care should be taken to never decrease security when applying a profile. + /// Profiles should only enhance security requirements to avoid being + /// incompatible with each other. + /// </remarks> + void IRelyingPartyBehavior.ApplySecuritySettings(RelyingPartySecuritySettings securitySettings) { + Contract.Requires<ArgumentNullException>(securitySettings != null); + } + + /// <summary> + /// Called when an authentication request is about to be sent. + /// </summary> + /// <param name="request">The request.</param> + /// <remarks> + /// Implementations should be prepared to be called multiple times on the same outgoing message + /// without malfunctioning. + /// </remarks> + void IRelyingPartyBehavior.OnOutgoingAuthenticationRequest(IAuthenticationRequest request) { + Contract.Requires<ArgumentNullException>(request != null); + } + + /// <summary> + /// Called when an incoming positive assertion is received. + /// </summary> + /// <param name="assertion">The positive assertion.</param> + void IRelyingPartyBehavior.OnIncomingPositiveAssertion(IAuthenticationResponse assertion) { + Contract.Requires<ArgumentNullException>(assertion != null); + } + + #endregion + } } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/ISetupRequiredAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/ISetupRequiredAuthenticationResponse.cs index e4f220f..cfbccef 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/ISetupRequiredAuthenticationResponse.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/ISetupRequiredAuthenticationResponse.cs @@ -5,11 +5,15 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Diagnostics.Contracts; + /// <summary> /// An interface to expose useful properties and functionality for handling /// authentication responses that are returned from Immediate authentication /// requests that require a subsequent request to be made in non-immediate mode. /// </summary> + [ContractClass(typeof(ISetupRequiredAuthenticationResponseContract))] public interface ISetupRequiredAuthenticationResponse { /// <summary> /// Gets the <see cref="Identifier"/> to pass to <see cref="OpenIdRelyingParty.CreateRequest(Identifier)"/> @@ -17,4 +21,31 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> Identifier UserSuppliedIdentifier { get; } } + + /// <summary> + /// Code contract class for the <see cref="ISetupRequiredAuthenticationResponse"/> type. + /// </summary> + [ContractClassFor(typeof(ISetupRequiredAuthenticationResponse))] + internal abstract class ISetupRequiredAuthenticationResponseContract : ISetupRequiredAuthenticationResponse { + /// <summary> + /// Initializes a new instance of the <see cref="ISetupRequiredAuthenticationResponseContract"/> class. + /// </summary> + protected ISetupRequiredAuthenticationResponseContract() { + } + + #region ISetupRequiredAuthenticationResponse Members + + /// <summary> + /// Gets the <see cref="Identifier"/> to pass to <see cref="OpenIdRelyingParty.CreateRequest(Identifier)"/> + /// in a subsequent authentication attempt. + /// </summary> + Identifier ISetupRequiredAuthenticationResponse.UserSuppliedIdentifier { + get { + Contract.Requires<InvalidOperationException>(((IAuthenticationResponse)this).Status == AuthenticationStatus.SetupRequired, OpenIdStrings.OperationOnlyValidForSetupRequiredState); + throw new System.NotImplementedException(); + } + } + + #endregion + } } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IXrdsProviderEndpoint.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IXrdsProviderEndpoint.cs index ebd8518..89b4ef0 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/IXrdsProviderEndpoint.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/IXrdsProviderEndpoint.cs @@ -5,13 +5,16 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; /// <summary> /// An <see cref="IProviderEndpoint"/> interface with additional members for use /// in sorting for most preferred endpoint. /// </summary> [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Xrds", Justification = "Xrds is an acronym.")] + [ContractClass(typeof(IXrdsProviderEndpointContract))] public interface IXrdsProviderEndpoint : IProviderEndpoint { /// <summary> /// Gets the priority associated with this service that may have been given diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IXrdsProviderEndpointContract.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IXrdsProviderEndpointContract.cs new file mode 100644 index 0000000..e0e2b0b --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/IXrdsProviderEndpointContract.cs @@ -0,0 +1,59 @@ +// <auto-generated /> + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Globalization; + using DotNetOpenAuth.OpenId.Messages; + + [ContractClassFor(typeof(IXrdsProviderEndpoint))] + internal abstract class IXrdsProviderEndpointContract : IXrdsProviderEndpoint { + #region IXrdsProviderEndpoint Properties + + int? IXrdsProviderEndpoint.ServicePriority { + get { throw new System.NotImplementedException(); } + } + + int? IXrdsProviderEndpoint.UriPriority { + get { throw new System.NotImplementedException(); } + } + + #endregion + + #region IProviderEndpoint Properties + + Version IProviderEndpoint.Version { + get { throw new System.NotImplementedException(); } + } + + Uri IProviderEndpoint.Uri { + get { throw new System.NotImplementedException(); } + } + + #endregion + + #region IXrdsProviderEndpoint Methods + + bool IXrdsProviderEndpoint.IsTypeUriPresent(string typeUri) { + throw new System.NotImplementedException(); + } + + #endregion + + #region IProviderEndpoint Methods + + bool IProviderEndpoint.IsExtensionSupported<T>() { + throw new System.NotImplementedException(); + } + + bool IProviderEndpoint.IsExtensionSupported(System.Type extensionType) { + Contract.Requires<ArgumentNullException>(extensionType != null); + Contract.Requires<ArgumentException>(typeof(IOpenIdMessageExtension).IsAssignableFrom(extensionType)); + ////ErrorUtilities.VerifyArgument(typeof(IOpenIdMessageExtension).IsAssignableFrom(extensionType), string.Format(CultureInfo.CurrentCulture, OpenIdStrings.TypeMustImplementX, typeof(IOpenIdMessageExtension).FullName)); + throw new System.NotImplementedException(); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/NegativeAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/NegativeAuthenticationResponse.cs index 5aa2e24..02c5185 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/NegativeAuthenticationResponse.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/NegativeAuthenticationResponse.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.Web; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.Messages; @@ -27,7 +28,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> /// <param name="response">The negative assertion response received by the Relying Party.</param> internal NegativeAuthenticationResponse(NegativeAssertionResponse response) { - ErrorUtilities.VerifyArgumentNotNull(response, "response"); + Contract.Requires<ArgumentNullException>(response != null); this.response = response; } @@ -128,8 +129,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <value></value> public Identifier UserSuppliedIdentifier { get { - ErrorUtilities.VerifyOperation(this.Status == AuthenticationStatus.SetupRequired, OpenIdStrings.OperationOnlyValidForSetupRequiredState); - string userSuppliedIdentifier; this.response.ExtraData.TryGetValue(AuthenticationRequest.UserSuppliedIdentifierParameterName, out userSuppliedIdentifier); return userSuppliedIdentifier; diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.cs index 6a4413f..4249834 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.cs @@ -5,6 +5,7 @@ //----------------------------------------------------------------------- [assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedScriptResourceName, "text/javascript")] +[assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedStylesheetResourceName, "text/css")] [assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedDotNetOpenIdLogoResourceName, "image/gif")] [assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedSpinnerResourceName, "image/gif")] [assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdAjaxTextBox.EmbeddedLoginSuccessResourceName, "image/png")] @@ -19,18 +20,13 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; using System.Drawing.Design; using System.Globalization; - using System.Linq; using System.Text; - using System.Text.RegularExpressions; - using System.Web; using System.Web.UI; - using System.Web.UI.WebControls; + using System.Web.UI.HtmlControls; using DotNetOpenAuth.Messaging; - using DotNetOpenAuth.OpenId.ChannelElements; - using DotNetOpenAuth.OpenId.Extensions; - using DotNetOpenAuth.OpenId.Extensions.UI; /// <summary> /// An ASP.NET control that provides a minimal text box that is OpenID-aware and uses AJAX for @@ -38,13 +34,18 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> [DefaultProperty("Text"), ValidationProperty("Text")] [ToolboxData("<{0}:OpenIdAjaxTextBox runat=\"server\" />")] - public sealed class OpenIdAjaxTextBox : WebControl, ICallbackEventHandler { + public class OpenIdAjaxTextBox : OpenIdRelyingPartyAjaxControlBase, IEditableTextControl, ITextControl, IPostBackDataHandler { /// <summary> /// The name of the manifest stream containing the OpenIdAjaxTextBox.js file. /// </summary> internal const string EmbeddedScriptResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.OpenIdAjaxTextBox.js"; /// <summary> + /// The name of the manifest stream containing the OpenIdAjaxTextBox.css file. + /// </summary> + internal const string EmbeddedStylesheetResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.OpenIdAjaxTextBox.css"; + + /// <summary> /// The name of the manifest stream containing the dotnetopenid_16x16.gif file. /// </summary> internal const string EmbeddedDotNetOpenIdLogoResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.dotnetopenid_16x16.gif"; @@ -64,47 +65,47 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> internal const string EmbeddedLoginFailureResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.login_failure.png"; - #region Property viewstate keys - /// <summary> - /// The viewstate key to use for storing the value of the <see cref="Columns"/> property. + /// The default value for the <see cref="DownloadYahooUILibrary"/> property. /// </summary> - private const string ColumnsViewStateKey = "Columns"; + internal const bool DownloadYahooUILibraryDefault = true; + + #region Property viewstate keys /// <summary> - /// The viewstate key to use for storing the value of the <see cref="OnClientAssertionReceived"/> property. + /// The viewstate key to use for storing the value of the <see cref="AutoPostBack"/> property. /// </summary> - private const string OnClientAssertionReceivedViewStateKey = "OnClientAssertionReceived"; + private const string AutoPostBackViewStateKey = "AutoPostback"; /// <summary> - /// The viewstate key to use for storing the value of the <see cref="AuthenticationResponse"/> property. + /// The viewstate key to use for the <see cref="Text"/> property. /// </summary> - private const string AuthenticationResponseViewStateKey = "AuthenticationResponse"; + private const string TextViewStateKey = "Text"; /// <summary> - /// The viewstate key to use for storing the value of the a successful authentication. + /// The viewstate key to use for storing the value of the <see cref="Columns"/> property. /// </summary> - private const string AuthDataViewStateKey = "AuthData"; + private const string ColumnsViewStateKey = "Columns"; /// <summary> - /// The viewstate key to use for storing the value of the <see cref="AuthenticatedAsToolTip"/> property. + /// The viewstate key to use for the <see cref="CssClass"/> property. /// </summary> - private const string AuthenticatedAsToolTipViewStateKey = "AuthenticatedAsToolTip"; + private const string CssClassViewStateKey = "CssClass"; /// <summary> - /// The viewstate key to use for storing the value of the <see cref="AuthenticationSucceededToolTip"/> property. + /// The viewstate key to use for storing the value of the <see cref="OnClientAssertionReceived"/> property. /// </summary> - private const string AuthenticationSucceededToolTipViewStateKey = "AuthenticationSucceededToolTip"; + private const string OnClientAssertionReceivedViewStateKey = "OnClientAssertionReceived"; /// <summary> - /// The viewstate key to use for storing the value of the <see cref="ReturnToUrl"/> property. + /// The viewstate key to use for storing the value of the <see cref="AuthenticatedAsToolTip"/> property. /// </summary> - private const string ReturnToUrlViewStateKey = "ReturnToUrl"; + private const string AuthenticatedAsToolTipViewStateKey = "AuthenticatedAsToolTip"; /// <summary> - /// The viewstate key to use for storing the value of the <see cref="RealmUrl"/> property. + /// The viewstate key to use for storing the value of the <see cref="AuthenticationSucceededToolTip"/> property. /// </summary> - private const string RealmUrlViewStateKey = "RealmUrl"; + private const string AuthenticationSucceededToolTipViewStateKey = "AuthenticationSucceededToolTip"; /// <summary> /// The viewstate key to use for storing the value of the <see cref="LogOnInProgressMessage"/> property. @@ -142,6 +143,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { private const string LogOnToolTipViewStateKey = "LoginToolTip"; /// <summary> + /// The viewstate key to use for storing the value of the <see cref="LogOnPostBackToolTip"/> property. + /// </summary> + private const string LogOnPostBackToolTipViewStateKey = "LoginPostBackToolTip"; + + /// <summary> /// The viewstate key to use for storing the value of the <see cref="Name"/> property. /// </summary> private const string NameViewStateKey = "Name"; @@ -152,14 +158,14 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { private const string TimeoutViewStateKey = "Timeout"; /// <summary> - /// The viewstate key to use for storing the value of the <see cref="Text"/> property. + /// The viewstate key to use for storing the value of the <see cref="TabIndex"/> property. /// </summary> - private const string TextViewStateKey = "Text"; + private const string TabIndexViewStateKey = "TabIndex"; /// <summary> - /// The viewstate key to use for storing the value of the <see cref="TabIndex"/> property. + /// The viewstate key to use for the <see cref="Enabled"/> property. /// </summary> - private const string TabIndexViewStateKey = "TabIndex"; + private const string EnabledViewStateKey = "Enabled"; /// <summary> /// The viewstate key to use for storing the value of the <see cref="RetryToolTip"/> property. @@ -171,24 +177,34 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> private const string RetryTextViewStateKey = "RetryText"; + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="DownloadYahooUILibrary"/> property. + /// </summary> + private const string DownloadYahooUILibraryViewStateKey = "DownloadYahooUILibrary"; + + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="ShowLogOnPostBackButton"/> property. + /// </summary> + private const string ShowLogOnPostBackButtonViewStateKey = "ShowLogOnPostBackButton"; + #endregion #region Property defaults /// <summary> - /// The default value for the <see cref="Columns"/> property. + /// The default value for the <see cref="AutoPostBack"/> property. /// </summary> - private const int ColumnsDefault = 40; + private const bool AutoPostBackDefault = false; /// <summary> - /// The default value for the <see cref="ReturnToUrl"/> property. + /// The default value for the <see cref="Columns"/> property. /// </summary> - private const string ReturnToUrlDefault = ""; + private const int ColumnsDefault = 40; /// <summary> - /// The default value for the <see cref="RealmUrl"/> property. + /// The default value for the <see cref="CssClass"/> property. /// </summary> - private const string RealmUrlDefault = "~/"; + private const string CssClassDefault = "openid"; /// <summary> /// The default value for the <see cref="LogOnInProgressMessage"/> property. @@ -251,71 +267,46 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { private const string LogOnToolTipDefault = "Click here to log in using a pop-up window."; /// <summary> - /// The default value for the <see cref="RetryText"/> property. + /// The default value for the <see cref="LogOnPostBackToolTip"/> property. /// </summary> - private const string RetryTextDefault = "RETRY"; - - #endregion + private const string LogOnPostBackToolTipDefault = "Click here to log in immediately."; /// <summary> - /// Backing field for the <see cref="RelyingParty"/> property. + /// The default value for the <see cref="RetryText"/> property. /// </summary> - private OpenIdRelyingParty relyingParty; + private const string RetryTextDefault = "RETRY"; /// <summary> - /// Backing field for the <see cref="RelyingPartyNonVerifying"/> property. + /// The default value for the <see cref="ShowLogOnPostBackButton"/> property. /// </summary> - private OpenIdRelyingParty relyingPartyNonVerifying; + private const bool ShowLogOnPostBackButtonDefault = false; - /// <summary> - /// Tracks whether the text box should receive input focus when the page is rendered. - /// </summary> - private bool focusCalled; + #endregion /// <summary> - /// The authentication response that just came in. + /// The path where the YUI control library should be downloaded from for HTTP pages. /// </summary> - private IAuthenticationResponse authenticationResponse; + private const string YuiLoaderHttp = "http://ajax.googleapis.com/ajax/libs/yui/2.8.0r4/build/yuiloader/yuiloader-min.js"; /// <summary> - /// A dictionary of extension response types and the javascript member - /// name to map them to on the user agent. + /// The path where the YUI control library should be downloaded from for HTTPS pages. /// </summary> - private Dictionary<Type, string> clientScriptExtensions = new Dictionary<Type, string>(); + private const string YuiLoaderHttps = "https://ajax.googleapis.com/ajax/libs/yui/2.8.0r4/build/yuiloader/yuiloader-min.js"; /// <summary> - /// Stores the result of an AJAX discovery request while it is waiting - /// to be picked up by ASP.NET on the way down to the user agent. + /// Initializes a new instance of the <see cref="OpenIdAjaxTextBox"/> class. /// </summary> - private string discoveryResult; + public OpenIdAjaxTextBox() { + this.HookFormSubmit = true; + } #region Events /// <summary> - /// Fired when the user has typed in their identifier, discovery was successful - /// and a login attempt is about to begin. - /// </summary> - [Description("Fired when the user has typed in their identifier, discovery was successful and a login attempt is about to begin.")] - public event EventHandler<OpenIdEventArgs> LoggingIn; - - /// <summary> - /// Fired when a Provider sends back a positive assertion to this control, - /// but the authentication has not yet been verified. - /// </summary> - /// <remarks> - /// <b>No security critical decisions should be made within event handlers - /// for this event</b> as the authenticity of the assertion has not been - /// verified yet. All security related code should go in the event handler - /// for the <see cref="LoggedIn"/> event. - /// </remarks> - [Description("Fired when a Provider sends back a positive assertion to this control, but the authentication has not yet been verified.")] - public event EventHandler<OpenIdEventArgs> UnconfirmedPositiveAssertion; - - /// <summary> - /// Fired when authentication has completed successfully. + /// Fired when the content of the text changes between posts to the server. /// </summary> - [Description("Fired when authentication has completed successfully.")] - public event EventHandler<OpenIdEventArgs> LoggedIn; + [Description("Occurs when the content of the text changes between posts to the server."), Category(BehaviorCategory)] + public event EventHandler TextChanged; /// <summary> /// Gets or sets the client-side script that executes when an authentication @@ -331,10 +322,10 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// the authentication has not been verified and may have been spoofed. /// No security-sensitive operations should take place in this javascript code. /// The authentication is verified on the server by the time the - /// <see cref="LoggedIn"/> server-side event fires.</para> + /// <see cref="OpenIdRelyingPartyControlBase.LoggedIn"/> server-side event fires.</para> /// </remarks> [Description("Gets or sets the client-side script that executes when an authentication assertion is received (but before it is verified).")] - [Bindable(true), DefaultValue(""), Category("Behavior")] + [Bindable(true), DefaultValue(""), Category(BehaviorCategory)] public string OnClientAssertionReceived { get { return this.ViewState[OnClientAssertionReceivedViewStateKey] as string; } set { this.ViewState[OnClientAssertionReceivedViewStateKey] = value; } @@ -345,54 +336,51 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { #region Properties /// <summary> - /// Gets the completed authentication response. + /// Gets or sets the value in the text field, completely unprocessed or normalized. /// </summary> - public IAuthenticationResponse AuthenticationResponse { + [Bindable(true), DefaultValue(""), Category(AppearanceCategory)] + [Description("The content of the text box.")] + public string Text { get { - if (this.authenticationResponse == null) { - // We will either validate a new response and return a live AuthenticationResponse - // or we will try to deserialize a previous IAuthenticationResponse (snapshot) - // from viewstate and return that. - IAuthenticationResponse viewstateResponse = this.ViewState[AuthenticationResponseViewStateKey] as IAuthenticationResponse; - string viewstateAuthData = this.ViewState[AuthDataViewStateKey] as string; - string formAuthData = this.Page.Request.Form[this.OpenIdAuthDataFormKey]; - - // First see if there is fresh auth data to be processed into a response. - if (!string.IsNullOrEmpty(formAuthData) && !string.Equals(viewstateAuthData, formAuthData, StringComparison.Ordinal)) { - this.ViewState[AuthDataViewStateKey] = formAuthData; - - Uri authUri = new Uri(formAuthData); - HttpRequestInfo clientResponseInfo = new HttpRequestInfo { - UrlBeforeRewriting = authUri, - }; - - this.authenticationResponse = this.RelyingParty.GetResponse(clientResponseInfo); - - // Save out the authentication response to viewstate so we can find it on - // a subsequent postback. - this.ViewState[AuthenticationResponseViewStateKey] = new AuthenticationResponseSnapshot(this.authenticationResponse); - } else { - this.authenticationResponse = viewstateResponse; - } + return this.Identifier != null ? this.Identifier.OriginalString : (this.ViewState[TextViewStateKey] as string ?? string.Empty); + } + + set { + // Try to store it as a validated identifier, + // but failing that at least store the text. + Identifier id; + if (Identifier.TryParse(value, out id)) { + this.Identifier = id; + } else { + // Be sure to set the viewstate AFTER setting the Identifier, + // since setting the Identifier clears the viewstate in OnIdentifierChanged. + this.Identifier = null; + this.ViewState[TextViewStateKey] = value; } - return this.authenticationResponse; } } /// <summary> - /// Gets or sets the value in the text field, completely unprocessed or normalized. + /// Gets or sets a value indicating whether a postback is made to fire the + /// <see cref="OpenIdRelyingPartyControlBase.LoggedIn"/> event as soon as authentication has completed + /// successfully. /// </summary> - [Bindable(true), DefaultValue(""), Category("Appearance")] - [Description("The value in the text field, completely unprocessed or normalized.")] - public string Text { - get { return (string)(this.ViewState[TextViewStateKey] ?? string.Empty); } - set { this.ViewState[TextViewStateKey] = value ?? string.Empty; } + /// <value> + /// <c>true</c> if a postback should be made automatically upon authentication; + /// otherwise, <c>false</c> to delay the <see cref="OpenIdRelyingPartyControlBase.LoggedIn"/> + /// event from firing at the server until a postback is made by some other control. + /// </value> + [Bindable(true), Category(BehaviorCategory), DefaultValue(AutoPostBackDefault)] + [Description("Whether the LoggedIn event fires on the server as soon as authentication completes successfully.")] + public bool AutoPostBack { + get { return (bool)(this.ViewState[AutoPostBackViewStateKey] ?? AutoPostBackDefault); } + set { this.ViewState[AutoPostBackViewStateKey] = value; } } /// <summary> /// Gets or sets the width of the text box in characters. /// </summary> - [Bindable(true), Category("Appearance"), DefaultValue(ColumnsDefault)] + [Bindable(true), Category(AppearanceCategory), DefaultValue(ColumnsDefault)] [Description("The width of the text box in characters.")] public int Columns { get { @@ -400,22 +388,44 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } set { - ErrorUtilities.VerifyArgumentInRange(value >= 0, "value"); + Contract.Requires<ArgumentOutOfRangeException>(value >= 0); this.ViewState[ColumnsViewStateKey] = value; } } /// <summary> + /// Gets or sets the CSS class assigned to the text box. + /// </summary> + [Bindable(true), DefaultValue(CssClassDefault), Category(AppearanceCategory)] + [Description("The CSS class assigned to the text box.")] + public string CssClass { + get { return (string)this.ViewState[CssClassViewStateKey]; } + set { this.ViewState[CssClassViewStateKey] = value; } + } + + /// <summary> /// Gets or sets the tab index of the text box control. Use 0 to omit an explicit tabindex. /// </summary> - [Bindable(true), Category("Behavior"), DefaultValue(TabIndexDefault)] + [Bindable(true), Category(BehaviorCategory), DefaultValue(TabIndexDefault)] [Description("The tab index of the text box control. Use 0 to omit an explicit tabindex.")] - public override short TabIndex { + public virtual short TabIndex { get { return (short)(this.ViewState[TabIndexViewStateKey] ?? TabIndexDefault); } set { this.ViewState[TabIndexViewStateKey] = value; } } /// <summary> + /// Gets or sets a value indicating whether this <see cref="OpenIdTextBox"/> is enabled + /// in the browser for editing and will respond to incoming OpenID messages. + /// </summary> + /// <value><c>true</c> if enabled; otherwise, <c>false</c>.</value> + [Bindable(true), DefaultValue(true), Category(BehaviorCategory)] + [Description("Whether the control is editable in the browser and will respond to OpenID messages.")] + public bool Enabled { + get { return (bool)(this.ViewState[EnabledViewStateKey] ?? true); } + set { this.ViewState[EnabledViewStateKey] = value; } + } + + /// <summary> /// Gets or sets the HTML name to assign to the text field. /// </summary> [Bindable(true), DefaultValue(NameDefault), Category("Misc")] @@ -426,7 +436,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } set { - ErrorUtilities.VerifyNonZeroLength(value, "value"); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(value)); this.ViewState[NameViewStateKey] = value ?? string.Empty; } } @@ -434,7 +444,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// Gets or sets the time duration for the AJAX control to wait for an OP to respond before reporting failure to the user. /// </summary> - [Browsable(true), DefaultValue(typeof(TimeSpan), "00:00:01"), Category("Behavior")] + [Browsable(true), DefaultValue(typeof(TimeSpan), "00:00:08"), Category(BehaviorCategory)] [Description("The time duration for the AJAX control to wait for an OP to respond before reporting failure to the user.")] public TimeSpan Timeout { get { @@ -442,7 +452,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } set { - ErrorUtilities.VerifyArgumentInRange(value.TotalMilliseconds > 0, "value"); + Contract.Requires<ArgumentOutOfRangeException>(value.TotalMilliseconds > 0); this.ViewState[TimeoutViewStateKey] = value; } } @@ -450,7 +460,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// Gets or sets the maximum number of OpenID Providers to simultaneously try to authenticate with. /// </summary> - [Browsable(true), DefaultValue(ThrottleDefault), Category("Behavior")] + [Browsable(true), DefaultValue(ThrottleDefault), Category(BehaviorCategory)] [Description("The maximum number of OpenID Providers to simultaneously try to authenticate with.")] public int Throttle { get { @@ -458,7 +468,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } set { - ErrorUtilities.VerifyArgumentInRange(value > 0, "value"); + Contract.Requires<ArgumentOutOfRangeException>(value > 0); this.ViewState[ThrottleViewStateKey] = value; } } @@ -466,7 +476,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// Gets or sets the text that appears on the LOG IN button in cases where immediate (invisible) authentication fails. /// </summary> - [Bindable(true), DefaultValue(LogOnTextDefault), Localizable(true), Category("Appearance")] + [Bindable(true), DefaultValue(LogOnTextDefault), Localizable(true), Category(AppearanceCategory)] [Description("The text that appears on the LOG IN button in cases where immediate (invisible) authentication fails.")] public string LogOnText { get { @@ -474,7 +484,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } set { - ErrorUtilities.VerifyNonZeroLength(value, "value"); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(value)); this.ViewState[LogOnTextViewStateKey] = value ?? string.Empty; } } @@ -482,7 +492,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// Gets or sets the rool tip text that appears on the LOG IN button in cases where immediate (invisible) authentication fails. /// </summary> - [Bindable(true), DefaultValue(LogOnToolTipDefault), Localizable(true), Category("Appearance")] + [Bindable(true), DefaultValue(LogOnToolTipDefault), Localizable(true), Category(AppearanceCategory)] [Description("The tool tip text that appears on the LOG IN button in cases where immediate (invisible) authentication fails.")] public string LogOnToolTip { get { return (string)(this.ViewState[LogOnToolTipViewStateKey] ?? LogOnToolTipDefault); } @@ -490,9 +500,19 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> + /// Gets or sets the rool tip text that appears on the LOG IN button when clicking the button will result in an immediate postback. + /// </summary> + [Bindable(true), DefaultValue(LogOnPostBackToolTipDefault), Localizable(true), Category(AppearanceCategory)] + [Description("The tool tip text that appears on the LOG IN button when clicking the button will result in an immediate postback.")] + public string LogOnPostBackToolTip { + get { return (string)(this.ViewState[LogOnPostBackToolTipViewStateKey] ?? LogOnPostBackToolTipDefault); } + set { this.ViewState[LogOnPostBackToolTipViewStateKey] = value ?? string.Empty; } + } + + /// <summary> /// Gets or sets the text that appears on the RETRY button in cases where authentication times out. /// </summary> - [Bindable(true), DefaultValue(RetryTextDefault), Localizable(true), Category("Appearance")] + [Bindable(true), DefaultValue(RetryTextDefault), Localizable(true), Category(AppearanceCategory)] [Description("The text that appears on the RETRY button in cases where authentication times out.")] public string RetryText { get { @@ -500,7 +520,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } set { - ErrorUtilities.VerifyNonZeroLength(value, "value"); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(value)); this.ViewState[RetryTextViewStateKey] = value ?? string.Empty; } } @@ -508,7 +528,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// Gets or sets the tool tip text that appears on the RETRY button in cases where authentication times out. /// </summary> - [Bindable(true), DefaultValue(RetryToolTipDefault), Localizable(true), Category("Appearance")] + [Bindable(true), DefaultValue(RetryToolTipDefault), Localizable(true), Category(AppearanceCategory)] [Description("The tool tip text that appears on the RETRY button in cases where authentication times out.")] public string RetryToolTip { get { return (string)(this.ViewState[RetryToolTipViewStateKey] ?? RetryToolTipDefault); } @@ -518,7 +538,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// Gets or sets the tool tip text that appears when authentication succeeds. /// </summary> - [Bindable(true), DefaultValue(AuthenticationSucceededToolTipDefault), Localizable(true), Category("Appearance")] + [Bindable(true), DefaultValue(AuthenticationSucceededToolTipDefault), Localizable(true), Category(AppearanceCategory)] [Description("The tool tip text that appears when authentication succeeds.")] public string AuthenticationSucceededToolTip { get { return (string)(this.ViewState[AuthenticationSucceededToolTipViewStateKey] ?? AuthenticationSucceededToolTipDefault); } @@ -528,7 +548,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// Gets or sets the tool tip text that appears on the green checkmark when authentication succeeds. /// </summary> - [Bindable(true), DefaultValue(AuthenticatedAsToolTipDefault), Localizable(true), Category("Appearance")] + [Bindable(true), DefaultValue(AuthenticatedAsToolTipDefault), Localizable(true), Category(AppearanceCategory)] [Description("The tool tip text that appears on the green checkmark when authentication succeeds.")] public string AuthenticatedAsToolTip { get { return (string)(this.ViewState[AuthenticatedAsToolTipViewStateKey] ?? AuthenticatedAsToolTipDefault); } @@ -538,7 +558,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// Gets or sets the tool tip text that appears when authentication fails. /// </summary> - [Bindable(true), DefaultValue(AuthenticationFailedToolTipDefault), Localizable(true), Category("Appearance")] + [Bindable(true), DefaultValue(AuthenticationFailedToolTipDefault), Localizable(true), Category(AppearanceCategory)] [Description("The tool tip text that appears when authentication fails.")] public string AuthenticationFailedToolTip { get { return (string)(this.ViewState[AuthenticationFailedToolTipViewStateKey] ?? AuthenticationFailedToolTipDefault); } @@ -548,7 +568,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// Gets or sets the tool tip text that appears over the text box when it is discovering and authenticating. /// </summary> - [Bindable(true), DefaultValue(BusyToolTipDefault), Localizable(true), Category("Appearance")] + [Bindable(true), DefaultValue(BusyToolTipDefault), Localizable(true), Category(AppearanceCategory)] [Description("The tool tip text that appears over the text box when it is discovering and authenticating.")] public string BusyToolTip { get { return (string)(this.ViewState[BusyToolTipViewStateKey] ?? BusyToolTipDefault); } @@ -558,7 +578,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// Gets or sets the message that is displayed if a postback is about to occur before the identifier has been supplied. /// </summary> - [Bindable(true), DefaultValue(IdentifierRequiredMessageDefault), Localizable(true), Category("Appearance")] + [Bindable(true), DefaultValue(IdentifierRequiredMessageDefault), Localizable(true), Category(AppearanceCategory)] [Description("The message that is displayed if a postback is about to occur before the identifier has been supplied.")] public string IdentifierRequiredMessage { get { return (string)(this.ViewState[IdentifierRequiredMessageViewStateKey] ?? IdentifierRequiredMessageDefault); } @@ -568,7 +588,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// Gets or sets the message that is displayed if a postback is attempted while login is in process. /// </summary> - [Bindable(true), DefaultValue(LogOnInProgressMessageDefault), Localizable(true), Category("Appearance")] + [Bindable(true), DefaultValue(LogOnInProgressMessageDefault), Localizable(true), Category(AppearanceCategory)] [Description("The message that is displayed if a postback is attempted while login is in process.")] public string LogOnInProgressMessage { get { return (string)(this.ViewState[LogOnInProgressMessageViewStateKey] ?? LogOnInProgressMessageDefault); } @@ -576,222 +596,51 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> - /// Gets or sets the OpenID <see cref="Realm"/> of the relying party web site. + /// Gets or sets a value indicating whether the Yahoo! User Interface Library (YUI) + /// will be downloaded in order to provide a login split button. /// </summary> - [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Uri", Justification = "Using Uri.ctor for validation.")] - [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "DotNetOpenAuth.OpenId.Realm", Justification = "Using ctor for validation.")] - [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Property grid on form designer only supports primitive types.")] - [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Property grid on form designer only supports primitive types.")] - [Bindable(true)] - [Category("Behavior")] - [DefaultValue(RealmUrlDefault)] - [Description("The OpenID Realm of the relying party web site.")] - [UrlProperty, Editor("System.Web.UI.Design.UrlEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))] - public string RealmUrl { - get { - return (string)(this.ViewState[RealmUrlViewStateKey] ?? RealmUrlDefault); - } - - set { - if (Page != null && !DesignMode) { - // Validate new value by trying to construct a Realm object based on it. - new Realm(OpenIdUtilities.GetResolvedRealm(this.Page, value, this.RelyingParty.Channel.GetRequestFromContext())); // throws an exception on failure. - } else { - // We can't fully test it, but it should start with either ~/ or a protocol. - if (Regex.IsMatch(value, @"^https?://")) { - new Uri(value.Replace("*.", "")); // make sure it's fully-qualified, but ignore wildcards - } else if (value.StartsWith("~/", StringComparison.Ordinal)) { - // this is valid too - } else { - throw new UriFormatException(); - } - } - this.ViewState[RealmUrlViewStateKey] = value; - } + /// <value> + /// <c>true</c> to use a split button; otherwise, <c>false</c> to use a standard HTML button + /// or a split button by downloading the YUI library yourself on the hosting web page. + /// </value> + /// <remarks> + /// The split button brings in about 180KB of YUI javascript dependencies. + /// </remarks> + [Bindable(true), DefaultValue(DownloadYahooUILibraryDefault), Category(BehaviorCategory)] + [Description("Whether a split button will be used for the \"log in\" when the user provides an identifier that delegates to more than one Provider.")] + public bool DownloadYahooUILibrary { + get { return (bool)(this.ViewState[DownloadYahooUILibraryViewStateKey] ?? DownloadYahooUILibraryDefault); } + set { this.ViewState[DownloadYahooUILibraryViewStateKey] = value; } } /// <summary> - /// Gets or sets the OpenID ReturnTo of the relying party web site. + /// Gets or sets a value indicating whether the "Log in" button will be shown + /// to initiate a postback containing the positive assertion. /// </summary> - [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Uri", Justification = "Using Uri.ctor for validation.")] - [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Property grid on form designer only supports primitive types.")] - [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Property grid on form designer only supports primitive types.")] - [Bindable(true)] - [Category("Behavior")] - [DefaultValue(ReturnToUrlDefault)] - [Description("The OpenID ReturnTo of the relying party web site.")] - [UrlProperty, Editor("System.Web.UI.Design.UrlEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))] - public string ReturnToUrl { - get { - return (string)(this.ViewState[ReturnToUrlViewStateKey] ?? ReturnToUrlDefault); - } - - set { - if (Page != null && !DesignMode) { - // Validate new value by trying to construct a Uri based on it. - new Uri(this.RelyingParty.Channel.GetRequestFromContext().UrlBeforeRewriting, Page.ResolveUrl(value)); // throws an exception on failure. - } else { - // We can't fully test it, but it should start with either ~/ or a protocol. - if (Regex.IsMatch(value, @"^https?://")) { - new Uri(value); // make sure it's fully-qualified, but ignore wildcards - } else if (value.StartsWith("~/", StringComparison.Ordinal)) { - // this is valid too - } else { - throw new UriFormatException(); - } - } - this.ViewState[ReturnToUrlViewStateKey] = value; - } + [Bindable(true), DefaultValue(ShowLogOnPostBackButtonDefault), Category(AppearanceCategory)] + [Description("Whether the log in button will be shown to initiate a postback containing the positive assertion.")] + public bool ShowLogOnPostBackButton { + get { return (bool)(this.ViewState[ShowLogOnPostBackButtonViewStateKey] ?? ShowLogOnPostBackButtonDefault); } + set { this.ViewState[ShowLogOnPostBackButtonViewStateKey] = value; } } #endregion - #region Properties to hide - - /// <summary> - /// Gets or sets the foreground color (typically the color of the text) of the Web server control. - /// </summary> - /// <returns> - /// A <see cref="T:System.Drawing.Color"/> that represents the foreground color of the control. The default is <see cref="F:System.Drawing.Color.Empty"/>. - /// </returns> - [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)] - public override System.Drawing.Color ForeColor { - get { throw new NotSupportedException(); } - set { throw new NotSupportedException(); } - } - /// <summary> - /// Gets or sets the background color of the Web server control. + /// Gets or sets a value indicating whether the ajax text box should hook the form's submit event for special behavior. /// </summary> - /// <returns> - /// A <see cref="T:System.Drawing.Color"/> that represents the background color of the control. The default is <see cref="F:System.Drawing.Color.Empty"/>, which indicates that this property is not set. - /// </returns> - [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)] - public override System.Drawing.Color BackColor { - get { throw new NotSupportedException(); } - set { throw new NotSupportedException(); } - } + internal bool HookFormSubmit { get; set; } /// <summary> - /// Gets or sets the border color of the Web control. - /// </summary> - /// <returns> - /// A <see cref="T:System.Drawing.Color"/> that represents the border color of the control. The default is <see cref="F:System.Drawing.Color.Empty"/>, which indicates that this property is not set. - /// </returns> - [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)] - public override System.Drawing.Color BorderColor { - get { throw new NotSupportedException(); } - set { throw new NotSupportedException(); } - } - - /// <summary> - /// Gets or sets the border width of the Web server control. - /// </summary> - /// <returns> - /// A <see cref="T:System.Web.UI.WebControls.Unit"/> that represents the border width of a Web server control. The default value is <see cref="F:System.Web.UI.WebControls.Unit.Empty"/>, which indicates that this property is not set. - /// </returns> - /// <exception cref="T:System.ArgumentException"> - /// The specified border width is a negative value. - /// </exception> - [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)] - public override Unit BorderWidth { - get { return Unit.Empty; } - set { throw new NotSupportedException(); } - } - - /// <summary> - /// Gets or sets the border style of the Web server control. - /// </summary> - /// <returns> - /// One of the <see cref="T:System.Web.UI.WebControls.BorderStyle"/> enumeration values. The default is NotSet. - /// </returns> - [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)] - public override BorderStyle BorderStyle { - get { return BorderStyle.None; } - set { throw new NotSupportedException(); } - } - - /// <summary> - /// Gets the font properties associated with the Web server control. - /// </summary> - /// <returns> - /// A <see cref="T:System.Web.UI.WebControls.FontInfo"/> that represents the font properties of the Web server control. - /// </returns> - [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)] - public override FontInfo Font { - get { return null; } - } - - /// <summary> - /// Gets or sets the height of the Web server control. - /// </summary> - /// <returns> - /// A <see cref="T:System.Web.UI.WebControls.Unit"/> that represents the height of the control. The default is <see cref="F:System.Web.UI.WebControls.Unit.Empty"/>. - /// </returns> - /// <exception cref="T:System.ArgumentException"> - /// The height was set to a negative value. - /// </exception> - [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)] - public override Unit Height { - get { return Unit.Empty; } - set { throw new NotSupportedException(); } - } - - /// <summary> - /// Gets or sets the width of the Web server control. - /// </summary> - /// <returns> - /// A <see cref="T:System.Web.UI.WebControls.Unit"/> that represents the width of the control. The default is <see cref="F:System.Web.UI.WebControls.Unit.Empty"/>. - /// </returns> - /// <exception cref="T:System.ArgumentException"> - /// The width of the Web server control was set to a negative value. - /// </exception> - [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)] - public override Unit Width { - get { return Unit.Empty; } - set { throw new NotSupportedException(); } - } - - /// <summary> - /// Gets or sets the text displayed when the mouse pointer hovers over the Web server control. - /// </summary> - /// <returns> - /// The text displayed when the mouse pointer hovers over the Web server control. The default is <see cref="F:System.String.Empty"/>. - /// </returns> - [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)] - public override string ToolTip { - get { return string.Empty; } - set { throw new NotSupportedException(); } - } - - /// <summary> - /// Gets or sets the skin to apply to the control. - /// </summary> - /// <returns> - /// The name of the skin to apply to the control. The default is <see cref="F:System.String.Empty"/>. - /// </returns> - /// <exception cref="T:System.ArgumentException"> - /// The skin specified in the <see cref="P:System.Web.UI.WebControls.WebControl.SkinID"/> property does not exist in the theme. - /// </exception> - [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)] - public override string SkinID { - get { return string.Empty; } - set { throw new NotSupportedException(); } - } - - /// <summary> - /// Gets or sets a value indicating whether themes apply to this control. + /// Gets the name of the open id auth data form key. /// </summary> - /// <returns>true to use themes; otherwise, false. The default is false. - /// </returns> - [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)] - public override bool EnableTheming { - get { return false; } - set { throw new NotSupportedException(); } + /// <value> + /// A concatenation of <see cref="Name"/> and <c>"_openidAuthData"</c>. + /// </value> + protected override string OpenIdAuthDataFormKey { + get { return this.Name + "_openidAuthData"; } } - #endregion - /// <summary> /// Gets the default value for the <see cref="Timeout"/> property. /// </summary> @@ -807,171 +656,45 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } } - /// <summary> - /// Gets the relying party to use when verification of incoming messages is needed. - /// </summary> - private OpenIdRelyingParty RelyingParty { - get { - if (this.relyingParty == null) { - this.relyingParty = CreateRelyingParty(true); - } - return this.relyingParty; - } - } - - /// <summary> - /// Gets the relying party to use when verification of incoming messages is NOT wanted. - /// </summary> - private OpenIdRelyingParty RelyingPartyNonVerifying { - get { - if (this.relyingPartyNonVerifying == null) { - this.relyingPartyNonVerifying = CreateRelyingParty(false); - } - return this.relyingPartyNonVerifying; - } - } + #region IPostBackDataHandler Members /// <summary> - /// Gets the name of the open id auth data form key. + /// When implemented by a class, processes postback data for an ASP.NET server control. /// </summary> - /// <value>A concatenation of <see cref="Name"/> and <c>"_openidAuthData"</c>.</value> - private string OpenIdAuthDataFormKey { - get { return this.Name + "_openidAuthData"; } - } - - /// <summary> - /// Places focus on the text box when the page is rendered on the browser. - /// </summary> - public override void Focus() { - // we don't emit the code to focus the control immediately, in case the control - // is never rendered to the page because its Visible property is false or that - // of any of its parent containers. - this.focusCalled = true; - } - - /// <summary> - /// Allows an OpenID extension to read data out of an unverified positive authentication assertion - /// and send it down to the client browser so that Javascript running on the page can perform - /// some preprocessing on the extension data. - /// </summary> - /// <typeparam name="T">The extension <i>response</i> type that will read data from the assertion.</typeparam> - /// <param name="propertyName">The property name on the openid_identifier input box object that will be used to store the extension data. For example: sreg</param> - /// <remarks> - /// This method should be called from the <see cref="UnconfirmedPositiveAssertion"/> event handler. - /// </remarks> - [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "By design")] - public void RegisterClientScriptExtension<T>(string propertyName) where T : IClientScriptExtensionResponse { - ErrorUtilities.VerifyNonZeroLength(propertyName, "propertyName"); - ErrorUtilities.VerifyArgumentNamed(!this.clientScriptExtensions.ContainsValue(propertyName), "propertyName", OpenIdStrings.ClientScriptExtensionPropertyNameCollision, propertyName); - foreach (var ext in this.clientScriptExtensions.Keys) { - ErrorUtilities.VerifyArgument(ext != typeof(T), OpenIdStrings.ClientScriptExtensionTypeCollision, typeof(T).FullName); - } - this.clientScriptExtensions.Add(typeof(T), propertyName); + /// <param name="postDataKey">The key identifier for the control.</param> + /// <param name="postCollection">The collection of all incoming name values.</param> + /// <returns> + /// true if the server control's state changes as a result of the postback; otherwise, false. + /// </returns> + bool IPostBackDataHandler.LoadPostData(string postDataKey, NameValueCollection postCollection) { + return this.LoadPostData(postDataKey, postCollection); } - #region ICallbackEventHandler Members - /// <summary> - /// Returns the result of discovery on some Identifier passed to <see cref="ICallbackEventHandler.RaiseCallbackEvent"/>. + /// When implemented by a class, signals the server control to notify the ASP.NET application that the state of the control has changed. /// </summary> - /// <returns>The result of the callback.</returns> - /// <value>A whitespace delimited list of URLs that can be used to initiate authentication.</value> - string ICallbackEventHandler.GetCallbackResult() { - this.Page.Response.ContentType = "text/javascript"; - return this.discoveryResult; - } - - /// <summary> - /// Performs discovery on some OpenID Identifier. Called directly from the user agent via - /// AJAX callback mechanisms. - /// </summary> - /// <param name="eventArgument">The identifier to perform discovery on.</param> - void ICallbackEventHandler.RaiseCallbackEvent(string eventArgument) { - string userSuppliedIdentifier = eventArgument; - - ErrorUtilities.VerifyNonZeroLength(userSuppliedIdentifier, "userSuppliedIdentifier"); - Logger.OpenId.InfoFormat("AJAX discovery on {0} requested.", userSuppliedIdentifier); - - // We prepare a JSON object with this interface: - // class jsonResponse { - // string claimedIdentifier; - // Array requests; // never null - // string error; // null if no error - // } - // Each element in the requests array looks like this: - // class jsonAuthRequest { - // string endpoint; // URL to the OP endpoint - // string immediate; // URL to initiate an immediate request - // string setup; // URL to initiate a setup request. - // } - StringBuilder discoveryResultBuilder = new StringBuilder(); - discoveryResultBuilder.Append("{"); - try { - List<IAuthenticationRequest> requests = this.CreateRequests(userSuppliedIdentifier, true).Where(req => this.OnLoggingIn(req)).ToList(); - if (requests.Count > 0) { - discoveryResultBuilder.AppendFormat("claimedIdentifier: {0},", MessagingUtilities.GetSafeJavascriptValue(requests[0].ClaimedIdentifier)); - discoveryResultBuilder.Append("requests: ["); - foreach (IAuthenticationRequest request in requests) { - discoveryResultBuilder.Append("{"); - discoveryResultBuilder.AppendFormat("endpoint: {0},", MessagingUtilities.GetSafeJavascriptValue(request.Provider.Uri.AbsoluteUri)); - request.Mode = AuthenticationRequestMode.Immediate; - OutgoingWebResponse response = request.RedirectingResponse; - discoveryResultBuilder.AppendFormat("immediate: {0},", MessagingUtilities.GetSafeJavascriptValue(response.GetDirectUriRequest(this.RelyingParty.Channel).AbsoluteUri)); - request.Mode = AuthenticationRequestMode.Setup; - response = request.RedirectingResponse; - discoveryResultBuilder.AppendFormat("setup: {0}", MessagingUtilities.GetSafeJavascriptValue(response.GetDirectUriRequest(this.RelyingParty.Channel).AbsoluteUri)); - discoveryResultBuilder.Append("},"); - } - discoveryResultBuilder.Length -= 1; // trim off last comma - discoveryResultBuilder.Append("]"); - } else { - discoveryResultBuilder.Append("requests: new Array(),"); - discoveryResultBuilder.AppendFormat("error: {0}", MessagingUtilities.GetSafeJavascriptValue(OpenIdStrings.OpenIdEndpointNotFound)); - } - } catch (ProtocolException ex) { - discoveryResultBuilder.Append("requests: new Array(),"); - discoveryResultBuilder.AppendFormat("error: {0}", MessagingUtilities.GetSafeJavascriptValue(ex.Message)); - } - discoveryResultBuilder.Append("}"); - this.discoveryResult = discoveryResultBuilder.ToString(); + void IPostBackDataHandler.RaisePostDataChangedEvent() { + this.RaisePostDataChangedEvent(); } #endregion /// <summary> - /// Enables a server control to perform final clean up before it is released from memory. - /// </summary> - public sealed override void Dispose() { - this.Dispose(true); - base.Dispose(); - GC.SuppressFinalize(this); - } - - /// <summary> - /// Prepares the control for loading. + /// Raises the <see cref="E:Load"/> event. /// </summary> - /// <param name="e">The <see cref="T:System.EventArgs"/> object that contains the event data.</param> + /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param> protected override void OnLoad(EventArgs e) { base.OnLoad(e); - if (this.Page.IsPostBack) { - // If the control was temporarily hidden, it won't be in the Form data, - // and we'll just implicitly keep the last Text setting. - if (this.Page.Request.Form[this.Name] != null) { - this.Text = this.Page.Request.Form[this.Name]; - } + this.Page.RegisterRequiresPostBack(this); + } - // If there is a response, and it is fresh (live object, not a snapshot object)... - if (this.AuthenticationResponse != null && this.AuthenticationResponse.Status == AuthenticationStatus.Authenticated) { - this.OnLoggedIn(this.AuthenticationResponse); - } - } else { - NameValueCollection query = this.RelyingParty.Channel.GetRequestFromContext().GetQueryOrFormFromContext(); - string userSuppliedIdentifier = query["dotnetopenid.userSuppliedIdentifier"]; - if (!string.IsNullOrEmpty(userSuppliedIdentifier) && query["dotnetopenid.phase"] == "2") { - this.ReportAuthenticationResult(); - } - } + /// <summary> + /// Called when the <see cref="Identifier"/> property is changed. + /// </summary> + protected override void OnIdentifierChanged() { + this.ViewState.Remove(TextViewStateKey); + base.OnIdentifierChanged(); } /// <summary> @@ -981,217 +704,137 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { protected override void OnPreRender(EventArgs e) { base.OnPreRender(e); + if (this.DownloadYahooUILibrary) { + // Although we'll add the <script> tag to download the YAHOO component, + // a download failure may have occurred, so protect ourselves from a + // script error using an if (YAHOO) block. But apparently at least in IE + // that's not even enough, so we use a try/catch. + string yuiLoadScript = @"try { if (YAHOO) { + var loader = new YAHOO.util.YUILoader({ + require: ['button', 'menu'], + loadOptional: false, + combine: true + }); + + loader.insert(); +} } catch (e) { }"; + this.Page.ClientScript.RegisterClientScriptInclude("yuiloader", this.Page.Request.Url.IsTransportSecure() ? YuiLoaderHttps : YuiLoaderHttp); + this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "requiredYuiComponents", yuiLoadScript, true); + } + + var css = new HtmlLink(); + css.Href = this.Page.ClientScript.GetWebResourceUrl(this.GetType(), EmbeddedStylesheetResourceName); + css.Attributes["rel"] = "stylesheet"; + css.Attributes["type"] = "text/css"; + ErrorUtilities.VerifyHost(this.Page.Header != null, OpenIdStrings.HeadTagMustIncludeRunatServer); + this.Page.Header.Controls.AddAt(0, css); // insert at top so host page can override + this.PrepareClientJavascript(); + + // If an Identifier is preset on this control, preload discovery on that identifier, + // but only if we're not already persisting an authentication result since that would + // be redundant. + if (this.Identifier != null && this.AuthenticationResponse == null) { + this.PreloadDiscovery(this.Identifier); + } } /// <summary> /// Renders the control. /// </summary> /// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the control content.</param> - protected override void Render(System.Web.UI.HtmlTextWriter writer) { + protected override void Render(HtmlTextWriter writer) { + base.Render(writer); + // We surround the textbox with a span so that the .js file can inject a // login button within the text box with easy placement. - writer.WriteBeginTag("span"); - writer.WriteAttribute("class", this.CssClass); - writer.Write(" style='"); - writer.WriteStyleAttribute("display", "inline-block"); - writer.WriteStyleAttribute("position", "relative"); - writer.WriteStyleAttribute("font-size", "16px"); - writer.Write("'>"); - - writer.WriteBeginTag("input"); - writer.WriteAttribute("name", this.Name); - writer.WriteAttribute("id", this.ClientID); - writer.WriteAttribute("value", this.Text, true); - writer.WriteAttribute("size", this.Columns.ToString(CultureInfo.InvariantCulture)); + string css = this.CssClass ?? string.Empty; + css += " OpenIdAjaxTextBox"; + writer.AddAttribute(HtmlTextWriterAttribute.Class, css); + + writer.AddStyleAttribute(HtmlTextWriterStyle.Display, "inline-block"); + writer.AddStyleAttribute(HtmlTextWriterStyle.Position, "relative"); + writer.AddStyleAttribute(HtmlTextWriterStyle.FontSize, "16px"); + writer.RenderBeginTag(HtmlTextWriterTag.Span); + + writer.AddAttribute(HtmlTextWriterAttribute.Name, this.Name); + writer.AddAttribute(HtmlTextWriterAttribute.Id, this.ClientID); + writer.AddAttribute(HtmlTextWriterAttribute.Size, this.Columns.ToString(CultureInfo.InvariantCulture)); + if (!string.IsNullOrEmpty(this.Text)) { + writer.AddAttribute(HtmlTextWriterAttribute.Value, this.Text, true); + } + if (this.TabIndex > 0) { - writer.WriteAttribute("tabindex", this.TabIndex.ToString(CultureInfo.InvariantCulture)); + writer.AddAttribute(HtmlTextWriterAttribute.Tabindex, this.TabIndex.ToString(CultureInfo.InvariantCulture)); } if (!this.Enabled) { - writer.WriteAttribute("disabled", "true"); + writer.AddAttribute(HtmlTextWriterAttribute.Disabled, "true"); } if (!string.IsNullOrEmpty(this.CssClass)) { - writer.WriteAttribute("class", this.CssClass); - } - writer.Write(" style='"); - writer.WriteStyleAttribute("padding-left", "18px"); - writer.WriteStyleAttribute("border-style", "solid"); - writer.WriteStyleAttribute("border-width", "1px"); - writer.WriteStyleAttribute("border-color", "lightgray"); - writer.Write("'"); - writer.Write(" />"); - - writer.WriteEndTag("span"); - - // Emit a hidden field to let the javascript on the user agent know if an - // authentication has already successfully taken place. - string viewstateAuthData = this.ViewState[AuthDataViewStateKey] as string; - if (!string.IsNullOrEmpty(viewstateAuthData)) { - writer.WriteBeginTag("input"); - writer.WriteAttribute("type", "hidden"); - writer.WriteAttribute("name", this.OpenIdAuthDataFormKey); - writer.WriteAttribute("value", viewstateAuthData, true); - writer.Write(" />"); + writer.AddAttribute(HtmlTextWriterAttribute.Class, this.CssClass); } + writer.AddStyleAttribute(HtmlTextWriterStyle.PaddingLeft, "18px"); + writer.AddStyleAttribute(HtmlTextWriterStyle.BorderStyle, "solid"); + writer.AddStyleAttribute(HtmlTextWriterStyle.BorderWidth, "1px"); + writer.AddStyleAttribute(HtmlTextWriterStyle.BorderColor, "lightgray"); + writer.RenderBeginTag(HtmlTextWriterTag.Input); + writer.RenderEndTag(); // </input> + writer.RenderEndTag(); // </span> } /// <summary> - /// Filters a sequence of OP endpoints so that an OP hostname only appears once in the list. + /// When implemented by a class, processes postback data for an ASP.NET server control. /// </summary> - /// <param name="requests">The authentication requests against those OP endpoints.</param> - /// <returns>The filtered list.</returns> - private static List<IAuthenticationRequest> RemoveDuplicateEndpoints(List<IAuthenticationRequest> requests) { - var filteredRequests = new List<IAuthenticationRequest>(requests.Count); - foreach (IAuthenticationRequest request in requests) { - // We'll distinguish based on the host name only, which - // admittedly is only a heuristic, but if we remove one that really wasn't a duplicate, well, - // this multiple OP attempt thing was just a convenience feature anyway. - if (!filteredRequests.Any(req => string.Equals(req.Provider.Uri.Host, request.Provider.Uri.Host, StringComparison.OrdinalIgnoreCase))) { - filteredRequests.Add(request); - } - } - - return filteredRequests; - } - - /// <summary> - /// Creates the relying party. - /// </summary> - /// <param name="verifySignature"> - /// A value indicating whether message protections should be applied to the processed messages. - /// Use <c>false</c> to postpone verification to a later time without invalidating nonces. - /// </param> - /// <returns>The newly instantiated relying party.</returns> - private static OpenIdRelyingParty CreateRelyingParty(bool verifySignature) { - return verifySignature ? new OpenIdRelyingParty() : OpenIdRelyingParty.CreateNonVerifying(); - } - - /// <summary> - /// Releases unmanaged and - optionally - managed resources - /// </summary> - /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - private void Dispose(bool disposing) { - if (disposing) { - if (this.relyingParty != null) { - this.relyingParty.Dispose(); - this.relyingParty = null; - } - - if (this.relyingPartyNonVerifying != null) { - this.relyingPartyNonVerifying.Dispose(); - this.relyingPartyNonVerifying = null; + /// <param name="postDataKey">The key identifier for the control.</param> + /// <param name="postCollection">The collection of all incoming name values.</param> + /// <returns> + /// true if the server control's state changes as a result of the postback; otherwise, false. + /// </returns> + protected virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection) { + // If the control was temporarily hidden, it won't be in the Form data, + // and we'll just implicitly keep the last Text setting. + if (postCollection[this.Name] != null) { + Identifier identifier = postCollection[this.Name].Length == 0 ? null : postCollection[this.Name]; + if (identifier != this.Identifier) { + this.Identifier = identifier; + return true; } } - } - - /// <summary> - /// Fires the <see cref="LoggingIn"/> event. - /// </summary> - /// <param name="request">The request.</param> - /// <returns><c>true</c> if the login should proceed; <c>false</c> otherwise.</returns> - private bool OnLoggingIn(IAuthenticationRequest request) { - var loggingIn = this.LoggingIn; - if (loggingIn != null) { - var args = new OpenIdEventArgs(request); - loggingIn(this, args); - return !args.Cancel; - } - return true; + return false; } /// <summary> - /// Fires the <see cref="UnconfirmedPositiveAssertion"/> event. + /// When implemented by a class, signals the server control to notify the ASP.NET application that the state of the control has changed. /// </summary> - private void OnUnconfirmedPositiveAssertion() { - var unconfirmedPositiveAssertion = this.UnconfirmedPositiveAssertion; - if (unconfirmedPositiveAssertion != null) { - unconfirmedPositiveAssertion(this, null); - } + [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "Predefined signature.")] + protected virtual void RaisePostDataChangedEvent() { + this.OnTextChanged(); } /// <summary> - /// Fires the <see cref="LoggedIn"/> event. + /// Called on a postback when the Text property has changed. /// </summary> - /// <param name="response">The response.</param> - private void OnLoggedIn(IAuthenticationResponse response) { - var loggedIn = this.LoggedIn; - if (loggedIn != null) { - loggedIn(this, new OpenIdEventArgs(response)); + protected virtual void OnTextChanged() { + EventHandler textChanged = this.TextChanged; + if (textChanged != null) { + textChanged(this, EventArgs.Empty); } } /// <summary> - /// Invokes a method on a parent frame/window's OpenIdAjaxTextBox, - /// and closes the calling popup window if applicable. - /// </summary> - /// <param name="methodCall">The method to call on the OpenIdAjaxTextBox, including - /// parameters. (i.e. "callback('arg1', 2)"). No escaping is done by this method.</param> - private void CallbackUserAgentMethod(string methodCall) { - this.CallbackUserAgentMethod(methodCall, null); - } - - /// <summary> - /// Invokes a method on a parent frame/window's OpenIdAjaxTextBox, - /// and closes the calling popup window if applicable. - /// </summary> - /// <param name="methodCall">The method to call on the OpenIdAjaxTextBox, including - /// parameters. (i.e. "callback('arg1', 2)"). No escaping is done by this method.</param> - /// <param name="preAssignments">An optional list of assignments to make to the input box object before placing the method call.</param> - private void CallbackUserAgentMethod(string methodCall, string[] preAssignments) { - Logger.OpenId.InfoFormat("Sending Javascript callback: {0}", methodCall); - Page.Response.Write(@"<html><body><script language='javascript'> - var inPopup = !window.frameElement; - var objSrc = inPopup ? window.opener.waiting_openidBox : window.frameElement.openidBox; -"); - if (preAssignments != null) { - foreach (string assignment in preAssignments) { - Page.Response.Write(string.Format(CultureInfo.InvariantCulture, " objSrc.{0};\n", assignment)); - } - } - - // Something about calling objSrc.{0} can somehow cause FireFox to forget about the inPopup variable, - // so we have to actually put the test for it ABOVE the call to objSrc.{0} so that it already - // whether to call window.self.close() after the call. - string htmlFormat = @" if (inPopup) {{ - objSrc.{0}; - window.self.close(); -}} else {{ - objSrc.{0}; -}} -</script></body></html>"; - Page.Response.Write(string.Format(CultureInfo.InvariantCulture, htmlFormat, methodCall)); - Page.Response.End(); - } - - /// <summary> /// Assembles the javascript to send to the client and registers it with ASP.NET for transmission. /// </summary> private void PrepareClientJavascript() { - string identifierParameterName = "identifier"; - string discoveryCallbackResultParameterName = "resultFunction"; - string discoveryErrorCallbackParameterName = "errorCallback"; - string discoveryCallback = Page.ClientScript.GetCallbackEventReference( - this, - identifierParameterName, - discoveryCallbackResultParameterName, - identifierParameterName, - discoveryErrorCallbackParameterName, - true); - // Import the .js file where most of the code is. this.Page.ClientScript.RegisterClientScriptResource(typeof(OpenIdAjaxTextBox), EmbeddedScriptResourceName); // Call into the .js file with initialization information. StringBuilder startupScript = new StringBuilder(); - startupScript.AppendLine("<script language='javascript'>"); startupScript.AppendFormat("var box = document.getElementsByName('{0}')[0];{1}", this.Name, Environment.NewLine); - if (this.focusCalled) { - startupScript.AppendLine("box.focus();"); - } startupScript.AppendFormat( CultureInfo.InvariantCulture, - "initAjaxOpenId(box, {0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11}, {12}, {13}, {14}, {15}, {16}, {17}, function({18}, {19}, {20}) {{{21}}});{22}", + "initAjaxOpenId(box, {0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11}, {12}, {13}, {14}, {15}, {16}, {17}, {18}, {19}, {20}, function() {{{21};}});{22}", MessagingUtilities.GetSafeJavascriptValue(this.Page.ClientScript.GetWebResourceUrl(this.GetType(), OpenIdTextBox.EmbeddedLogoResourceName)), MessagingUtilities.GetSafeJavascriptValue(this.Page.ClientScript.GetWebResourceUrl(this.GetType(), EmbeddedDotNetOpenIdLogoResourceName)), MessagingUtilities.GetSafeJavascriptValue(this.Page.ClientScript.GetWebResourceUrl(this.GetType(), EmbeddedSpinnerResourceName)), @@ -1202,6 +845,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { string.IsNullOrEmpty(this.OnClientAssertionReceived) ? "null" : "'" + this.OnClientAssertionReceived.Replace(@"\", @"\\").Replace("'", @"\'") + "'", MessagingUtilities.GetSafeJavascriptValue(this.LogOnText), MessagingUtilities.GetSafeJavascriptValue(this.LogOnToolTip), + this.ShowLogOnPostBackButton ? "true" : "false", + MessagingUtilities.GetSafeJavascriptValue(this.LogOnPostBackToolTip), MessagingUtilities.GetSafeJavascriptValue(this.RetryText), MessagingUtilities.GetSafeJavascriptValue(this.RetryToolTip), MessagingUtilities.GetSafeJavascriptValue(this.BusyToolTip), @@ -1210,124 +855,21 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { MessagingUtilities.GetSafeJavascriptValue(this.AuthenticationSucceededToolTip), MessagingUtilities.GetSafeJavascriptValue(this.AuthenticatedAsToolTip), MessagingUtilities.GetSafeJavascriptValue(this.AuthenticationFailedToolTip), - identifierParameterName, - discoveryCallbackResultParameterName, - discoveryErrorCallbackParameterName, - discoveryCallback, + this.AutoPostBack ? "true" : "false", + Page.ClientScript.GetPostBackEventReference(this, null), Environment.NewLine); - startupScript.AppendLine("</script>"); - - Page.ClientScript.RegisterStartupScript(this.GetType(), "ajaxstartup", startupScript.ToString()); - string htmlFormat = @" + ScriptManager.RegisterStartupScript(this, this.GetType(), "ajaxstartup", startupScript.ToString(), true); + if (this.HookFormSubmit) { + string htmlFormat = @" var openidbox = document.getElementsByName('{0}')[0]; if (!openidbox.dnoi_internal.onSubmit()) {{ return false; }} "; - Page.ClientScript.RegisterOnSubmitStatement( - this.GetType(), - "loginvalidation", - string.Format(CultureInfo.InvariantCulture, htmlFormat, this.Name)); - } - - /// <summary> - /// Creates the authentication requests for a given user-supplied Identifier. - /// </summary> - /// <param name="userSuppliedIdentifier">The user supplied identifier.</param> - /// <param name="immediate">A value indicating whether the authentication - /// requests should be initialized for use in invisible iframes for background authentication.</param> - /// <returns>The list of authentication requests, any one of which may be - /// used to determine the user's control of the <see cref="IAuthenticationRequest.ClaimedIdentifier"/>.</returns> - private IEnumerable<IAuthenticationRequest> CreateRequests(string userSuppliedIdentifier, bool immediate) { - var requests = new List<IAuthenticationRequest>(); - - // Approximate the returnTo (either based on the customize property or the page URL) - // so we can use it to help with Realm resolution. - Uri returnToApproximation = this.ReturnToUrl != null ? new Uri(this.RelyingParty.Channel.GetRequestFromContext().UrlBeforeRewriting, this.ReturnToUrl) : this.Page.Request.Url; - - // Resolve the trust root, and swap out the scheme and port if necessary to match the - // return_to URL, since this match is required by OpenId, and the consumer app - // may be using HTTP at some times and HTTPS at others. - UriBuilder realm = OpenIdUtilities.GetResolvedRealm(this.Page, this.RealmUrl, this.RelyingParty.Channel.GetRequestFromContext()); - realm.Scheme = returnToApproximation.Scheme; - realm.Port = returnToApproximation.Port; - - // Initiate openid request - // We use TryParse here to avoid throwing an exception which - // might slip through our validator control if it is disabled. - Realm typedRealm = new Realm(realm); - if (string.IsNullOrEmpty(this.ReturnToUrl)) { - requests.AddRange(this.RelyingParty.CreateRequests(userSuppliedIdentifier, typedRealm)); - } else { - // Since the user actually gave us a return_to value, - // the "approximation" is exactly what we want. - requests.AddRange(this.RelyingParty.CreateRequests(userSuppliedIdentifier, typedRealm, returnToApproximation)); - } - - // Some OPs may be listed multiple times (one with HTTPS and the other with HTTP, for example). - // Since we're gathering OPs to try one after the other, just take the first choice of each OP - // and don't try it multiple times. - requests = RemoveDuplicateEndpoints(requests); - - // Configure each generated request. - int reqIndex = 0; - foreach (var req in requests) { - req.AddCallbackArguments("index", (reqIndex++).ToString(CultureInfo.InvariantCulture)); - - if (req.Provider.IsExtensionSupported<UIRequest>()) { - // Inform the OP that we'll be using a popup window. - req.AddExtension(new UIRequest()); - - // Provide a hint for the client javascript about whether the OP supports the UI extension. - // This is so the window can be made the correct size for the extension. - // If the OP doesn't advertise support for the extension, the javascript will use - // a bigger popup window. - req.AddCallbackArguments("dotnetopenid.popupUISupported", "1"); - } - - // If the ReturnToUrl was explicitly set, we'll need to reset our first parameter - if (string.IsNullOrEmpty(HttpUtility.ParseQueryString(req.ReturnToUrl.Query)["dotnetopenid.userSuppliedIdentifier"])) { - req.AddCallbackArguments("dotnetopenid.userSuppliedIdentifier", userSuppliedIdentifier); - } - - // Our javascript needs to let the user know which endpoint responded. So we force it here. - // This gives us the info even for 1.0 OPs and 2.0 setup_required responses. - req.AddCallbackArguments("dotnetopenid.op_endpoint", req.Provider.Uri.AbsoluteUri); - req.AddCallbackArguments("dotnetopenid.claimed_id", (string)req.ClaimedIdentifier ?? string.Empty); - req.AddCallbackArguments("dotnetopenid.phase", "2"); - if (immediate) { - req.Mode = AuthenticationRequestMode.Immediate; - ((AuthenticationRequest)req).AssociationPreference = AssociationPreference.IfAlreadyEstablished; - } - } - - return requests; - } - - /// <summary> - /// Notifies the user agent via an AJAX response of a completed authentication attempt. - /// </summary> - private void ReportAuthenticationResult() { - Logger.OpenId.InfoFormat("AJAX (iframe) callback from OP: {0}", this.Page.Request.Url); - List<string> assignments = new List<string>(); - - var authResponse = this.RelyingPartyNonVerifying.GetResponse(); - if (authResponse.Status == AuthenticationStatus.Authenticated) { - this.OnUnconfirmedPositiveAssertion(); - foreach (var pair in this.clientScriptExtensions) { - IClientScriptExtensionResponse extension = (IClientScriptExtensionResponse)authResponse.GetExtension(pair.Key); - if (extension == null) { - continue; - } - var positiveResponse = (PositiveAuthenticationResponse)authResponse; - string js = extension.InitializeJavaScriptData(positiveResponse.Response); - if (string.IsNullOrEmpty(js)) { - js = "null"; - } - assignments.Add(pair.Value + " = " + js); - } + Page.ClientScript.RegisterOnSubmitStatement( + this.GetType(), + "loginvalidation", + string.Format(CultureInfo.InvariantCulture, htmlFormat, this.Name)); } - - this.CallbackUserAgentMethod("dnoi_internal.processAuthorizationResult(document.URL)", assignments.ToArray()); } } } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.css b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.css new file mode 100644 index 0000000..bed2e79 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.css @@ -0,0 +1,49 @@ +.OpenIdAjaxTextBox input +{ + margin: 0px; +} + +.OpenIdAjaxTextBox > span +{ + position: absolute; + right: -1px; + top: 2px; +} + +.OpenIdAjaxTextBox input[type=button] +{ + visibility: hidden; + position: absolute; + padding: 0px; + font-size: 8px; + top: 1px; + bottom: 1px; + right: 2px; +} + +.OpenIdAjaxTextBox .yui-split-button span button +{ + font-size: 50%; + font-size: 60%\9; /* the \9 is a hack that causes only IE7/8 to use this value. */ + line-height: 1; + min-height: 1em; + padding-top: 2px; + padding-top: 3px\9; + padding-bottom: 1px; + padding-left: 5px; + height: auto; +} + +.OpenIdAjaxTextBox .yuimenuitem .yuimenuitemlabel +{ + padding-left: 5px; +} + +.OpenIdAjaxTextBox .yuimenuitem .yuimenuitemlabel img +{ + border: 0; + margin-right: 4px; + vertical-align: middle; + width: 16px; + height: 16px; +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.js b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.js index 1078003..65e7ffe 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.js +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.js @@ -1,152 +1,101 @@ //----------------------------------------------------------------------- // <copyright file="OpenIdAjaxTextBox.js" company="Andrew Arnott"> // Copyright (c) Andrew Arnott. All rights reserved. +// This file may be used and redistributed under the terms of the +// Microsoft Public License (Ms-PL) http://opensource.org/licenses/ms-pl.html // </copyright> //----------------------------------------------------------------------- -// Options that can be set on the host page: -//window.openid_visible_iframe = true; // causes the hidden iframe to show up -//window.openid_trace = true; // causes lots of messages - -function trace(msg) { - if (window.openid_trace) { - if (!window.tracediv) { - window.tracediv = document.createElement("ol"); - document.body.appendChild(window.tracediv); - } - var el = document.createElement("li"); - el.appendChild(document.createTextNode(msg)); - window.tracediv.appendChild(el); - //alert(msg); - } -} - -/// <summary>Removes a given element from the array.</summary> -/// <returns>True if the element was in the array, or false if it was not found.</returns> -Array.prototype.remove = function(element) { - function elementToRemoveLast(a, b) { - if (a == element) { return 1; } - if (b == element) { return -1; } - return 0; - } - this.sort(elementToRemoveLast); - if (this[this.length - 1] == element) { - this.pop(); - return true; - } else { - return false; - } -}; - function initAjaxOpenId(box, openid_logo_url, dotnetopenid_logo_url, spinner_url, success_icon_url, failure_icon_url, throttle, timeout, assertionReceivedCode, - loginButtonText, loginButtonToolTip, retryButtonText, retryButtonToolTip, busyToolTip, + loginButtonText, loginButtonToolTip, showLoginPostBackButton, loginPostBackToolTip, + retryButtonText, retryButtonToolTip, busyToolTip, identifierRequiredMessage, loginInProgressMessage, authenticatedByToolTip, authenticatedAsToolTip, authenticationFailedToolTip, - discoverCallback, discoveryFailedCallback) { - box.dnoi_internal = new Object(); + autoPostback, postback) { + box.dnoi_internal = { + postback: postback + }; if (assertionReceivedCode) { - box.dnoi_internal.onauthenticated = function(sender, e) { eval(assertionReceivedCode); } + box.dnoi_internal.onauthenticated = function(sender, e) { eval(assertionReceivedCode); }; } box.dnoi_internal.originalBackground = box.style.background; box.timeout = timeout; - box.dnoi_internal.discoverIdentifier = discoverCallback; - box.dnoi_internal.authenticationRequests = new Array(); - - // The possible authentication results - var authSuccess = new Object(); - var authRefused = new Object(); - var timedOut = new Object(); - - function FrameManager(maxFrames) { - this.queuedWork = new Array(); - this.frames = new Array(); - this.maxFrames = maxFrames; - - /// <summary>Called to queue up some work that will use an iframe as soon as it is available.</summary> - /// <param name="job"> - /// A delegate that must return the url to point to iframe to. - /// Its first parameter is the iframe created to service the request. - /// It will only be called when the work actually begins. - /// </param> - this.enqueueWork = function(job) { - // Assign an iframe to this task immediately if there is one available. - if (this.frames.length < this.maxFrames) { - this.createIFrame(job); - } else { - this.queuedWork.unshift(job); - } - }; - /// <summary>Clears the job queue and immediately closes all iframes.</summary> - this.cancelAllWork = function() { - trace('Canceling all open and pending iframes.'); - while (this.queuedWork.pop()); - this.closeFrames(); - }; - - /// <summary>An event fired when a frame is closing.</summary> - this.onJobCompleted = function() { - // If there is a job in the queue, go ahead and start it up. - if (job = this.queuedWork.pop()) { - this.createIFrame(job); - } - } - - this.createIFrame = function(job) { - var iframe = document.createElement("iframe"); - if (!window.openid_visible_iframe) { - iframe.setAttribute("width", 0); - iframe.setAttribute("height", 0); - iframe.setAttribute("style", "display: none"); - } - iframe.setAttribute("src", job(iframe)); - iframe.openidBox = box; - box.parentNode.insertBefore(iframe, box); - this.frames.push(iframe); - return iframe; - }; - this.closeFrames = function() { - if (this.frames.length == 0) { return false; } - for (var i = 0; i < this.frames.length; i++) { - if (this.frames[i].parentNode) { this.frames[i].parentNode.removeChild(this.frames[i]); } - } - while (this.frames.length > 0) { this.frames.pop(); } - return true; - }; - this.closeFrame = function(frame) { - if (frame.parentNode) { frame.parentNode.removeChild(frame); } - var removed = this.frames.remove(frame); - this.onJobCompleted(); - return removed; - }; - } - - box.dnoi_internal.authenticationIFrames = new FrameManager(throttle); + box.dnoi_internal.authenticationIFrames = new window.dnoa_internal.FrameManager(throttle); box.dnoi_internal.constructButton = function(text, tooltip, onclick) { var button = document.createElement('input'); button.textContent = text; // Mozilla button.value = text; // IE button.type = 'button'; - button.title = tooltip != null ? tooltip : ''; + button.title = tooltip || ''; button.onclick = onclick; - button.style.visibility = 'hidden'; - button.style.position = 'absolute'; - button.style.padding = "0px"; - button.style.fontSize = '8px'; - button.style.top = "1px"; - button.style.bottom = "1px"; - button.style.right = "2px"; box.parentNode.appendChild(button); return button; - } + }; + + box.dnoi_internal.constructSplitButton = function(text, tooltip, onclick, menu) { + var htmlButton = box.dnoi_internal.constructButton(text, tooltip, onclick); + + if (!box.parentNode.className || box.parentNode.className.indexOf(' yui-skin-sam') < 0) { + box.parentNode.className = (box.parentNode.className || '') + ' yui-skin-sam'; + } + + var splitButton = new YAHOO.widget.Button(htmlButton, { + type: 'split', + menu: menu + }); + + splitButton.on('click', onclick); + + return splitButton; + }; + + box.dnoi_internal.createLoginPostBackButton = function() { + var postback = function() { + var discoveryResult = window.dnoa_internal.discoveryResults[box.value]; + var respondingEndpoint = discoveryResult.findSuccessfulRequest(); + box.dnoi_internal.postback(discoveryResult, respondingEndpoint, respondingEndpoint.extensionResponses, { background: false }); + }; + var button = box.dnoi_internal.constructButton(loginButtonText, loginPostBackToolTip, postback); + button.style.visibility = 'visible'; + button.destroy = function() { + button.parentNode.removeChild(button); + }; + + return button; + }; + + box.dnoi_internal.createLoginButton = function(providers) { + var onMenuItemClick = function(p_sType, p_aArgs, p_oItem) { + var selectedProvider = (p_oItem && p_oItem.value) ? p_oItem.value : providers[0].value; + selectedProvider.loginPopup(); + return false; + }; + + for (var i = 0; i < providers.length; i++) { + providers[i].onclick = { fn: onMenuItemClick }; + } + + // We'll use the split button if we have more than one Provider, and the YUI library is available. + if (providers.length > 1 && YAHOO && YAHOO.widget && YAHOO.widget.Button) { + return box.dnoi_internal.constructSplitButton(loginButtonText, loginButtonToolTip, onMenuItemClick, providers); + } else { + var button = box.dnoi_internal.constructButton(loginButtonText, loginButtonToolTip, onMenuItemClick); + button.style.visibility = 'visible'; + button.destroy = function() { + button.parentNode.removeChild(button); + }; + return button; + } + }; box.dnoi_internal.constructIcon = function(imageUrl, tooltip, rightSide, visible, height) { var icon = document.createElement('img'); icon.src = imageUrl; - icon.title = tooltip != null ? tooltip : ''; + icon.title = tooltip || ''; icon.originalTitle = icon.title; if (!visible) { icon.style.visibility = 'hidden'; @@ -164,7 +113,7 @@ function initAjaxOpenId(box, openid_logo_url, dotnetopenid_logo_url, spinner_url } box.parentNode.appendChild(icon); return icon; - } + }; box.dnoi_internal.prefetchImage = function(imageUrl) { var img = document.createElement('img'); @@ -172,15 +121,15 @@ function initAjaxOpenId(box, openid_logo_url, dotnetopenid_logo_url, spinner_url img.style.display = 'none'; box.parentNode.appendChild(img); return img; - } + }; function findParentForm(element) { - if (element == null || element.nodeName == "FORM") { + if (!element || element.nodeName == "FORM") { return element; } return findParentForm(element.parentNode); - }; + } box.parentForm = findParentForm(box); @@ -196,22 +145,11 @@ function initAjaxOpenId(box, openid_logo_url, dotnetopenid_logo_url, spinner_url hiddenField.setAttribute("type", "hidden"); box.parentForm.appendChild(hiddenField); return hiddenField; - }; + } - box.dnoi_internal.loginButton = box.dnoi_internal.constructButton(loginButtonText, loginButtonToolTip, function() { - var discoveryInfo = box.dnoi_internal.authenticationRequests[box.lastDiscoveredIdentifier]; - if (discoveryInfo == null) { - trace('Ooops! Somehow the login button click event was invoked, but no openid discovery information for ' + box.lastDiscoveredIdentifier + ' is available.'); - return; - } - // The login button always sends a setup message to the first OP. - var selectedProvider = discoveryInfo[0]; - selectedProvider.trySetup(); - return false; - }); box.dnoi_internal.retryButton = box.dnoi_internal.constructButton(retryButtonText, retryButtonToolTip, function() { box.timeout += 5000; // give the retry attempt 5s longer than the last attempt - box.dnoi_internal.performDiscovery(box.value); + box.dnoi_internal.performDiscovery(); return false; }); box.dnoi_internal.openid_logo = box.dnoi_internal.constructIcon(openid_logo_url, null, false, true); @@ -219,24 +157,32 @@ function initAjaxOpenId(box, openid_logo_url, dotnetopenid_logo_url, spinner_url box.dnoi_internal.op_logo.style.maxWidth = '16px'; box.dnoi_internal.spinner = box.dnoi_internal.constructIcon(spinner_url, busyToolTip, true); box.dnoi_internal.success_icon = box.dnoi_internal.constructIcon(success_icon_url, authenticatedAsToolTip, true); - //box.dnoi_internal.failure_icon = box.dnoi_internal.constructIcon(failure_icon_url, authenticationFailedToolTip, true); + box.dnoi_internal.failure_icon = box.dnoi_internal.constructIcon(failure_icon_url, authenticationFailedToolTip, true); // Disable the display of the DotNetOpenId logo //box.dnoi_internal.dnoi_logo = box.dnoi_internal.constructIcon(dotnetopenid_logo_url); box.dnoi_internal.dnoi_logo = box.dnoi_internal.openid_logo; - box.dnoi_internal.setVisualCue = function(state, authenticatedBy, authenticatedAs) { + box.dnoi_internal.setVisualCue = function(state, authenticatedBy, authenticatedAs, providers, errorMessage) { box.dnoi_internal.openid_logo.style.visibility = 'hidden'; box.dnoi_internal.dnoi_logo.style.visibility = 'hidden'; box.dnoi_internal.op_logo.style.visibility = 'hidden'; box.dnoi_internal.openid_logo.title = box.dnoi_internal.openid_logo.originalTitle; box.dnoi_internal.spinner.style.visibility = 'hidden'; box.dnoi_internal.success_icon.style.visibility = 'hidden'; - // box.dnoi_internal.failure_icon.style.visibility = 'hidden'; - box.dnoi_internal.loginButton.style.visibility = 'hidden'; + box.dnoi_internal.failure_icon.style.visibility = 'hidden'; box.dnoi_internal.retryButton.style.visibility = 'hidden'; + if (box.dnoi_internal.loginButton) { + box.dnoi_internal.loginButton.destroy(); + box.dnoi_internal.loginButton = null; + } + if (box.dnoi_internal.postbackLoginButton) { + box.dnoi_internal.postbackLoginButton.destroy(); + box.dnoi_internal.postbackLoginButton = null; + } box.title = ''; box.dnoi_internal.state = state; + var opLogo; if (state == "discovering") { box.dnoi_internal.dnoi_logo.style.visibility = 'visible'; box.dnoi_internal.spinner.style.visibility = 'visible'; @@ -244,42 +190,55 @@ function initAjaxOpenId(box, openid_logo_url, dotnetopenid_logo_url, spinner_url box.title = ''; window.status = "Discovering OpenID Identifier '" + box.value + "'..."; } else if (state == "authenticated") { - var opLogo = box.dnoi_internal.deriveOPFavIcon(); + opLogo = box.dnoi_internal.deriveOPFavIcon(); if (opLogo) { box.dnoi_internal.op_logo.src = opLogo; box.dnoi_internal.op_logo.style.visibility = 'visible'; box.dnoi_internal.op_logo.title = box.dnoi_internal.op_logo.originalTitle.replace('{0}', authenticatedBy.getHost()); } - trace("OP icon size: " + box.dnoi_internal.op_logo.fileSize); - if (opLogo == null || box.dnoi_internal.op_logo.fileSize == -1 /*IE*/ || box.dnoi_internal.op_logo.fileSize === undefined /* FF */) { + //trace("OP icon size: " + box.dnoi_internal.op_logo.fileSize); + // The filesize check just doesn't seem to work any more. + if (!opLogo) {// || box.dnoi_internal.op_logo.fileSize == -1 /*IE*/ || box.dnoi_internal.op_logo.fileSize === undefined /* FF */) { trace('recovering from missing OP icon'); box.dnoi_internal.op_logo.style.visibility = 'hidden'; box.dnoi_internal.openid_logo.style.visibility = 'visible'; box.dnoi_internal.openid_logo.title = box.dnoi_internal.op_logo.originalTitle.replace('{0}', authenticatedBy.getHost()); } - box.dnoi_internal.success_icon.style.visibility = 'visible'; - box.dnoi_internal.success_icon.title = box.dnoi_internal.success_icon.originalTitle.replace('{0}', authenticatedAs); + if (showLoginPostBackButton) { + box.dnoi_internal.postbackLoginButton = box.dnoi_internal.createLoginPostBackButton(); + } else { + box.dnoi_internal.success_icon.style.visibility = 'visible'; + box.dnoi_internal.success_icon.title = box.dnoi_internal.success_icon.originalTitle.replace('{0}', authenticatedAs); + } box.title = box.dnoi_internal.claimedIdentifier; - window.status = "Authenticated as " + box.value; + window.status = "Authenticated as " + authenticatedAs; } else if (state == "setup") { - var opLogo = box.dnoi_internal.deriveOPFavIcon(); + opLogo = box.dnoi_internal.deriveOPFavIcon(); if (opLogo) { box.dnoi_internal.op_logo.src = opLogo; box.dnoi_internal.op_logo.style.visibility = 'visible'; } else { box.dnoi_internal.openid_logo.style.visibility = 'visible'; } - box.dnoi_internal.loginButton.style.visibility = 'visible'; + + box.dnoi_internal.loginButton = box.dnoi_internal.createLoginButton(providers); + box.dnoi_internal.claimedIdentifier = null; - window.status = "Authentication requires setup."; + window.status = "Authentication requires user interaction."; } else if (state == "failed") { box.dnoi_internal.openid_logo.style.visibility = 'visible'; - //box.dnoi_internal.failure_icon.style.visibility = 'visible'; box.dnoi_internal.retryButton.style.visibility = 'visible'; box.dnoi_internal.claimedIdentifier = null; window.status = authenticationFailedToolTip; box.title = authenticationFailedToolTip; - } else if (state == '' || state == null) { + } else if (state == "failednoretry") { + box.dnoi_internal.failure_icon.title = errorMessage; + box.dnoi_internal.failure_icon.style.visibility = 'visible'; + box.dnoi_internal.openid_logo.style.visibility = 'visible'; + box.dnoi_internal.claimedIdentifier = null; + window.status = errorMessage; + box.title = errorMessage; + } else if (state == '' || !state) { box.dnoi_internal.openid_logo.style.visibility = 'visible'; box.title = ''; box.dnoi_internal.claimedIdentifier = null; @@ -288,35 +247,39 @@ function initAjaxOpenId(box, openid_logo_url, dotnetopenid_logo_url, spinner_url box.dnoi_internal.claimedIdentifier = null; trace('unrecognized state ' + state); } - } + + if (box.onStateChanged) { + box.onStateChanged(state); + } + }; box.dnoi_internal.isBusy = function() { - var lastDiscovery = box.dnoi_internal.authenticationRequests[box.lastDiscoveredIdentifier]; + var lastDiscovery = window.dnoa_internal.discoveryResults[box.lastDiscoveredIdentifier]; return box.dnoi_internal.state == 'discovering' || (lastDiscovery && lastDiscovery.busy()); }; box.dnoi_internal.canAttemptLogin = function() { - if (box.value.length == 0) return false; - if (box.dnoi_internal.authenticationRequests[box.value] == null) return false; - if (box.dnoi_internal.state == 'failed') return false; + if (box.value.length === 0) { return false; } + if (!window.dnoa_internal.discoveryResults[box.value]) { return false; } + if (box.dnoi_internal.state == 'failed') { return false; } return true; }; box.dnoi_internal.getUserSuppliedIdentifierResults = function() { - return box.dnoi_internal.authenticationRequests[box.value]; - } + return window.dnoa_internal.discoveryResults[box.value]; + }; box.dnoi_internal.isAuthenticated = function() { var results = box.dnoi_internal.getUserSuppliedIdentifierResults(); - return results != null && results.findSuccessfulRequest() != null; - } + return results && results.findSuccessfulRequest(); + }; box.dnoi_internal.onSubmit = function() { var hiddenField = findOrCreateHiddenField(); if (box.dnoi_internal.isAuthenticated()) { // stick the result in a hidden field so the RP can verify it - hiddenField.setAttribute("value", box.dnoi_internal.authenticationRequests[box.value].successAuthData); + hiddenField.setAttribute("value", window.dnoa_internal.discoveryResults[box.value].successAuthData); } else { hiddenField.setAttribute("value", ''); if (box.dnoi_internal.isBusy()) { @@ -332,7 +295,7 @@ function initAjaxOpenId(box, openid_logo_url, dotnetopenid_logo_url, spinner_url // after leaving a note for ourselves to automatically click submit // when login is complete. box.dnoi_internal.submitPending = box.dnoi_internal.submitButtonJustClicked; - if (box.dnoi_internal.submitPending == null) { + if (box.dnoi_internal.submitPending === null) { box.dnoi_internal.submitPending = true; } box.dnoi_internal.loginButton.onclick(); @@ -380,398 +343,304 @@ function initAjaxOpenId(box, openid_logo_url, dotnetopenid_logo_url, spinner_url /// <summary> /// Returns the URL of the authenticating OP's logo so it can be displayed to the user. /// </summary> - box.dnoi_internal.deriveOPFavIcon = function() { - var response = box.dnoi_internal.getUserSuppliedIdentifierResults().successAuthData; - if (!response || response.length == 0) return; - var authResult = new Uri(response); - var opUri; - if (authResult.getQueryArgValue("openid.op_endpoint")) { - opUri = new Uri(authResult.getQueryArgValue("openid.op_endpoint")); - } if (authResult.getQueryArgValue("dotnetopenid.op_endpoint")) { - opUri = new Uri(authResult.getQueryArgValue("dotnetopenid.op_endpoint")); - } else if (authResult.getQueryArgValue("openid.user_setup_url")) { - opUri = new Uri(authResult.getQueryArgValue("openid.user_setup_url")); - } else return null; - var favicon = opUri.getAuthority() + "/favicon.ico"; - return favicon; - }; - - box.dnoi_internal.createDiscoveryInfo = function(discoveryInfo, identifier) { - this.identifier = identifier; - // The claimed identifier may be null if the user provided an OP Identifier. - this.claimedIdentifier = discoveryInfo.claimedIdentifier; - trace('Discovered claimed identifier: ' + this.claimedIdentifier); - - // Add extra tracking bits and behaviors. - this.findByEndpoint = function(opEndpoint) { - for (var i = 0; i < this.length; i++) { - if (this[i].endpoint == opEndpoint) { - return this[i]; - } + /// <param name="opUri">The OP Endpoint, if known.</param> + box.dnoi_internal.deriveOPFavIcon = function(opUri) { + if (!opUri) { + var idresults = box.dnoi_internal.getUserSuppliedIdentifierResults(); + var response = idresults ? idresults.successAuthData : null; + if (!response || response.length === 0) { + trace('No favicon because no successAuthData.'); + return; } - }; - this.findSuccessfulRequest = function() { - for (var i = 0; i < this.length; i++) { - if (this[i].result == authSuccess) { - return this[i]; - } - } - }; - this.busy = function() { - for (var i = 0; i < this.length; i++) { - if (this[i].busy()) { - return true; - } - } - }; - this.abortAll = function() { - // Abort all other asynchronous authentication attempts that may be in progress. - box.dnoi_internal.authenticationIFrames.cancelAllWork(); - for (var i = 0; i < this.length; i++) { - this[i].abort(); - } - }; - this.tryImmediate = function() { - if (this.length > 0) { - for (var i = 0; i < this.length; i++) { - box.dnoi_internal.authenticationIFrames.enqueueWork(this[i].tryImmediate); - } + var authResult = new window.dnoa_internal.Uri(response); + if (authResult.getQueryArgValue("openid.op_endpoint")) { + opUri = new window.dnoa_internal.Uri(authResult.getQueryArgValue("openid.op_endpoint")); + } else if (authResult.getQueryArgValue("dnoa.op_endpoint")) { + opUri = new window.dnoa_internal.Uri(authResult.getQueryArgValue("dnoa.op_endpoint")); + } else if (authResult.getQueryArgValue("openid.user_setup_url")) { + opUri = new window.dnoa_internal.Uri(authResult.getQueryArgValue("openid.user_setup_url")); } else { - box.dnoi_internal.discoveryFailed(null, this.identifier); + return null; } - }; - - this.length = discoveryInfo.requests.length; - for (var i = 0; i < discoveryInfo.requests.length; i++) { - this[i] = new box.dnoi_internal.createTrackingRequest(discoveryInfo.requests[i], identifier); } + var favicon = opUri.getAuthority() + "/favicon.ico"; + trace('Guessing favicon location of: ' + favicon); + return favicon; }; - box.dnoi_internal.createTrackingRequest = function(requestInfo, identifier) { - // It's possible during a postback that discovered request URLs are not available. - this.immediate = requestInfo.immediate ? new Uri(requestInfo.immediate) : null; - this.setup = requestInfo.setup ? new Uri(requestInfo.setup) : null; - this.endpoint = new Uri(requestInfo.endpoint); - this.identifier = identifier; - var self = this; // closure so that delegates have the right instance - - this.host = self.endpoint.getHost(); + /***************************************** + * Event Handlers + *****************************************/ - this.getDiscoveryInfo = function() { - return box.dnoi_internal.authenticationRequests[self.identifier]; + window.dnoa_internal.addDiscoveryStarted(function(identifier) { + if (identifier == box.value) { + box.dnoi_internal.setVisualCue('discovering'); } - - this.busy = function() { - return self.iframe != null || self.popup != null; - }; - - this.completeAttempt = function() { - if (!self.busy()) return false; - if (self.iframe) { - trace('iframe hosting ' + self.endpoint + ' now CLOSING.'); - box.dnoi_internal.authenticationIFrames.closeFrame(self.iframe); - self.iframe = null; - } - if (self.popup) { - self.popup.close(); - self.popup = null; - } - if (self.timeout) { - window.clearTimeout(self.timeout); - self.timeout = null; - } - - if (!self.getDiscoveryInfo().busy() && self.getDiscoveryInfo().findSuccessfulRequest() == null) { - trace('No asynchronous authentication attempt is in progress. Display setup view.'); - // visual cue that auth failed - box.dnoi_internal.setVisualCue('setup'); - } - - return true; - }; - - this.authenticationTimedOut = function() { - if (self.completeAttempt()) { - trace(self.host + " timed out"); - self.result = timedOut; - } - }; - this.authSuccess = function(authUri) { - if (self.completeAttempt()) { - trace(self.host + " authenticated!"); - self.result = authSuccess; - self.response = authUri; - box.dnoi_internal.authenticationRequests[self.identifier].abortAll(); - } - }; - this.authFailed = function() { - if (self.completeAttempt()) { - //trace(self.host + " failed authentication"); - self.result = authRefused; - } - }; - this.abort = function() { - if (self.completeAttempt()) { - trace(self.host + " aborted"); - // leave the result as whatever it was before. + }, box); + + window.dnoa_internal.addDiscoverySuccess(function(identifier, discoveryResult, state) { + if (identifier == box.value && (box.dnoi_internal.state == 'discovering' || !box.dnoi_internal.state)) { + // Start pre-fetching the OP favicons + for (var i = 0; i < discoveryResult.length; i++) { + var favicon = box.dnoi_internal.deriveOPFavIcon(discoveryResult[i].endpoint); + if (favicon) { + trace('Prefetching ' + favicon); + box.dnoi_internal.prefetchImage(favicon); + } } - }; - - this.tryImmediate = function(iframe) { - self.abort(); // ensure no concurrent attempts - self.timeout = setTimeout(function() { self.authenticationTimedOut(); }, box.timeout); - trace('iframe hosting ' + self.endpoint + ' now OPENING.'); - self.iframe = iframe; - //trace('initiating auth attempt with: ' + self.immediate); - return self.immediate.toString(); - }; - this.trySetup = function() { - self.abort(); // ensure no concurrent attempts - window.waiting_openidBox = box; - var width = 800; - var height = 600; - if (self.setup.getQueryArgValue("openid.return_to").indexOf("dotnetopenid.popupUISupported") >= 0) { - width = 450; - height = 500; + if (discoveryResult.length > 0) { + discoveryResult.loginBackground( + box.dnoi_internal.authenticationIFrames, + null, + null, + null, + box.timeout); + } else { + // discovery completed successfully -- it just didn't yield any service endpoints. + box.dnoi_internal.setVisualCue('failednoretry', null, null, null, discoveryResult.error); + if (discoveryResult.error) { box.title = discoveryResult.error; } } + } + }, box); - var left = (screen.width - width) / 2; - var top = (screen.height - height) / 2; - self.popup = window.open(self.setup, 'opLogin', 'status=0,toolbar=0,location=1,resizable=1,scrollbars=1,left=' + left + ',top=' + top + ',width=' + width + ',height=' + height); - - // If the OP supports the UI extension it MAY close its own window - // for a negative assertion. We must be able to recover from that scenario. - var localSelf = self; - self.popupCloseChecker = window.setInterval(function() { - if (localSelf.popup && localSelf.popup.closed) { - // So the user canceled and the window closed. - // It turns out we hae nothing special to do. - // If we were graying out the entire page while the child window was up, - // we would probably revert that here. - trace('User or OP canceled by closing the window.'); - window.clearInterval(localSelf.popupCloseChecker); - localSelf.popup = null; - } - }, 250); - }; - }; - - /***************************************** - * Flow - *****************************************/ - - /// <summary>Called to initiate discovery on some identifier.</summary> - box.dnoi_internal.performDiscovery = function(identifier) { - box.dnoi_internal.authenticationIFrames.closeFrames(); - box.dnoi_internal.setVisualCue('discovering'); - box.lastDiscoveredIdentifier = identifier; - box.dnoi_internal.discoverIdentifier(identifier, box.dnoi_internal.discoveryResult, box.dnoi_internal.discoveryFailed); - }; - - /// <summary>Callback that is invoked when discovery fails.</summary> - box.dnoi_internal.discoveryFailed = function(message, identifier) { - box.dnoi_internal.setVisualCue('failed'); - if (message) { box.title = message; } - } - - /// <summary>Callback that is invoked when discovery results are available.</summary> - /// <param name="discoveryResult">The JSON object containing the OpenID auth requests.</param> - /// <param name="identifier">The identifier that discovery was performed on.</param> - box.dnoi_internal.discoveryResult = function(discoveryResult, identifier) { - // Deserialize the JSON object and store the result if it was a successful discovery. - discoveryResult = eval('(' + discoveryResult + ')'); - // Store the discovery results and added behavior for later use. - box.dnoi_internal.authenticationRequests[identifier] = discoveryBehavior = new box.dnoi_internal.createDiscoveryInfo(discoveryResult, identifier); - - // Only act on the discovery event if we're still interested in the result. - // If the user already changed the identifier since discovery was initiated, - // we aren't interested in it any more. - if (identifier == box.lastDiscoveredIdentifier) { - discoveryBehavior.tryImmediate(); + window.dnoa_internal.addDiscoveryFailed(function(identifier, message) { + if (identifier == box.value) { + box.dnoi_internal.setVisualCue('failed'); + if (message) { box.title = message; } } - } + }, box); - /// <summary>Invoked by RP web server when an authentication has completed.</summary> - /// <remarks>The duty of this method is to distribute the notification to the appropriate tracking object.</remarks> - box.dnoi_internal.processAuthorizationResult = function(resultUrl) { - self.waiting_openidBox = null; - //trace('processAuthorizationResult ' + resultUrl); - var resultUri = new Uri(resultUrl); - - // Find the tracking object responsible for this request. - var discoveryInfo = box.dnoi_internal.authenticationRequests[resultUri.getQueryArgValue('dotnetopenid.userSuppliedIdentifier')]; - if (discoveryInfo == null) { - trace('processAuthorizationResult called but no userSuppliedIdentifier parameter was found. Exiting function.'); - return; + window.dnoa_internal.addAuthStarted(function(discoveryResult, serviceEndpoint, state) { + if (discoveryResult.userSuppliedIdentifier == box.value) { + box.dnoi_internal.setVisualCue('discovering'); } - var opEndpoint = resultUri.getQueryArgValue("openid.op_endpoint") ? resultUri.getQueryArgValue("openid.op_endpoint") : resultUri.getQueryArgValue("dotnetopenid.op_endpoint"); - var tracker = discoveryInfo.findByEndpoint(opEndpoint); - //trace('Auth result for ' + tracker.host + ' received:\n' + resultUrl); - - if (isAuthSuccessful(resultUri)) { - tracker.authSuccess(resultUri); - - discoveryInfo.successAuthData = resultUrl; - var claimed_id = resultUri.getQueryArgValue("openid.claimed_id"); - if (claimed_id && claimed_id != discoveryInfo.claimedIdentifier) { - discoveryInfo.claimedIdentifier = resultUri.getQueryArgValue("openid.claimed_id"); - trace('Authenticated as ' + claimed_id); - } + }, box); + window.dnoa_internal.addAuthSuccess(function(discoveryResult, serviceEndpoint, extensionResponses, state) { + if (discoveryResult.userSuppliedIdentifier == box.value) { // visual cue that auth was successful - box.dnoi_internal.claimedIdentifier = discoveryInfo.claimedIdentifier; - box.dnoi_internal.setVisualCue('authenticated', tracker.endpoint, discoveryInfo.claimedIdentifier); + var parsedPositiveAssertion = new window.dnoa_internal.PositiveAssertion(discoveryResult.successAuthData); + box.dnoi_internal.claimedIdentifier = parsedPositiveAssertion.claimedIdentifier; + + // If the OP doesn't support delegation, "correct" the identifier the user entered + // so he realizes his identity didn't stick. But don't change out OP Identifiers. + if (discoveryResult.claimedIdentifier && discoveryResult.claimedIdentifier != parsedPositiveAssertion.claimedIdentifier) { + box.value = parsedPositiveAssertion.claimedIdentifier; + box.lastDiscoveredIdentifier = box.value; + + // Also inject a fake discovery result for this new identifier to keep the UI from performing + // discovery on the new identifier (the RP will perform the necessary verification server-side). + if (!window.dnoa_internal.discoveryResults[box.value]) { + // We must make sure that the only service endpoint from the earlier discovery that + // is copied over is the one that sent the assertion just now. Deep clone, then strip + // out the other SEPs. + window.dnoa_internal.discoveryResults[box.value] = discoveryResult.cloneWithOneServiceEndpoint(serviceEndpoint); + } + } + box.dnoi_internal.setVisualCue('authenticated', parsedPositiveAssertion.endpoint, parsedPositiveAssertion.claimedIdentifier); if (box.dnoi_internal.onauthenticated) { - box.dnoi_internal.onauthenticated(box); + box.dnoi_internal.onauthenticated(box, extensionResponses); } - if (box.dnoi_internal.submitPending) { + + if (showLoginPostBackButton && !state.background) { + box.dnoi_internal.postback(discoveryResult, serviceEndpoint, extensionResponses, state); + } else if (box.dnoi_internal.submitPending) { // We submit the form BEFORE resetting the submitPending so // the submit handler knows we've already tried this route. - if (box.dnoi_internal.submitPending == true) { + if (box.dnoi_internal.submitPending === true) { box.parentForm.submit(); } else { box.dnoi_internal.submitPending.click(); } + + box.dnoi_internal.submitPending = null; + } else if (!state.deserialized && autoPostback) { + // as long as this is a fresh auth response, postback to the server if configured to do so. + box.dnoi_internal.postback(discoveryResult, serviceEndpoint, extensionResponses, state); } - } else { - tracker.authFailed(); } + }, box); - box.dnoi_internal.submitPending = null; - }; + window.dnoa_internal.addAuthFailed(function(discoveryResult, serviceEndpoint, state) { + if (discoveryResult.userSuppliedIdentifier == box.value) { + box.dnoi_internal.submitPending = null; + if (!serviceEndpoint || !state.background) { // if the last service endpoint just turned the user down + box.dnoi_internal.displayLoginButton(discoveryResult); + } + } + }, box); + + window.dnoa_internal.addAuthCleared(function(discoveryResult, serviceEndpoint) { + if (discoveryResult.userSuppliedIdentifier == box.value) { + if (!discoveryResult.findSuccessfulRequest()) { + // attempt to renew the positive assertion. + discoveryResult.loginBackground( + box.dnoi_internal.authenticationIFrames, + null, + null, + null, + box.timeout); + } + } + }, box); - function isAuthSuccessful(resultUri) { - if (isOpenID2Response(resultUri)) { - return resultUri.getQueryArgValue("openid.mode") == "id_res"; - } else { - return resultUri.getQueryArgValue("openid.mode") == "id_res" && !resultUri.containsQueryArg("openid.user_setup_url"); + /***************************************** + * Flow + *****************************************/ + + box.dnoi_internal.displayLoginButton = function(discoveryResult) { + trace('No asynchronous authentication attempt is in progress. Display setup view.'); + var providers = []; + for (var i = 0; i < discoveryResult.length; i++) { + var favicon = box.dnoi_internal.deriveOPFavIcon(discoveryResult[i].endpoint); + var img = '<img src="' + favicon + '" />'; + providers.push({ text: img + discoveryResult[i].host, value: discoveryResult[i] }); } + + // visual cue that auth failed + box.dnoi_internal.setVisualCue('setup', null, null, providers); }; - function isOpenID2Response(resultUri) { - return resultUri.containsQueryArg("openid.ns"); + /// <summary>Called to initiate discovery on some identifier.</summary> + box.dnoi_internal.performDiscovery = function() { + box.dnoi_internal.authenticationIFrames.closeFrames(); + box.lastDiscoveredIdentifier = box.value; + var openid = new window.OpenIdIdentifier(box.value); + openid.discover(); }; box.onblur = function(event) { - var discoveryInfo = box.dnoi_internal.authenticationRequests[box.value]; - if (discoveryInfo == null) { + if (box.lastDiscoveredIdentifier != box.value || !box.dnoi_internal.state) { if (box.value.length > 0) { - box.dnoi_internal.performDiscovery(box.value); + box.dnoi_internal.resetAndDiscover(); } else { box.dnoi_internal.setVisualCue(); } - } else { - if ((priorSuccess = discoveryInfo.findSuccessfulRequest())) { - box.dnoi_internal.setVisualCue('authenticated', priorSuccess.endpoint, discoveryInfo.claimedIdentifier); - } else { - discoveryInfo.tryImmediate(); - } } - return true; - }; - box.onkeyup = function(event) { - box.dnoi_internal.setVisualCue(); + return true; }; - box.getClaimedIdentifier = function() { return box.dnoi_internal.claimedIdentifier; }; - - // Restore a previously achieved state (from pre-postback) if it is given. - var oldAuth = findOrCreateHiddenField().value; - if (oldAuth.length > 0) { - var oldAuthResult = new Uri(oldAuth); - // The control ensures that we ALWAYS have an OpenID 2.0-style claimed_id attribute, even against - // 1.0 Providers via the return_to URL mechanism. - var claimedId = oldAuthResult.getQueryArgValue("dotnetopenid.claimed_id"); - var endpoint = oldAuthResult.getQueryArgValue("dotnetopenid.op_endpoint"); - // We weren't given a full discovery history, but we can spoof this much from the - // authentication assertion. - box.dnoi_internal.authenticationRequests[box.value] = new box.dnoi_internal.createDiscoveryInfo({ - claimedIdentifier: claimedId, - requests: [{ endpoint: endpoint }] - }, box.value); - - box.dnoi_internal.processAuthorizationResult(oldAuthResult.toString()); - } -} + //{ + var rate = NaN; + var lastValue = box.value; + var keyPresses = 0; + var startTime = null; + var lastKeyPress = null; + var discoveryTimer; + + function cancelTimer() { + if (discoveryTimer) { + trace('canceling timer', 'gray'); + clearTimeout(discoveryTimer); + discoveryTimer = null; + } + } -function Uri(url) { - this.originalUri = url; + function identifierSanityCheck(id) { + return id.match("^[=@+$!(].+|.*?\\..*[^\\.]|\\w+://.+"); + } - this.toString = function() { - return this.originalUri; - }; + function discover() { + cancelTimer(); + trace('typist discovery candidate', 'gray'); + if (identifierSanityCheck(box.value)) { + trace('typist discovery begun', 'gray'); + box.dnoi_internal.performDiscovery(); + } else { + trace('typist discovery canceled due to incomplete identifier.', 'gray'); + } + } - this.getAuthority = function() { - var authority = this.getScheme() + "://" + this.getHost(); - return authority; - } + function reset() { + keyPresses = 0; + startTime = null; + rate = NaN; + trace('resetting state', 'gray'); + } - this.getHost = function() { - var hostStartIdx = this.originalUri.indexOf("://") + 3; - var hostEndIndex = this.originalUri.indexOf("/", hostStartIdx); - if (hostEndIndex < 0) hostEndIndex = this.originalUri.length; - var host = this.originalUri.substr(hostStartIdx, hostEndIndex - hostStartIdx); - return host; - } + box.dnoi_internal.resetAndDiscover = function() { + reset(); + discover(); + }; - this.getScheme = function() { - var schemeStartIdx = this.indexOf("://"); - return this.originalUri.substr(this.originalUri, schemeStartIdx); - } + box.onkeyup = function(e) { + e = e || window.event; // for IE - this.trimFragment = function() { - var hashmark = this.originalUri.indexOf('#'); - if (hashmark >= 0) { - return new Uri(this.originalUri.substr(0, hashmark)); - } - return this; - }; + if (new Date() - lastKeyPress > 3000) { + // the user seems to have altogether stopped typing, + // so reset our typist speed detector. + reset(); + } + lastKeyPress = new Date(); + + var newValue = box.value; + if (e.keyCode == 13) { + if (box.dnoi_internal.state === 'setup') { + box.dnoi_internal.loginButton.click(); + } else if (box.dnoi_internal.postbackLoginButton) { + box.dnoi_internal.postbackLoginButton.click(); + } else { + discover(); + } + } else { + if (lastValue != newValue && newValue != box.lastDiscoveredIdentifier) { + box.dnoi_internal.setVisualCue(); + if (newValue.length === 0) { + reset(); + } else if (Math.abs((lastValue || '').length - newValue.length) > 1) { + // One key press is responsible for multiple character changes. + // The user may have pasted in his identifier in which case + // we want to begin discovery immediately. + trace(newValue + ': paste detected (old value ' + lastValue + ')', 'gray'); + discover(); + } else { + keyPresses++; + var timeout = 3000; // timeout to use if we don't have enough keying to figure out type rate + if (startTime === null) { + startTime = new Date(); + } else if (keyPresses > 1) { + cancelTimer(); + rate = (new Date() - startTime) / keyPresses; + var minTimeout = 300; + var maxTimeout = 3000; + var typistFactor = 5; + timeout = Math.max(minTimeout, Math.min(rate * typistFactor, maxTimeout)); + } - this.appendQueryVariable = function(name, value) { - var pair = encodeURI(name) + "=" + encodeURI(value); - if (this.originalUri.indexOf('?') >= 0) { - this.originalUri = this.originalUri + "&" + pair; - } else { - this.originalUri = this.originalUri + "?" + pair; - } - }; + trace(newValue + ': setting timer for ' + timeout, 'gray'); + discoveryTimer = setTimeout(discover, timeout); + } + } + } - function KeyValuePair(key, value) { - this.key = key; - this.value = value; - }; + trace(newValue + ': updating lastValue', 'gray'); + lastValue = newValue; - this.Pairs = new Array(); + return true; + }; + //} - var queryBeginsAt = this.originalUri.indexOf('?'); - if (queryBeginsAt >= 0) { - this.queryString = url.substr(queryBeginsAt + 1); - var queryStringPairs = this.queryString.split('&'); + box.getClaimedIdentifier = function() { return box.dnoi_internal.claimedIdentifier; }; - for (var i = 0; i < queryStringPairs.length; i++) { - var equalsAt = queryStringPairs[i].indexOf('='); - left = (equalsAt >= 0) ? queryStringPairs[i].substring(0, equalsAt) : null; - right = (equalsAt >= 0) ? queryStringPairs[i].substring(equalsAt + 1) : queryStringPairs[i]; - this.Pairs.push(new KeyValuePair(unescape(left), unescape(right))); - } - }; + // If an identifier is preset on the box, perform discovery on it, but only + // if there isn't a prior authentication that we're about to deserialize. + if (box.value.length > 0 && findOrCreateHiddenField().value.length === 0) { + trace('jumpstarting discovery on ' + box.value + ' because it was preset.'); + box.dnoi_internal.performDiscovery(); + } + + // Restore a previously achieved state (from pre-postback) if it is given. + window.dnoa_internal.deserializePreviousAuthentication(findOrCreateHiddenField().value); - this.getQueryArgValue = function(key) { - for (var i = 0; i < this.Pairs.length; i++) { - if (this.Pairs[i].key == key) { - return this.Pairs[i].value; - } + // public methods + box.setValue = function(value) { + box.value = value; + if (box.value) { + box.dnoi_internal.performDiscovery(); } }; - this.containsQueryArg = function(key) { - return this.getQueryArgValue(key); - }; - - this.indexOf = function(args) { - return this.originalUri.indexOf(args); - }; - - return this; -}; + // public events + // box.onStateChanged(state) +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdButton.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdButton.cs index a090032..dbf6944 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdButton.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdButton.cs @@ -20,7 +20,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// An ASP.NET control that renders a button that initiates an /// authentication when clicked. /// </summary> - public class OpenIdButton : OpenIdRelyingPartyControlBase, IPostBackEventHandler { + public class OpenIdButton : OpenIdRelyingPartyControlBase { #region Property defaults /// <summary> @@ -110,13 +110,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { set { ErrorUtilities.VerifySupported(value == base.Popup, OpenIdStrings.PropertyValueNotSupported); } } - #region IPostBackEventHandler Members - /// <summary> /// When implemented by a class, enables a server control to process an event raised when a form is posted to the server. /// </summary> /// <param name="eventArgument">A <see cref="T:System.String"/> that represents an optional event argument to be passed to the event handler.</param> - public void RaisePostBackEvent(string eventArgument) { + protected override void RaisePostBackEvent(string eventArgument) { if (!this.PrecreateRequest) { try { IAuthenticationRequest request = this.CreateRequests().First(); @@ -127,8 +125,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } } - #endregion - /// <summary> /// Raises the <see cref="E:System.Web.UI.Control.PreRender"/> event. /// </summary> diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdEventArgs.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdEventArgs.cs index 4d68fcc..5668cf4 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdEventArgs.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdEventArgs.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.Linq; using System.Text; using DotNetOpenAuth.Messaging; @@ -21,7 +22,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> /// <param name="request">The outgoing authentication request.</param> internal OpenIdEventArgs(IAuthenticationRequest request) { - ErrorUtilities.VerifyArgumentNotNull(request, "request"); + Contract.Requires<ArgumentNullException>(request != null); this.Request = request; this.ClaimedIdentifier = request.ClaimedIdentifier; @@ -35,7 +36,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> /// <param name="response">The incoming authentication response.</param> internal OpenIdEventArgs(IAuthenticationResponse response) { - ErrorUtilities.VerifyArgumentNotNull(response, "response"); + Contract.Requires<ArgumentNullException>(response != null); this.Response = response; this.ClaimedIdentifier = response.ClaimedIdentifier; diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdLogin.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdLogin.cs index fe1ce67..f89ec0a 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdLogin.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdLogin.cs @@ -8,10 +8,13 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { using System; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; using System.Globalization; + using System.Linq; using System.Web.UI; using System.Web.UI.HtmlControls; using System.Web.UI.WebControls; + using DotNetOpenAuth.Messaging; /// <summary> /// An ASP.NET control providing a complete OpenID login experience. @@ -105,7 +108,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// The default value for the <see cref="RememberMe"/> property. /// </summary> - private const bool RememberMeDefault = UsePersistentCookieDefault; + private const bool RememberMeDefault = false; /// <summary> /// The default value for the <see cref="UriValidatorEnabled"/> property. @@ -226,14 +229,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { #region Events /// <summary> - /// Fired after the user clicks the log in button, but before the authentication - /// process begins. Offers a chance for the web application to disallow based on - /// OpenID URL before redirecting the user to the OpenID Provider. - /// </summary> - [Description("Fired after the user clicks the log in button, but before the authentication process begins. Offers a chance for the web application to disallow based on OpenID URL before redirecting the user to the OpenID Provider.")] - public event EventHandler<OpenIdEventArgs> LoggingIn; - - /// <summary> /// Fired when the Remember Me checkbox is changed by the user. /// </summary> [Description("Fires when the Remember Me checkbox is changed by the user.")] @@ -242,6 +237,20 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { #endregion #region Properties + + /// <summary> + /// Gets a <see cref="T:System.Web.UI.ControlCollection"/> object that represents the child controls for a specified server control in the UI hierarchy. + /// </summary> + /// <returns> + /// The collection of child controls for the specified server control. + /// </returns> + public override ControlCollection Controls { + get { + this.EnsureChildControls(); + return base.Controls; + } + } + /// <summary> /// Gets or sets the caption that appears before the text box. /// </summary> @@ -251,8 +260,15 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { [Localizable(true)] [Description("The caption that appears before the text box.")] public string LabelText { - get { return this.label.InnerText; } - set { this.label.InnerText = value; } + get { + EnsureChildControls(); + return this.label.InnerText; + } + + set { + EnsureChildControls(); + this.label.InnerText = value; + } } /// <summary> @@ -264,8 +280,15 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { [Localizable(true)] [Description("The text that introduces the example OpenID url.")] public string ExamplePrefix { - get { return this.examplePrefixLabel.Text; } - set { this.examplePrefixLabel.Text = value; } + get { + EnsureChildControls(); + return this.examplePrefixLabel.Text; + } + + set { + EnsureChildControls(); + this.examplePrefixLabel.Text = value; + } } /// <summary> @@ -278,8 +301,15 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { [Localizable(true)] [Description("The example OpenID Identifier to display to the user.")] public string ExampleUrl { - get { return this.exampleUrlLabel.Text; } - set { this.exampleUrlLabel.Text = value; } + get { + EnsureChildControls(); + return this.exampleUrlLabel.Text; + } + + set { + EnsureChildControls(); + this.exampleUrlLabel.Text = value; + } } /// <summary> @@ -292,8 +322,15 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { [Localizable(true)] [Description("The text to display if the user attempts to login without providing an Identifier.")] public string RequiredText { - get { return this.requiredValidator.Text.Substring(0, this.requiredValidator.Text.Length - RequiredTextSuffix.Length); } - set { this.requiredValidator.ErrorMessage = this.requiredValidator.Text = value + RequiredTextSuffix; } + get { + EnsureChildControls(); + return this.requiredValidator.Text.Substring(0, this.requiredValidator.Text.Length - RequiredTextSuffix.Length); + } + + set { + EnsureChildControls(); + this.requiredValidator.ErrorMessage = this.requiredValidator.Text = value + RequiredTextSuffix; + } } /// <summary> @@ -306,8 +343,15 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { [Localizable(true)] [Description("The text to display if the user provides an invalid form for an Identifier.")] public string UriFormatText { - get { return this.identifierFormatValidator.Text.Substring(0, this.identifierFormatValidator.Text.Length - RequiredTextSuffix.Length); } - set { this.identifierFormatValidator.ErrorMessage = this.identifierFormatValidator.Text = value + RequiredTextSuffix; } + get { + EnsureChildControls(); + return this.identifierFormatValidator.Text.Substring(0, this.identifierFormatValidator.Text.Length - RequiredTextSuffix.Length); + } + + set { + EnsureChildControls(); + this.identifierFormatValidator.ErrorMessage = this.identifierFormatValidator.Text = value + RequiredTextSuffix; + } } /// <summary> @@ -319,8 +363,15 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { [DefaultValue(UriValidatorEnabledDefault)] [Description("Whether to perform Identifier format validation prior to an authentication attempt.")] public bool UriValidatorEnabled { - get { return this.identifierFormatValidator.Enabled; } - set { this.identifierFormatValidator.Enabled = value; } + get { + EnsureChildControls(); + return this.identifierFormatValidator.Enabled; + } + + set { + EnsureChildControls(); + this.identifierFormatValidator.Enabled = value; + } } /// <summary> @@ -332,8 +383,15 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { [Localizable(true)] [Description("The text of the link users can click on to obtain an OpenID.")] public string RegisterText { - get { return this.registerLink.Text; } - set { this.registerLink.Text = value; } + get { + EnsureChildControls(); + return this.registerLink.Text; + } + + set { + EnsureChildControls(); + this.registerLink.Text = value; + } } /// <summary> @@ -346,8 +404,15 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { [Localizable(true)] [Description("The URL to link users to who click the link to obtain a new OpenID.")] public string RegisterUrl { - get { return this.registerLink.NavigateUrl; } - set { this.registerLink.NavigateUrl = value; } + get { + EnsureChildControls(); + return this.registerLink.NavigateUrl; + } + + set { + EnsureChildControls(); + this.registerLink.NavigateUrl = value; + } } /// <summary> @@ -360,8 +425,15 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { [Localizable(true)] [Description("The text of the tooltip to display when the user hovers over the link to obtain a new OpenID.")] public string RegisterToolTip { - get { return this.registerLink.ToolTip; } - set { this.registerLink.ToolTip = value; } + get { + EnsureChildControls(); + return this.registerLink.ToolTip; + } + + set { + EnsureChildControls(); + this.registerLink.ToolTip = value; + } } /// <summary> @@ -373,8 +445,15 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { [DefaultValue(RegisterVisibleDefault)] [Description("Whether to display a link to allow users to easily obtain a new OpenID.")] public bool RegisterVisible { - get { return this.registerLink.Visible; } - set { this.registerLink.Visible = value; } + get { + EnsureChildControls(); + return this.registerLink.Visible; + } + + set { + EnsureChildControls(); + this.registerLink.Visible = value; + } } /// <summary> @@ -386,8 +465,15 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { [Localizable(true)] [Description("The text that appears on the button that initiates login.")] public string ButtonText { - get { return this.loginButton.Text; } - set { this.loginButton.Text = value; } + get { + EnsureChildControls(); + return this.loginButton.Text; + } + + set { + EnsureChildControls(); + this.loginButton.Text = value; + } } /// <summary> @@ -399,8 +485,15 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { [Localizable(true)] [Description("The text of the \"Remember Me\" checkbox.")] public string RememberMeText { - get { return this.rememberMeCheckBox.Text; } - set { this.rememberMeCheckBox.Text = value; } + get { + EnsureChildControls(); + return this.rememberMeCheckBox.Text; + } + + set { + EnsureChildControls(); + this.rememberMeCheckBox.Text = value; + } } /// <summary> @@ -438,8 +531,15 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { [DefaultValue(RememberMeVisibleDefault)] [Description("Whether the \"Remember Me\" checkbox should be displayed.")] public bool RememberMeVisible { - get { return this.rememberMeCheckBox.Visible; } - set { this.rememberMeCheckBox.Visible = value; } + get { + EnsureChildControls(); + return this.rememberMeCheckBox.Visible; + } + + set { + EnsureChildControls(); + this.rememberMeCheckBox.Visible = value; + } } /// <summary> @@ -448,11 +548,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> [Bindable(true)] [Category("Appearance")] - [DefaultValue(UsePersistentCookieDefault)] + [DefaultValue(RememberMeDefault)] [Description("Whether a successful authentication should result in a persistent cookie being saved to the browser.")] public bool RememberMe { - get { return this.UsePersistentCookie; } - set { this.UsePersistentCookie = value; } + get { return this.UsePersistentCookie != LogOnPersistence.Session; } + set { this.UsePersistentCookie = value ? LogOnPersistence.PersistentAuthentication : LogOnPersistence.Session; } } /// <summary> @@ -468,7 +568,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { set { unchecked { - this.WrappedTextBox.TabIndex = (short)(value + TextBoxTabIndexOffset); + EnsureChildControls(); + base.TabIndex = (short)(value + TextBoxTabIndexOffset); this.loginButton.TabIndex = (short)(value + LoginButtonTabIndexOffset); this.rememberMeCheckBox.TabIndex = (short)(value + RememberMeTabIndexOffset); this.registerLink.TabIndex = (short)(value + RegisterTabIndexOffset); @@ -485,8 +586,15 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { [Localizable(true)] [Description("The tooltip to display when the user hovers over the login button.")] public string ButtonToolTip { - get { return this.loginButton.ToolTip; } - set { this.loginButton.ToolTip = value; } + get { + EnsureChildControls(); + return this.loginButton.ToolTip; + } + + set { + EnsureChildControls(); + this.loginButton.ToolTip = value; + } } /// <summary> @@ -497,10 +605,12 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { [Description("The validation group that the login button and text box validator belong to.")] public string ValidationGroup { get { + EnsureChildControls(); return this.requiredValidator.ValidationGroup; } set { + EnsureChildControls(); this.requiredValidator.ValidationGroup = value; this.loginButton.ValidationGroup = value; } @@ -525,7 +635,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// cookie should persist across user sessions. /// </summary> [Browsable(false), Bindable(false)] - public override bool UsePersistentCookie { + public override LogOnPersistence UsePersistentCookie { get { return base.UsePersistentCookie; } @@ -535,8 +645,9 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { // use conditional here to prevent infinite recursion // with CheckedChanged event. - if (this.rememberMeCheckBox.Checked != value) { - this.rememberMeCheckBox.Checked = value; + bool rememberMe = value != LogOnPersistence.Session; + if (this.rememberMeCheckBox.Checked != rememberMe) { + this.rememberMeCheckBox.Checked = rememberMe; } } } @@ -544,27 +655,40 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { #endregion /// <summary> + /// Outputs server control content to a provided <see cref="T:System.Web.UI.HtmlTextWriter"/> object and stores tracing information about the control if tracing is enabled. + /// </summary> + /// <param name="writer">The <see cref="T:System.Web.UI.HTmlTextWriter"/> object that receives the control content.</param> + public override void RenderControl(HtmlTextWriter writer) { + this.RenderChildren(writer); + } + + /// <summary> /// Creates the child controls. /// </summary> protected override void CreateChildControls() { - // Don't call base.CreateChildControls(). This would add the WrappedTextBox - // to the Controls collection, which would implicitly remove it from the table - // we have already added it to. + this.InitializeControls(); // Just add the panel we've assembled earlier. - this.Controls.Add(this.panel); + base.Controls.Add(this.panel); + } - if (ShouldBeFocused) { - WrappedTextBox.Focus(); - } + /// <summary> + /// Raises the <see cref="E:System.Web.UI.Control.PreRender"/> event. + /// </summary> + /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param> + protected override void OnPreRender(EventArgs e) { + base.OnPreRender(e); + + EnsureChildControls(); + EnsureID(); + this.requiredValidator.ControlToValidate = this.ID; + this.identifierFormatValidator.ControlToValidate = this.ID; } /// <summary> /// Initializes the child controls. /// </summary> - protected override void InitializeControls() { - base.InitializeControls(); - + protected void InitializeControls() { this.panel = new Panel(); Table table = new Table(); @@ -583,7 +707,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { // top row, middle cell cell = new TableCell(); - cell.Controls.Add(this.WrappedTextBox); + cell.Controls.Add(new InPlaceControl(this)); row1.Cells.Add(cell); // top row, right cell @@ -611,7 +735,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { this.requiredValidator.ErrorMessage = RequiredTextDefault + RequiredTextSuffix; this.requiredValidator.Text = RequiredTextDefault + RequiredTextSuffix; this.requiredValidator.Display = ValidatorDisplay.Dynamic; - this.requiredValidator.ControlToValidate = WrappedTextBox.ID; this.requiredValidator.ValidationGroup = ValidationGroupDefault; cell.Controls.Add(this.requiredValidator); this.identifierFormatValidator = new CustomValidator(); @@ -620,7 +743,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { this.identifierFormatValidator.ServerValidate += this.IdentifierFormatValidator_ServerValidate; this.identifierFormatValidator.Enabled = UriValidatorEnabledDefault; this.identifierFormatValidator.Display = ValidatorDisplay.Dynamic; - this.identifierFormatValidator.ControlToValidate = WrappedTextBox.ID; this.identifierFormatValidator.ValidationGroup = ValidationGroupDefault; cell.Controls.Add(this.identifierFormatValidator); this.errorLabel = new Label(); @@ -680,26 +802,17 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> - /// Customizes HTML rendering of the control. - /// </summary> - /// <param name="writer">An <see cref="T:System.Web.UI.HtmlTextWriter"/> that represents the output stream to render HTML content on the client.</param> - protected override void Render(HtmlTextWriter writer) { - // avoid writing begin and end SPAN tags for XHTML validity. - RenderContents(writer); - } - - /// <summary> /// Renders the child controls. /// </summary> /// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the rendered content.</param> protected override void RenderChildren(HtmlTextWriter writer) { if (!this.DesignMode) { - this.label.Attributes["for"] = this.WrappedTextBox.ClientID; + this.label.Attributes["for"] = this.ClientID; if (!string.IsNullOrEmpty(this.IdSelectorIdentifier)) { this.idselectorJavascript.Visible = true; this.idselectorJavascript.Text = @"<script type='text/javascript'><!-- -idselector_input_id = '" + WrappedTextBox.ClientID + @"'; +idselector_input_id = '" + this.ClientID + @"'; // --></script> <script type='text/javascript' id='__openidselector' src='https://www.idselector.com/selector/" + this.IdSelectorIdentifier + @"' charset='utf-8'></script>"; } else { @@ -718,7 +831,7 @@ idselector_input_id = '" + WrappedTextBox.ClientID + @"'; base.OnFailed(response); if (!string.IsNullOrEmpty(this.FailedMessageText)) { - this.errorLabel.Text = string.Format(CultureInfo.CurrentCulture, this.FailedMessageText, response.Exception.Message); + this.errorLabel.Text = string.Format(CultureInfo.CurrentCulture, this.FailedMessageText, response.Exception.ToStringDescriptive()); this.errorLabel.Visible = true; } } @@ -737,30 +850,6 @@ idselector_input_id = '" + WrappedTextBox.ClientID + @"'; } /// <summary> - /// Fires the <see cref="LoggingIn"/> event. - /// </summary> - /// <returns> - /// Returns whether the login should proceed. False if some event handler canceled the request. - /// </returns> - protected virtual bool OnLoggingIn() { - EventHandler<OpenIdEventArgs> loggingIn = this.LoggingIn; - if (this.Request == null) { - this.CreateRequest(); - } - - if (this.Request != null) { - OpenIdEventArgs args = new OpenIdEventArgs(this.Request); - if (loggingIn != null) { - loggingIn(this, args); - } - - return !args.Cancel; - } else { - return false; - } - } - - /// <summary> /// Fires the <see cref="RememberMeChanged"/> event. /// </summary> protected virtual void OnRememberMeChanged() { @@ -799,8 +888,49 @@ idselector_input_id = '" + WrappedTextBox.ClientID + @"'; return; } - if (this.OnLoggingIn()) { - this.LogOn(); + IAuthenticationRequest request = this.CreateRequests().FirstOrDefault(); + if (request != null) { + this.LogOn(request); + } else { + if (!string.IsNullOrEmpty(this.FailedMessageText)) { + this.errorLabel.Text = string.Format(CultureInfo.CurrentCulture, this.FailedMessageText, OpenIdStrings.OpenIdEndpointNotFound); + this.errorLabel.Visible = true; + } + } + } + + /// <summary> + /// Renders the control inner. + /// </summary> + /// <param name="writer">The writer.</param> + private void RenderControlInner(HtmlTextWriter writer) { + base.RenderControl(writer); + } + + /// <summary> + /// A control that acts as a placeholder to indicate where + /// the OpenIdLogin control should render its OpenIdTextBox parent. + /// </summary> + private class InPlaceControl : PlaceHolder { + /// <summary> + /// The owning control to render. + /// </summary> + private OpenIdLogin renderControl; + + /// <summary> + /// Initializes a new instance of the <see cref="InPlaceControl"/> class. + /// </summary> + /// <param name="renderControl">The render control.</param> + internal InPlaceControl(OpenIdLogin renderControl) { + this.renderControl = renderControl; + } + + /// <summary> + /// Sends server control content to a provided <see cref="T:System.Web.UI.HtmlTextWriter"/> object, which writes the content to be rendered on the client. + /// </summary> + /// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the server control content.</param> + protected override void Render(HtmlTextWriter writer) { + this.renderControl.RenderControlInner(writer); } } } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdMobileTextBox.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdMobileTextBox.cs index e6e31e5..ce77df1 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdMobileTextBox.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdMobileTextBox.cs @@ -11,6 +11,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; using System.Globalization; using System.Text.RegularExpressions; using System.Web.Security; @@ -581,8 +582,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </remarks> [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Uri(Uri, string) accepts second arguments that Uri(Uri, new Uri(string)) does not that we must support.")] public IAuthenticationRequest CreateRequest() { - ErrorUtilities.VerifyOperation(this.Request == null, OpenIdStrings.CreateRequestAlreadyCalled); - ErrorUtilities.VerifyOperation(!string.IsNullOrEmpty(this.Text), OpenIdStrings.OpenIdTextBoxEmpty); + Contract.Requires<InvalidOperationException>(this.Request == null, OpenIdStrings.CreateRequestAlreadyCalled); + Contract.Requires<InvalidOperationException>(!string.IsNullOrEmpty(this.Text), OpenIdStrings.OpenIdTextBoxEmpty); try { // Resolve the trust root, and swap out the scheme and port if necessary to match the @@ -610,7 +611,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } // Add state that needs to survive across the redirect. - this.Request.AddCallbackArguments(UsePersistentCookieCallbackKey, this.UsePersistentCookie.ToString(CultureInfo.InvariantCulture)); + this.Request.SetUntrustedCallbackArgument(UsePersistentCookieCallbackKey, this.UsePersistentCookie.ToString(CultureInfo.InvariantCulture)); } else { Logger.OpenId.WarnFormat("An invalid identifier was entered ({0}), but not caught by any validation routine.", this.Text); this.Request = null; @@ -667,7 +668,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> /// <param name="response">The response.</param> protected virtual void OnLoggedIn(IAuthenticationResponse response) { - ErrorUtilities.VerifyArgumentNotNull(response, "response"); + Contract.Requires<ArgumentNullException>(response != null); ErrorUtilities.VerifyInternal(response.Status == AuthenticationStatus.Authenticated, "Firing OnLoggedIn event without an authenticated response."); var loggedIn = this.LoggedIn; @@ -686,7 +687,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> /// <param name="response">The response.</param> protected virtual void OnFailed(IAuthenticationResponse response) { - ErrorUtilities.VerifyArgumentNotNull(response, "response"); + Contract.Requires<ArgumentNullException>(response != null); ErrorUtilities.VerifyInternal(response.Status == AuthenticationStatus.Failed, "Firing Failed event for the wrong response type."); var failed = this.Failed; @@ -700,7 +701,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> /// <param name="response">The response.</param> protected virtual void OnCanceled(IAuthenticationResponse response) { - ErrorUtilities.VerifyArgumentNotNull(response, "response"); + Contract.Requires<ArgumentNullException>(response != null); ErrorUtilities.VerifyInternal(response.Status == AuthenticationStatus.Canceled, "Firing Canceled event for the wrong response type."); var canceled = this.Canceled; @@ -714,7 +715,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> /// <param name="response">The response.</param> protected virtual void OnSetupRequired(IAuthenticationResponse response) { - ErrorUtilities.VerifyArgumentNotNull(response, "response"); + Contract.Requires<ArgumentNullException>(response != null); ErrorUtilities.VerifyInternal(response.Status == AuthenticationStatus.SetupRequired, "Firing SetupRequired event for the wrong response type."); // Why are we firing Failed when we're OnSetupRequired? Backward compatibility. @@ -732,7 +733,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> /// <param name="request">The authentication request to add the extensions to.</param> private void AddProfileArgs(IAuthenticationRequest request) { - ErrorUtilities.VerifyArgumentNotNull(request, "request"); + Contract.Requires<ArgumentNullException>(request != null); request.AddExtension(new ClaimsRequest() { Nickname = this.RequestNickname, diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cd b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cd new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cd @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs index 1fe6521..e2d6356 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs @@ -87,8 +87,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { // If we are a smart-mode RP (supporting associations), then we MUST also be // capable of storing nonces to prevent replay attacks. // If we're a dumb-mode RP, then 2.0 OPs are responsible for preventing replays. - Contract.Requires(associationStore == null || nonceStore != null); - ErrorUtilities.VerifyArgument(associationStore == null || nonceStore != null, OpenIdStrings.AssociationStoreRequiresNonceStore); + Contract.Requires<ArgumentException>(associationStore == null || nonceStore != null, OpenIdStrings.AssociationStoreRequiresNonceStore); this.securitySettings = DotNetOpenAuthSection.Configuration.OpenId.RelyingParty.SecuritySettings.CreateSecuritySettings(); this.behaviors.CollectionChanged += this.OnBehaviorsChanged; @@ -155,8 +154,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } set { - Contract.Requires(value != null); - ErrorUtilities.VerifyArgumentNotNull(value, "value"); + Contract.Requires<ArgumentNullException>(value != null); this.channel = value; this.AssociationManager.Channel = value; } @@ -172,8 +170,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } internal set { - Contract.Requires(value != null); - ErrorUtilities.VerifyArgumentNotNull(value, "value"); + Contract.Requires<ArgumentNullException>(value != null); this.securitySettings = value; this.AssociationManager.SecuritySettings = value; } @@ -206,8 +203,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } set { - Contract.Requires(value != null); - ErrorUtilities.VerifyArgumentNotNull(value, "value"); + Contract.Requires<ArgumentNullException>(value != null); this.endpointOrder = value; } } @@ -273,9 +269,9 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </returns> /// <exception cref="ProtocolException">Thrown if no OpenID endpoint could be found.</exception> public IAuthenticationRequest CreateRequest(Identifier userSuppliedIdentifier, Realm realm, Uri returnToUrl) { - Contract.Requires(userSuppliedIdentifier != null); - Contract.Requires(realm != null); - Contract.Requires(returnToUrl != null); + Contract.Requires<ArgumentNullException>(userSuppliedIdentifier != null); + Contract.Requires<ArgumentNullException>(realm != null); + Contract.Requires<ArgumentNullException>(returnToUrl != null); Contract.Ensures(Contract.Result<IAuthenticationRequest>() != null); try { return this.CreateRequests(userSuppliedIdentifier, realm, returnToUrl).First(); @@ -306,11 +302,13 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <exception cref="ProtocolException">Thrown if no OpenID endpoint could be found.</exception> /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception> public IAuthenticationRequest CreateRequest(Identifier userSuppliedIdentifier, Realm realm) { - Contract.Requires(userSuppliedIdentifier != null); - Contract.Requires(realm != null); + Contract.Requires<ArgumentNullException>(userSuppliedIdentifier != null); + Contract.Requires<ArgumentNullException>(realm != null); Contract.Ensures(Contract.Result<IAuthenticationRequest>() != null); try { - return this.CreateRequests(userSuppliedIdentifier, realm).First(); + var result = this.CreateRequests(userSuppliedIdentifier, realm).First(); + Contract.Assume(result != null); + return result; } catch (InvalidOperationException ex) { throw ErrorUtilities.Wrap(ex, OpenIdStrings.OpenIdEndpointNotFound); } @@ -333,7 +331,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <exception cref="ProtocolException">Thrown if no OpenID endpoint could be found.</exception> /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception> public IAuthenticationRequest CreateRequest(Identifier userSuppliedIdentifier) { - Contract.Requires(userSuppliedIdentifier != null); + Contract.Requires<ArgumentNullException>(userSuppliedIdentifier != null); Contract.Ensures(Contract.Result<IAuthenticationRequest>() != null); try { return this.CreateRequests(userSuppliedIdentifier).First(); @@ -370,12 +368,10 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// An empty enumerable is returned instead.</para> /// </remarks> public IEnumerable<IAuthenticationRequest> CreateRequests(Identifier userSuppliedIdentifier, Realm realm, Uri returnToUrl) { - Contract.Requires(userSuppliedIdentifier != null); - Contract.Requires(realm != null); - Contract.Requires(returnToUrl != null); + Contract.Requires<ArgumentNullException>(userSuppliedIdentifier != null); + Contract.Requires<ArgumentNullException>(realm != null); + Contract.Requires<ArgumentNullException>(returnToUrl != null); Contract.Ensures(Contract.Result<IEnumerable<IAuthenticationRequest>>() != null); - ErrorUtilities.VerifyArgumentNotNull(realm, "realm"); - ErrorUtilities.VerifyArgumentNotNull(returnToUrl, "returnToUrl"); return AuthenticationRequest.Create(userSuppliedIdentifier, this, realm, returnToUrl, true).Cast<IAuthenticationRequest>(); } @@ -406,10 +402,14 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </remarks> /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception> public IEnumerable<IAuthenticationRequest> CreateRequests(Identifier userSuppliedIdentifier, Realm realm) { - Contract.Requires(userSuppliedIdentifier != null); - Contract.Requires(realm != null); + Contract.Requires<InvalidOperationException>(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); + Contract.Requires<ArgumentNullException>(userSuppliedIdentifier != null); + Contract.Requires<ArgumentNullException>(realm != null); Contract.Ensures(Contract.Result<IEnumerable<IAuthenticationRequest>>() != null); - ErrorUtilities.VerifyHttpContext(); + + // This next code contract is a BAD idea, because it causes each authentication request to be generated + // at least an extra time. + ////Contract.Ensures(Contract.ForAll(Contract.Result<IEnumerable<IAuthenticationRequest>>(), el => el != null)); // Build the return_to URL UriBuilder returnTo = new UriBuilder(this.Channel.GetRequestFromContext().UrlBeforeRewriting); @@ -450,9 +450,9 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </remarks> /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception> public IEnumerable<IAuthenticationRequest> CreateRequests(Identifier userSuppliedIdentifier) { - Contract.Requires(userSuppliedIdentifier != null); + Contract.Requires<ArgumentNullException>(userSuppliedIdentifier != null); + Contract.Requires<InvalidOperationException>(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); Contract.Ensures(Contract.Result<IEnumerable<IAuthenticationRequest>>() != null); - ErrorUtilities.VerifyHttpContext(); // Build the realm URL UriBuilder realmUrl = new UriBuilder(this.Channel.GetRequestFromContext().UrlBeforeRewriting); @@ -488,7 +488,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <param name="httpRequestInfo">The HTTP request that may be carrying an authentication response from the Provider.</param> /// <returns>The processed authentication response if there is any; <c>null</c> otherwise.</returns> public IAuthenticationResponse GetResponse(HttpRequestInfo httpRequestInfo) { - Contract.Requires(httpRequestInfo != null); + Contract.Requires<ArgumentNullException>(httpRequestInfo != null); try { var message = this.Channel.ReadFromRequest(httpRequestInfo); PositiveAssertionResponse positiveAssertion; @@ -571,18 +571,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { return rp; } -#if CONTRACTS_FULL - /// <summary> - /// Verifies conditions that should be true for any valid state of this object. - /// </summary> - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] - [ContractInvariantMethod] - private void ObjectInvariant() { - Contract.Invariant(this.SecuritySettings != null); - Contract.Invariant(this.Channel != null); - } -#endif - /// <summary> /// Releases unmanaged and - optionally - managed resources /// </summary> @@ -607,5 +595,19 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { profile.ApplySecuritySettings(this.SecuritySettings); } } + +#if CONTRACTS_FULL + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] + [ContractInvariantMethod] + private void ObjectInvariant() { + Contract.Invariant(this.SecuritySettings != null); + Contract.Invariant(this.Channel != null); + Contract.Invariant(this.EndpointOrder != null); + } +#endif } } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs index da2a9ae..44a5b41 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs @@ -12,24 +12,19 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; - using System.Drawing.Design; using System.Globalization; using System.Linq; using System.Text; - using System.Text.RegularExpressions; using System.Web; - using System.Web.Security; using System.Web.UI; - using DotNetOpenAuth.ComponentModel; using DotNetOpenAuth.Configuration; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.Extensions; - using DotNetOpenAuth.OpenId.Extensions.UI; /// <summary> /// A common base class for OpenID Relying Party controls. /// </summary> - internal abstract class OpenIdRelyingPartyAjaxControlBase : OpenIdRelyingPartyControlBase, ICallbackEventHandler { + public abstract class OpenIdRelyingPartyAjaxControlBase : OpenIdRelyingPartyControlBase, ICallbackEventHandler { /// <summary> /// The manifest resource name of the javascript file to include on the hosting page. /// </summary> @@ -38,15 +33,71 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// The name of the javascript function that will initiate a synchronous callback. /// </summary> - protected const string CallbackJsFunction = "window.dnoa_internal.callback"; + protected const string CallbackJSFunction = "window.dnoa_internal.callback"; /// <summary> /// The name of the javascript function that will initiate an asynchronous callback. /// </summary> - protected const string CallbackJsFunctionAsync = "window.dnoa_internal.callbackAsync"; + protected const string CallbackJSFunctionAsync = "window.dnoa_internal.callbackAsync"; /// <summary> - /// Stores the result of a AJAX callback discovery. + /// The name of the javascript field that stores the maximum time a positive assertion is + /// good for before it must be refreshed. + /// </summary> + private const string MaxPositiveAssertionLifetimeJsName = "window.dnoa_internal.maxPositiveAssertionLifetime"; + + /// <summary> + /// The "dnoa.op_endpoint" string. + /// </summary> + private const string OPEndpointParameterName = OpenIdUtilities.CustomParameterPrefix + "op_endpoint"; + + /// <summary> + /// The "dnoa.claimed_id" string. + /// </summary> + private const string ClaimedIdParameterName = OpenIdUtilities.CustomParameterPrefix + "claimed_id"; + + #region Property viewstate keys + + /// <summary> + /// The viewstate key to use for storing the value of a successful authentication. + /// </summary> + private const string AuthDataViewStateKey = "AuthData"; + + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="AuthenticationResponse"/> property. + /// </summary> + private const string AuthenticationResponseViewStateKey = "AuthenticationResponse"; + + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="AuthenticationProcessedAlready"/> property. + /// </summary> + private const string AuthenticationProcessedAlreadyViewStateKey = "AuthenticationProcessedAlready"; + + #endregion + + /// <summary> + /// Default value of the <see cref="Popup"/> property. + /// </summary> + private const PopupBehavior PopupDefault = PopupBehavior.Always; + + /// <summary> + /// Default value of <see cref="LogOnMode"/> property.. + /// </summary> + private const LogOnSiteNotification LogOnModeDefault = LogOnSiteNotification.None; + + /// <summary> + /// Backing field for the <see cref="RelyingPartyNonVerifying"/> property. + /// </summary> + private static OpenIdRelyingParty relyingPartyNonVerifying; + + /// <summary> + /// The authentication response that just came in. + /// </summary> + private IAuthenticationResponse authenticationResponse; + + /// <summary> + /// Stores the result of an AJAX discovery request while it is waiting + /// to be picked up by ASP.NET on the way down to the user agent. /// </summary> private string discoveryResult; @@ -61,19 +112,129 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> protected OpenIdRelyingPartyAjaxControlBase() { // The AJAX login style always uses popups (or invisible iframes). - this.Popup = PopupBehavior.Always; + base.Popup = PopupDefault; + + // The expected use case for the AJAX login box is for comments... not logging in. + this.LogOnMode = LogOnModeDefault; } /// <summary> + /// Fired when a Provider sends back a positive assertion to this control, + /// but the authentication has not yet been verified. + /// </summary> + /// <remarks> + /// <b>No security critical decisions should be made within event handlers + /// for this event</b> as the authenticity of the assertion has not been + /// verified yet. All security related code should go in the event handler + /// for the <see cref="OpenIdRelyingPartyControlBase.LoggedIn"/> event. + /// </remarks> + [Description("Fired when a Provider sends back a positive assertion to this control, but the authentication has not yet been verified.")] + public event EventHandler<OpenIdEventArgs> UnconfirmedPositiveAssertion; + + /// <summary> /// Gets or sets a value indicating when to use a popup window to complete the login experience. /// </summary> /// <value>The default value is <see cref="PopupBehavior.Never"/>.</value> - [Bindable(false), Browsable(false)] + [Bindable(false), Browsable(false), DefaultValue(PopupDefault)] public override PopupBehavior Popup { get { return base.Popup; } set { ErrorUtilities.VerifySupported(value == base.Popup, OpenIdStrings.PropertyValueNotSupported); } } + /// <summary> + /// Gets or sets the way a completed login is communicated to the rest of the web site. + /// </summary> + [Bindable(true), DefaultValue(LogOnModeDefault), Category(BehaviorCategory)] + [Description("The way a completed login is communicated to the rest of the web site.")] + public override LogOnSiteNotification LogOnMode { // override to set new DefaultValue + get { return base.LogOnMode; } + set { base.LogOnMode = value; } + } + + /// <summary> + /// Gets the completed authentication response. + /// </summary> + public IAuthenticationResponse AuthenticationResponse { + get { + if (this.authenticationResponse == null) { + // We will either validate a new response and return a live AuthenticationResponse + // or we will try to deserialize a previous IAuthenticationResponse (snapshot) + // from viewstate and return that. + IAuthenticationResponse viewstateResponse = this.ViewState[AuthenticationResponseViewStateKey] as IAuthenticationResponse; + string viewstateAuthData = this.ViewState[AuthDataViewStateKey] as string; + string formAuthData = this.Page.Request.Form[this.OpenIdAuthDataFormKey]; + + // First see if there is fresh auth data to be processed into a response. + if (!string.IsNullOrEmpty(formAuthData) && !string.Equals(viewstateAuthData, formAuthData, StringComparison.Ordinal)) { + this.ViewState[AuthDataViewStateKey] = formAuthData; + + Uri authUri = new Uri(formAuthData); + HttpRequestInfo clientResponseInfo = new HttpRequestInfo { + UrlBeforeRewriting = authUri, + }; + + this.authenticationResponse = this.RelyingParty.GetResponse(clientResponseInfo); + this.AuthenticationProcessedAlready = false; + + // Save out the authentication response to viewstate so we can find it on + // a subsequent postback. + this.ViewState[AuthenticationResponseViewStateKey] = new PositiveAuthenticationResponseSnapshot(this.authenticationResponse); + } else { + this.authenticationResponse = viewstateResponse; + } + } + + return this.authenticationResponse; + } + } + + /// <summary> + /// Gets the name of the open id auth data form key (for the value as stored at the user agent as a FORM field). + /// </summary> + /// <value>Usually a concatenation of the control's name and <c>"_openidAuthData"</c>.</value> + protected abstract string OpenIdAuthDataFormKey { get; } + + /// <summary> + /// Gets the relying party to use when verification of incoming messages is NOT wanted. + /// </summary> + private static OpenIdRelyingParty RelyingPartyNonVerifying { + get { + if (relyingPartyNonVerifying == null) { + relyingPartyNonVerifying = OpenIdRelyingParty.CreateNonVerifying(); + } + return relyingPartyNonVerifying; + } + } + + /// <summary> + /// Gets or sets a value indicating whether an authentication in the page's view state + /// has already been processed and appropriate events fired. + /// </summary> + private bool AuthenticationProcessedAlready { + get { return (bool)(ViewState[AuthenticationProcessedAlreadyViewStateKey] ?? false); } + set { ViewState[AuthenticationProcessedAlreadyViewStateKey] = value; } + } + + /// <summary> + /// Allows an OpenID extension to read data out of an unverified positive authentication assertion + /// and send it down to the client browser so that Javascript running on the page can perform + /// some preprocessing on the extension data. + /// </summary> + /// <typeparam name="T">The extension <i>response</i> type that will read data from the assertion.</typeparam> + /// <param name="propertyName">The property name on the openid_identifier input box object that will be used to store the extension data. For example: sreg</param> + /// <remarks> + /// This method should be called from the <see cref="UnconfirmedPositiveAssertion"/> event handler. + /// </remarks> + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "By design")] + public void RegisterClientScriptExtension<T>(string propertyName) where T : IClientScriptExtensionResponse { + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(propertyName)); + ErrorUtilities.VerifyArgumentNamed(!this.clientScriptExtensions.ContainsValue(propertyName), "propertyName", OpenIdStrings.ClientScriptExtensionPropertyNameCollision, propertyName); + foreach (var ext in this.clientScriptExtensions.Keys) { + ErrorUtilities.VerifyArgument(ext != typeof(T), OpenIdStrings.ClientScriptExtensionTypeCollision, typeof(T).FullName); + } + this.clientScriptExtensions.Add(typeof(T), propertyName); + } + #region ICallbackEventHandler Members /// <summary> @@ -82,8 +243,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <returns>The result of the callback.</returns> /// <value>A whitespace delimited list of URLs that can be used to initiate authentication.</value> string ICallbackEventHandler.GetCallbackResult() { - this.Page.Response.ContentType = "text/javascript"; - return this.discoveryResult; + return this.GetCallbackResult(); } /// <summary> @@ -91,12 +251,249 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// AJAX callback mechanisms. /// </summary> /// <param name="eventArgument">The identifier to perform discovery on.</param> + [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "We want to preserve the signature of the interface.")] void ICallbackEventHandler.RaiseCallbackEvent(string eventArgument) { + this.RaiseCallbackEvent(eventArgument); + } + + #endregion + + /// <summary> + /// Creates the authentication requests for a given user-supplied Identifier. + /// </summary> + /// <param name="identifier">The identifier to create a request for.</param> + /// <returns> + /// A sequence of authentication requests, any one of which may be + /// used to determine the user's control of the <see cref="IAuthenticationRequest.ClaimedIdentifier"/>. + /// </returns> + protected internal override IEnumerable<IAuthenticationRequest> CreateRequests(Identifier identifier) { + // If this control is actually a member of another OpenID RP control, + // delegate creation of requests to the parent control. + var parentOwner = this.ParentControls.OfType<OpenIdRelyingPartyControlBase>().FirstOrDefault(); + if (parentOwner != null) { + return parentOwner.CreateRequests(identifier); + } else { + // We delegate all our logic to another method, since invoking base. methods + // within an iterator method results in unverifiable code. + return this.CreateRequestsCore(base.CreateRequests(identifier)); + } + } + + /// <summary> + /// Returns the results of a callback event that targets a control. + /// </summary> + /// <returns>The result of the callback.</returns> + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "We want to preserve the signature of the interface.")] + protected virtual string GetCallbackResult() { + this.Page.Response.ContentType = "text/javascript"; + return this.discoveryResult; + } + + /// <summary> + /// Processes a callback event that targets a control. + /// </summary> + /// <param name="eventArgument">A string that represents an event argument to pass to the event handler.</param> + [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "We want to preserve the signature of the interface.")] + protected virtual void RaiseCallbackEvent(string eventArgument) { string userSuppliedIdentifier = eventArgument; ErrorUtilities.VerifyNonZeroLength(userSuppliedIdentifier, "userSuppliedIdentifier"); Logger.OpenId.InfoFormat("AJAX discovery on {0} requested.", userSuppliedIdentifier); + this.Identifier = userSuppliedIdentifier; + this.discoveryResult = this.SerializeDiscoveryAsJson(this.Identifier); + } + + /// <summary> + /// Pre-discovers an identifier and makes the results available to the + /// user agent for javascript as soon as the page loads. + /// </summary> + /// <param name="identifier">The identifier.</param> + protected void PreloadDiscovery(Identifier identifier) { + this.PreloadDiscovery(new[] { identifier }); + } + + /// <summary> + /// Pre-discovers a given set of identifiers and makes the results available to the + /// user agent for javascript as soon as the page loads. + /// </summary> + /// <param name="identifiers">The identifiers to perform discovery on.</param> + protected void PreloadDiscovery(IEnumerable<Identifier> identifiers) { + string discoveryResults = this.SerializeDiscoveryAsJson(identifiers); + string script = "window.dnoa_internal.loadPreloadedDiscoveryResults(" + discoveryResults + ");"; + this.Page.ClientScript.RegisterClientScriptBlock(typeof(OpenIdRelyingPartyAjaxControlBase), this.ClientID, script, true); + } + + /// <summary> + /// Fires the <see cref="UnconfirmedPositiveAssertion"/> event. + /// </summary> + protected virtual void OnUnconfirmedPositiveAssertion() { + var unconfirmedPositiveAssertion = this.UnconfirmedPositiveAssertion; + if (unconfirmedPositiveAssertion != null) { + unconfirmedPositiveAssertion(this, null); + } + } + + /// <summary> + /// Raises the <see cref="E:Load"/> event. + /// </summary> + /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param> + protected override void OnLoad(EventArgs e) { + base.OnLoad(e); + + // Our parent control ignores all OpenID messages included in a postback, + // but our AJAX controls hide an old OpenID message in a postback payload, + // so we deserialize it and process it when appropriate. + if (this.Page.IsPostBack) { + if (this.AuthenticationResponse != null && !this.AuthenticationProcessedAlready) { + // Only process messages targeted at this control. + // Note that Stateless mode causes no receiver to be indicated. + string receiver = this.AuthenticationResponse.GetUntrustedCallbackArgument(ReturnToReceivingControlId); + if (receiver == null || receiver == this.ClientID) { + this.ProcessResponse(this.AuthenticationResponse); + this.AuthenticationProcessedAlready = true; + } + } + } + } + + /// <summary> + /// Called when the <see cref="Identifier"/> property is changed. + /// </summary> + protected override void OnIdentifierChanged() { + base.OnIdentifierChanged(); + + // Since the identifier changed, make sure we reset any cached authentication on the user agent. + this.ViewState.Remove(AuthDataViewStateKey); + } + + /// <summary> + /// Raises the <see cref="E:System.Web.UI.Control.PreRender"/> event. + /// </summary> + /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param> + protected override void OnPreRender(EventArgs e) { + base.OnPreRender(e); + + this.SetWebAppPathOnUserAgent(); + this.Page.ClientScript.RegisterClientScriptResource(typeof(OpenIdRelyingPartyAjaxControlBase), EmbeddedAjaxJavascriptResource); + + StringBuilder initScript = new StringBuilder(); + + initScript.AppendLine(CallbackJSFunctionAsync + " = " + this.GetJsCallbackConvenienceFunction(true)); + initScript.AppendLine(CallbackJSFunction + " = " + this.GetJsCallbackConvenienceFunction(false)); + + // Positive assertions can last no longer than this library is willing to consider them valid, + // and when they come with OP private associations they last no longer than the OP is willing + // to consider them valid. We assume the OP will hold them valid for at least five minutes. + double assertionLifetimeInMilliseconds = Math.Min(TimeSpan.FromMinutes(5).TotalMilliseconds, Math.Min(DotNetOpenAuthSection.Configuration.OpenId.MaxAuthenticationTime.TotalMilliseconds, DotNetOpenAuthSection.Configuration.Messaging.MaximumMessageLifetime.TotalMilliseconds)); + initScript.AppendLine(MaxPositiveAssertionLifetimeJsName + " = " + assertionLifetimeInMilliseconds.ToString(CultureInfo.InvariantCulture) + ";"); + + // We register this callback code explicitly with a specific type rather than the derived-type of the control + // to ensure that this discovery callback function is only set ONCE for the HTML document. + this.Page.ClientScript.RegisterClientScriptBlock(typeof(OpenIdRelyingPartyControlBase), "initializer", initScript.ToString(), true); + } + + /// <summary> + /// Sends server control content to a provided <see cref="T:System.Web.UI.HtmlTextWriter"/> object, which writes the content to be rendered on the client. + /// </summary> + /// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the server control content.</param> + protected override void Render(HtmlTextWriter writer) { + base.Render(writer); + + // Emit a hidden field to let the javascript on the user agent know if an + // authentication has already successfully taken place. + string viewstateAuthData = this.ViewState[AuthDataViewStateKey] as string; + if (!string.IsNullOrEmpty(viewstateAuthData)) { + writer.AddAttribute(HtmlTextWriterAttribute.Name, this.OpenIdAuthDataFormKey); + writer.AddAttribute(HtmlTextWriterAttribute.Value, viewstateAuthData, true); + writer.AddAttribute(HtmlTextWriterAttribute.Type, "hidden"); + writer.RenderBeginTag(HtmlTextWriterTag.Input); + writer.RenderEndTag(); + } + } + + /// <summary> + /// Notifies the user agent via an AJAX response of a completed authentication attempt. + /// </summary> + protected override void ScriptClosingPopupOrIFrame() { + Logger.OpenId.InfoFormat("AJAX (iframe) callback from OP: {0}", this.Page.Request.Url); + string extensionsJson = null; + + var authResponse = RelyingPartyNonVerifying.GetResponse(); + if (authResponse.Status == AuthenticationStatus.Authenticated) { + this.OnUnconfirmedPositiveAssertion(); // event handler will fill the clientScriptExtensions collection. + var extensionsDictionary = new Dictionary<string, string>(); + foreach (var pair in this.clientScriptExtensions) { + IClientScriptExtensionResponse extension = (IClientScriptExtensionResponse)authResponse.GetExtension(pair.Key); + if (extension == null) { + continue; + } + var positiveResponse = (PositiveAuthenticationResponse)authResponse; + string js = extension.InitializeJavaScriptData(positiveResponse.Response); + if (!string.IsNullOrEmpty(js)) { + extensionsDictionary[pair.Value] = js; + } + } + + extensionsJson = MessagingUtilities.CreateJsonObject(extensionsDictionary, true); + } + + string payload = "document.URL"; + if (Page.Request.HttpMethod == "POST") { + // Promote all form variables to the query string, but since it won't be passed + // to any server (this is a javascript window-to-window transfer) the length of + // it can be arbitrarily long, whereas it was POSTed here probably because it + // was too long for HTTP transit. + UriBuilder payloadUri = new UriBuilder(Page.Request.Url); + payloadUri.AppendQueryArgs(Page.Request.Form.ToDictionary()); + payload = MessagingUtilities.GetSafeJavascriptValue(payloadUri.Uri.AbsoluteUri); + } + + if (!string.IsNullOrEmpty(extensionsJson)) { + payload += ", " + extensionsJson; + } + + this.CallbackUserAgentMethod("dnoa_internal.processAuthorizationResult(" + payload + ")"); + } + + /// <summary> + /// Serializes the discovery of multiple identifiers as a JSON object. + /// </summary> + /// <param name="identifiers">The identifiers to perform discovery on and create requests for.</param> + /// <returns>The serialized JSON object.</returns> + private string SerializeDiscoveryAsJson(IEnumerable<Identifier> identifiers) { + ErrorUtilities.VerifyArgumentNotNull(identifiers, "identifiers"); + + // We prepare a JSON object with this interface: + // Array discoveryWrappers; + // Where each element in the above array has this interface: + // class discoveryWrapper { + // string userSuppliedIdentifier; + // jsonResponse discoveryResult; // contains result of call to SerializeDiscoveryAsJson(Identifier) + // } + StringBuilder discoveryResultBuilder = new StringBuilder(); + discoveryResultBuilder.Append("["); + foreach (var identifier in identifiers) { // TODO: parallelize discovery on these identifiers + discoveryResultBuilder.Append("{"); + discoveryResultBuilder.AppendFormat("userSuppliedIdentifier: {0},", MessagingUtilities.GetSafeJavascriptValue(identifier)); + discoveryResultBuilder.AppendFormat("discoveryResult: {0}", this.SerializeDiscoveryAsJson(identifier)); + discoveryResultBuilder.Append("},"); + } + + discoveryResultBuilder.Length -= 1; // trim last comma + discoveryResultBuilder.Append("]"); + return discoveryResultBuilder.ToString(); + } + + /// <summary> + /// Serializes the results of discovery and the created auth requests as a JSON object + /// for the user agent to initiate. + /// </summary> + /// <param name="identifier">The identifier to perform discovery on.</param> + /// <returns>The JSON string.</returns> + private string SerializeDiscoveryAsJson(Identifier identifier) { + ErrorUtilities.VerifyArgumentNotNull(identifier, "identifier"); + // We prepare a JSON object with this interface: // class jsonResponse { // string claimedIdentifier; @@ -112,13 +509,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { StringBuilder discoveryResultBuilder = new StringBuilder(); discoveryResultBuilder.Append("{"); try { - this.Identifier = userSuppliedIdentifier; - IEnumerable<IAuthenticationRequest> requests = this.CreateRequests().CacheGeneratedResults(); + IEnumerable<IAuthenticationRequest> requests = this.CreateRequests(identifier).CacheGeneratedResults(); if (requests.Any()) { discoveryResultBuilder.AppendFormat("claimedIdentifier: {0},", MessagingUtilities.GetSafeJavascriptValue(requests.First().ClaimedIdentifier)); discoveryResultBuilder.Append("requests: ["); foreach (IAuthenticationRequest request in requests) { - this.OnLoggingIn(request); discoveryResultBuilder.Append("{"); discoveryResultBuilder.AppendFormat("endpoint: {0},", MessagingUtilities.GetSafeJavascriptValue(request.Provider.Uri.AbsoluteUri)); request.Mode = AuthenticationRequestMode.Immediate; @@ -132,47 +527,16 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { discoveryResultBuilder.Length -= 1; // trim off last comma discoveryResultBuilder.Append("]"); } else { - discoveryResultBuilder.Append("requests: new Array(),"); + discoveryResultBuilder.Append("requests: [],"); discoveryResultBuilder.AppendFormat("error: {0}", MessagingUtilities.GetSafeJavascriptValue(OpenIdStrings.OpenIdEndpointNotFound)); } } catch (ProtocolException ex) { - discoveryResultBuilder.Append("requests: new Array(),"); + discoveryResultBuilder.Append("requests: [],"); discoveryResultBuilder.AppendFormat("error: {0}", MessagingUtilities.GetSafeJavascriptValue(ex.Message)); } - discoveryResultBuilder.Append("}"); - this.discoveryResult = discoveryResultBuilder.ToString(); - } - - #endregion - - /// <summary> - /// Creates the authentication requests for a given user-supplied Identifier. - /// </summary> - /// <returns>A sequence of authentication requests, any one of which may be - /// used to determine the user's control of the <see cref="IAuthenticationRequest.ClaimedIdentifier"/>.</returns> - protected override IEnumerable<IAuthenticationRequest> CreateRequests() { - ErrorUtilities.VerifyOperation(this.Identifier != null, OpenIdStrings.NoIdentifierSet); - - // We delegate all our logic to another method, since invoking base. methods - // within an iterator method results in unverifiable code. - return this.CreateRequestsCore(base.CreateRequests()); - } - - /// <summary> - /// Raises the <see cref="E:System.Web.UI.Control.PreRender"/> event. - /// </summary> - /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param> - protected override void OnPreRender(EventArgs e) { - base.OnPreRender(e); - - this.Page.ClientScript.RegisterClientScriptResource(typeof(OpenIdRelyingPartyAjaxControlBase), EmbeddedAjaxJavascriptResource); - StringBuilder initScript = new StringBuilder(); - - initScript.AppendLine(CallbackJsFunctionAsync + " = " + this.GetJsCallbackConvenienceFunction(true)); - initScript.AppendLine(CallbackJsFunction + " = " + this.GetJsCallbackConvenienceFunction(false)); - - this.Page.ClientScript.RegisterClientScriptBlock(typeof(OpenIdRelyingPartyControlBase), "initializer", initScript.ToString(), true); + discoveryResultBuilder.Append("}"); + return discoveryResultBuilder.ToString(); } /// <summary> @@ -184,30 +548,26 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// used to determine the user's control of the <see cref="IAuthenticationRequest.ClaimedIdentifier"/>. /// </returns> private IEnumerable<IAuthenticationRequest> CreateRequestsCore(IEnumerable<IAuthenticationRequest> requests) { - Contract.Requires(requests != null); + ErrorUtilities.VerifyArgumentNotNull(requests, "requests"); // NO CODE CONTRACTS! (yield return used here) // Configure each generated request. int reqIndex = 0; foreach (var req in requests) { - req.AddCallbackArguments("index", (reqIndex++).ToString(CultureInfo.InvariantCulture)); - - if (req.Provider.IsExtensionSupported<UIRequest>()) { - // Provide a hint for the client javascript about whether the OP supports the UI extension. - // This is so the window can be made the correct size for the extension. - // If the OP doesn't advertise support for the extension, the javascript will use - // a bigger popup window. - req.AddCallbackArguments("dotnetopenid.popupUISupported", "1"); - } + req.SetUntrustedCallbackArgument("index", (reqIndex++).ToString(CultureInfo.InvariantCulture)); // If the ReturnToUrl was explicitly set, we'll need to reset our first parameter - if (string.IsNullOrEmpty(HttpUtility.ParseQueryString(req.ReturnToUrl.Query)["dotnetopenid.userSuppliedIdentifier"])) { - req.AddCallbackArguments("dotnetopenid.userSuppliedIdentifier", this.Identifier); + if (string.IsNullOrEmpty(HttpUtility.ParseQueryString(req.ReturnToUrl.Query)[AuthenticationRequest.UserSuppliedIdentifierParameterName])) { + Identifier userSuppliedIdentifier = ((AuthenticationRequest)req).Endpoint.UserSuppliedIdentifier; + req.SetUntrustedCallbackArgument(AuthenticationRequest.UserSuppliedIdentifierParameterName, userSuppliedIdentifier.OriginalString); } // Our javascript needs to let the user know which endpoint responded. So we force it here. // This gives us the info even for 1.0 OPs and 2.0 setup_required responses. - req.AddCallbackArguments("dotnetopenid.op_endpoint", req.Provider.Uri.AbsoluteUri); - req.AddCallbackArguments("dotnetopenid.claimed_id", (string)req.ClaimedIdentifier ?? string.Empty); + req.SetUntrustedCallbackArgument(OPEndpointParameterName, req.Provider.Uri.AbsoluteUri); + req.SetUntrustedCallbackArgument(ClaimedIdParameterName, (string)req.ClaimedIdentifier ?? string.Empty); + + // Inform ourselves in return_to that we're in a popup or iframe. + req.SetUntrustedCallbackArgument(UIPopupCallbackKey, "1"); // We append a # at the end so that if the OP happens to support it, // the OpenID response "query string" is appended after the hash rather than before, resulting in the @@ -247,73 +607,38 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> - /// Notifies the user agent via an AJAX response of a completed authentication attempt. - /// </summary> - private void ReportAuthenticationResult() { - Logger.OpenId.InfoFormat("AJAX (iframe) callback from OP: {0}", this.Page.Request.Url); - List<string> assignments = new List<string>(); - - var authResponse = this.RelyingParty.GetResponse(); - if (authResponse.Status == AuthenticationStatus.Authenticated) { - this.OnLoggedIn(authResponse); - foreach (var pair in this.clientScriptExtensions) { - IClientScriptExtensionResponse extension = (IClientScriptExtensionResponse)authResponse.GetExtension(pair.Key); - if (extension == null) { - continue; - } - var positiveResponse = (PositiveAuthenticationResponse)authResponse; - string js = extension.InitializeJavaScriptData(positiveResponse.Response); - if (string.IsNullOrEmpty(js)) { - js = "null"; - } - assignments.Add(pair.Value + " = " + js); - } - } - - this.CallbackUserAgentMethod("dnoi_internal.processAuthorizationResult(document.URL)", assignments.ToArray()); - } - - /// <summary> /// Invokes a method on a parent frame/window's OpenIdAjaxTextBox, /// and closes the calling popup window if applicable. /// </summary> /// <param name="methodCall">The method to call on the OpenIdAjaxTextBox, including /// parameters. (i.e. "callback('arg1', 2)"). No escaping is done by this method.</param> private void CallbackUserAgentMethod(string methodCall) { - this.CallbackUserAgentMethod(methodCall, null); - } - - /// <summary> - /// Invokes a method on a parent frame/window's OpenIdAjaxTextBox, - /// and closes the calling popup window if applicable. - /// </summary> - /// <param name="methodCall">The method to call on the OpenIdAjaxTextBox, including - /// parameters. (i.e. "callback('arg1', 2)"). No escaping is done by this method.</param> - /// <param name="preAssignments">An optional list of assignments to make to the input box object before placing the method call.</param> - private void CallbackUserAgentMethod(string methodCall, string[] preAssignments) { Logger.OpenId.InfoFormat("Sending Javascript callback: {0}", methodCall); Page.Response.Write(@"<html><body><script language='javascript'> var inPopup = !window.frameElement; - var objSrc = inPopup ? window.opener.waiting_openidBox : window.frameElement.openidBox; + var objSrc = inPopup ? window.opener : window.frameElement; "); - if (preAssignments != null) { - foreach (string assignment in preAssignments) { - Page.Response.Write(string.Format(CultureInfo.InvariantCulture, " objSrc.{0};\n", assignment)); - } - } // Something about calling objSrc.{0} can somehow cause FireFox to forget about the inPopup variable, // so we have to actually put the test for it ABOVE the call to objSrc.{0} so that it already // whether to call window.self.close() after the call. string htmlFormat = @" if (inPopup) {{ - objSrc.{0}; - window.self.close(); -}} else {{ - objSrc.{0}; -}} + objSrc.{0}; + window.self.close(); + }} else {{ + objSrc.{0}; + }} </script></body></html>"; Page.Response.Write(string.Format(CultureInfo.InvariantCulture, htmlFormat, methodCall)); Page.Response.End(); } + + /// <summary> + /// Sets the window.aspnetapppath variable on the user agent so that cookies can be set with the proper path. + /// </summary> + private void SetWebAppPathOnUserAgent() { + string script = "window.aspnetapppath = " + MessagingUtilities.GetSafeJavascriptValue(this.Page.Request.ApplicationPath) + ";"; + this.Page.ClientScript.RegisterClientScriptBlock(typeof(OpenIdRelyingPartyAjaxControlBase), "webapppath", script, true); + } } } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.js b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.js index 65b1b99..6faad56 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.js +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.js @@ -1,150 +1,747 @@ //----------------------------------------------------------------------- -// <copyright file="OpenIdRelyingPartyControlBase.js" company="Andrew Arnott"> +// <copyright file="OpenIdRelyingPartyAjaxControlBase.js" company="Andrew Arnott"> // Copyright (c) Andrew Arnott. All rights reserved. +// This file may be used and redistributed under the terms of the +// Microsoft Public License (Ms-PL) http://opensource.org/licenses/ms-pl.html // </copyright> //----------------------------------------------------------------------- if (window.dnoa_internal === undefined) { - window.dnoa_internal = new Object(); + window.dnoa_internal = {}; +} + +/// <summary>Removes a given element from the array.</summary> +/// <returns>True if the element was in the array, or false if it was not found.</returns> +Array.prototype.remove = function(element) { + function elementToRemoveLast(a, b) { + if (a == element) { return 1; } + if (b == element) { return -1; } + return 0; + } + this.sort(elementToRemoveLast); + if (this[this.length - 1] == element) { + this.pop(); + return true; + } else { + return false; + } +}; + +// Renders all the parameters in their string form, surrounded by parentheses. +window.dnoa_internal.argsToString = function() { + result = "("; + for (var i = 0; i < arguments.length; i++) { + if (i > 0) { result += ', '; } + var arg = arguments[i]; + if (typeof (arg) == 'string') { + arg = '"' + arg + '"'; + } else if (arg === null) { + arg = '[null]'; + } else if (arg === undefined) { + arg = '[undefined]'; + } + result += arg.toString(); + } + result += ')'; + return result; }; -window.dnoa_internal.discoveryResults = new Array(); // user supplied identifiers and discovery results +window.dnoa_internal.registerEvent = function(name) { + var filterOnApplicability = function(fn, domElement) { + /// <summary>Wraps a given function with a check so that the function only executes when a given element is still in the DOM.</summary> + return function() { + var args = Array.prototype.slice.call(arguments); + if (!domElement) { + // no element used as a basis of applicability indicates we always fire this callback. + fn.apply(null, args); + } else { + var elements = document.getElementsByTagName(domElement.tagName); + var isElementInDom = false; + for (var i = 0; i < elements.length; i++) { + if (elements[i] === domElement) { + isElementInDom = true; + break; + } + } + if (isElementInDom) { + fn.apply(null, args); + } + } + } + }; + + window.dnoa_internal[name + 'Listeners'] = []; + window.dnoa_internal['add' + name] = function(fn, whileDomElementApplicable) { window.dnoa_internal[name + 'Listeners'].push(filterOnApplicability(fn, whileDomElementApplicable)); }; + window.dnoa_internal['remove' + name] = function(fn) { window.dnoa_internal[name + 'Listeners'].remove(fn); }; + window.dnoa_internal['fire' + name] = function() { + var args = Array.prototype.slice.call(arguments); + trace('Firing event ' + name + window.dnoa_internal.argsToString.apply(null, args), 'blue'); + var listeners = window.dnoa_internal[name + 'Listeners']; + for (var i = 0; i < listeners.length; i++) { + listeners[i].apply(null, args); + } + }; +}; + +window.dnoa_internal.registerEvent('DiscoveryStarted'); // (identifier) - fired when a discovery callback is ACTUALLY made to the RP +window.dnoa_internal.registerEvent('DiscoverySuccess'); // (identifier, discoveryResult, { fresh: true|false }) - fired after a discovery callback is returned from the RP successfully or a cached result is retrieved +window.dnoa_internal.registerEvent('DiscoveryFailed'); // (identifier, message) - fired after a discovery callback fails +window.dnoa_internal.registerEvent('AuthStarted'); // (discoveryResult, serviceEndpoint, { background: true|false }) +window.dnoa_internal.registerEvent('AuthFailed'); // (discoveryResult, serviceEndpoint, { background: true|false }) - fired for each individual ServiceEndpoint, and once at last with serviceEndpoint==null if all failed +window.dnoa_internal.registerEvent('AuthSuccess'); // (discoveryResult, serviceEndpoint, extensionResponses, { background: true|false, deserialized: true|false }) +window.dnoa_internal.registerEvent('AuthCleared'); // (discoveryResult, serviceEndpoint) + +window.dnoa_internal.discoveryResults = []; // user supplied identifiers and discovery results +window.dnoa_internal.discoveryInProgress = []; // identifiers currently being discovered and their callbacks + +// The possible authentication results +window.dnoa_internal.authSuccess = 'auth-success'; +window.dnoa_internal.authRefused = 'auth-refused'; +window.dnoa_internal.timedOut = 'timed-out'; + +/// <summary>Instantiates a new FrameManager.</summary> +/// <param name="maxFrames">The maximum number of concurrent 'jobs' (authentication attempts).</param> +window.dnoa_internal.FrameManager = function(maxFrames) { + this.queuedWork = []; + this.frames = []; + this.maxFrames = maxFrames; + + /// <summary>Called to queue up some work that will use an iframe as soon as it is available.</summary> + /// <param name="job"> + /// A delegate that must return { url: /*to point the iframe to*/, onCanceled: /* callback */ } + /// Its first parameter is the iframe created to service the request. + /// It will only be called when the work actually begins. + /// </param> + /// <param name="p1">Arbitrary additional parameter to pass to the job.</param> + this.enqueueWork = function(job, p1) { + // Assign an iframe to this task immediately if there is one available. + if (this.frames.length < this.maxFrames) { + this.createIFrame(job, p1); + } else { + this.queuedWork.unshift({ job: job, p1: p1 }); + } + }; + + /// <summary>Clears the job queue and immediately closes all iframes.</summary> + this.cancelAllWork = function() { + trace('Canceling all open and pending iframes.'); + while (this.queuedWork.pop()) { } + this.closeFrames(); + }; + + /// <summary>An event fired when a frame is closing.</summary> + this.onJobCompleted = function() { + // If there is a job in the queue, go ahead and start it up. + if (jobDesc = this.queuedWork.pop()) { + this.createIFrame(jobDesc.job, jobDesc.p1); + } + }; + + this.createIFrame = function(job, p1) { + var iframe = document.createElement("iframe"); + if (!window.openid_visible_iframe) { + iframe.setAttribute("width", 0); + iframe.setAttribute("height", 0); + iframe.setAttribute("style", "display: none"); + } + var jobDescription = job(iframe, p1); + iframe.setAttribute("src", jobDescription.url); + iframe.onCanceled = jobDescription.onCanceled; + iframe.dnoa_internal = window.dnoa_internal; + document.body.insertBefore(iframe, document.body.firstChild); + this.frames.push(iframe); + return iframe; + }; + + this.closeFrames = function() { + if (this.frames.length === 0) { return false; } + for (var i = 0; i < this.frames.length; i++) { + this.frames[i].src = "about:blank"; // doesn't have to exist. Just stop its processing. + if (this.frames[i].parentNode) { this.frames[i].parentNode.removeChild(this.frames[i]); } + } + while (this.frames.length > 0) { + var frame = this.frames.pop(); + if (frame.onCanceled) { frame.onCanceled(); } + } + return true; + }; + + this.closeFrame = function(frame) { + frame.src = "about:blank"; // doesn't have to exist. Just stop its processing. + if (frame.parentNode) { frame.parentNode.removeChild(frame); } + var removed = this.frames.remove(frame); + this.onJobCompleted(); + return removed; + }; +}; /// <summary>Instantiates an object that represents an OpenID Identifier.</summary> -window.OpenId = function(identifier) { +window.OpenIdIdentifier = function(identifier) { + if (!identifier || identifier.length === 0) { + throw 'Error: trying to create OpenIdIdentifier for null or empty string.'; + } + /// <summary>Performs discovery on the identifier.</summary> - /// <param name="onCompleted">A function(DiscoveryResult) callback to be called when discovery has completed.</param> - this.discover = function(onCompleted) { - /// <summary>Instantiates an object that stores discovery results of some identifier.</summary> - function DiscoveryResult(identifier, discoveryInfo) { - /// <summary> - /// Instantiates an object that describes an OpenID service endpoint and facilitates - /// initiating and tracking an authentication request. - /// </summary> - function ServiceEndpoint(requestInfo, userSuppliedIdentifier) { - this.immediate = requestInfo.immediate ? new window.dnoa_internal.Uri(requestInfo.immediate) : null; - this.setup = requestInfo.setup ? new window.dnoa_internal.Uri(requestInfo.setup) : null; - this.endpoint = new window.dnoa_internal.Uri(requestInfo.endpoint); - this.userSuppliedIdentifier = userSuppliedIdentifier; - var self = this; // closure so that delegates have the right instance - this.loginPopup = function(onSuccess, onFailure) { - //self.abort(); // ensure no concurrent attempts - window.dnoa_internal.processAuthorizationResult = function(childLocation) { - window.dnoa_internal.processAuthorizationResult = null; - trace('Received event from child window: ' + childLocation); - var success = true; // TODO: discern between success and failure, and fire the correct event. - - if (success) { - if (onSuccess) { - onSuccess(); - } - } else { - if (onFailure) { - onFailure(); - } - } - }; - var width = 800; - var height = 600; - if (self.setup.getQueryArgValue("openid.return_to").indexOf("dotnetopenid.popupUISupported") >= 0) { - width = 450; - height = 500; + /// <param name="onDiscoverSuccess">A function(DiscoveryResult) callback to be called when discovery has completed successfully.</param> + /// <param name="onDiscoverFailure">A function callback to be called when discovery has completed in failure.</param> + this.discover = function(onDiscoverSuccess, onDiscoverFailure) { + /// <summary>Receives the results of a successful discovery (even if it yielded 0 results).</summary> + function discoverSuccessCallback(discoveryResult, identifier) { + trace('Discovery completed for: ' + identifier); + + // Deserialize the JSON object and store the result if it was a successful discovery. + discoveryResult = eval('(' + discoveryResult + ')'); + + // Add behavior for later use. + discoveryResult = new window.dnoa_internal.DiscoveryResult(identifier, discoveryResult); + window.dnoa_internal.discoveryResults[identifier] = discoveryResult; + + window.dnoa_internal.fireDiscoverySuccess(identifier, discoveryResult, { fresh: true }); + + // Clear our "in discovery" state and fire callbacks + var callbacks = window.dnoa_internal.discoveryInProgress[identifier]; + window.dnoa_internal.discoveryInProgress[identifier] = null; + + if (callbacks) { + for (var i = 0; i < callbacks.onSuccess.length; i++) { + if (callbacks.onSuccess[i]) { + callbacks.onSuccess[i](discoveryResult); + } + } + } + } + + /// <summary>Receives the discovery failure notification.</summary> + function discoverFailureCallback(message, userSuppliedIdentifier) { + trace('Discovery failed for: ' + identifier); + + // Clear our "in discovery" state and fire callbacks + var callbacks = window.dnoa_internal.discoveryInProgress[identifier]; + window.dnoa_internal.discoveryInProgress[identifier] = null; + + if (callbacks) { + for (var i = 0; i < callbacks.onSuccess.length; i++) { + if (callbacks.onFailure[i]) { + callbacks.onFailure[i](message); + } + } + } + + window.dnoa_internal.fireDiscoveryFailed(identifier, message); + } + + if (window.dnoa_internal.discoveryResults[identifier]) { + trace("We've already discovered " + identifier + " so we're using the cached version."); + + // In this special case, we never fire the DiscoveryStarted event. + window.dnoa_internal.fireDiscoverySuccess(identifier, window.dnoa_internal.discoveryResults[identifier], { fresh: false }); + + if (onDiscoverSuccess) { + onDiscoverSuccess(window.dnoa_internal.discoveryResults[identifier]); + } + + return; + } + + window.dnoa_internal.fireDiscoveryStarted(identifier); + + if (!window.dnoa_internal.discoveryInProgress[identifier]) { + trace('starting discovery on ' + identifier); + window.dnoa_internal.discoveryInProgress[identifier] = { + onSuccess: [onDiscoverSuccess], + onFailure: [onDiscoverFailure] + }; + window.dnoa_internal.callbackAsync(identifier, discoverSuccessCallback, discoverFailureCallback); + } else { + trace('Discovery on ' + identifier + ' already started. Registering an additional callback.'); + window.dnoa_internal.discoveryInProgress[identifier].onSuccess.push(onDiscoverSuccess); + window.dnoa_internal.discoveryInProgress[identifier].onFailure.push(onDiscoverFailure); + } + }; + + /// <summary>Performs discovery and immediately begins checkid_setup to authenticate the user using a given identifier.</summary> + this.login = function(onSuccess, onLoginFailure) { + this.discover(function(discoveryResult) { + if (discoveryResult) { + trace('Discovery succeeded and found ' + discoveryResult.length + ' OpenID service endpoints.'); + if (discoveryResult.length > 0) { + discoveryResult[0].loginPopup(onSuccess, onLoginFailure); + } else { + trace("This doesn't look like an OpenID Identifier. Aborting login."); + if (onLoginFailure) { + onLoginFailure(); } + } + } + }); + }; + + /// <summary>Performs discovery and immediately begins checkid_immediate on all discovered endpoints.</summary> + this.loginBackground = function(frameManager, onLoginSuccess, onLoginFailure, timeout, onLoginLastFailure) { + this.discover(function(discoveryResult) { + if (discoveryResult) { + trace('Discovery succeeded and found ' + discoveryResult.length + ' OpenID service endpoints.'); + if (discoveryResult.length > 0) { + discoveryResult.loginBackground(frameManager, onLoginSuccess, onLoginFailure, onLoginLastFailure || onLoginFailure, timeout); + } else { + trace("This doesn't look like an OpenID Identifier. Aborting login."); + if (onLoginFailure) { + onLoginFailure(); + } + } + } + }); + }; + + this.toString = function() { + return identifier; + }; +}; + +/// <summary>Invoked by RP web server when an authentication has completed.</summary> +/// <remarks>The duty of this method is to distribute the notification to the appropriate tracking object.</remarks> +window.dnoa_internal.processAuthorizationResult = function(resultUrl, extensionResponses) { + //trace('processAuthorizationResult ' + resultUrl); + var resultUri = new window.dnoa_internal.Uri(resultUrl); + trace('processing auth result with extensionResponses: ' + extensionResponses); + if (extensionResponses) { + extensionResponses = eval(extensionResponses); + } + + // Find the tracking object responsible for this request. + var userSuppliedIdentifier = resultUri.getQueryArgValue('dnoa.userSuppliedIdentifier'); + if (!userSuppliedIdentifier) { + throw 'processAuthorizationResult called but no userSuppliedIdentifier parameter was found. Exiting function.'; + } + var discoveryResult = window.dnoa_internal.discoveryResults[userSuppliedIdentifier]; + if (!discoveryResult) { + throw 'processAuthorizationResult called but no discovery result matching user supplied identifier ' + userSuppliedIdentifier + ' was found. Exiting function.'; + } + + var opEndpoint = resultUri.getQueryArgValue("openid.op_endpoint") ? resultUri.getQueryArgValue("openid.op_endpoint") : resultUri.getQueryArgValue("dnoa.op_endpoint"); + var respondingEndpoint = discoveryResult.findByEndpoint(opEndpoint); + trace('Auth result for ' + respondingEndpoint.host + ' received.'); //: ' + resultUrl); - var left = (screen.width - width) / 2; - var top = (screen.height - height) / 2; - self.popup = window.open(self.setup, 'opLogin', 'status=0,toolbar=0,location=1,resizable=1,scrollbars=1,left=' + left + ',top=' + top + ',width=' + width + ',height=' + height); + if (window.dnoa_internal.isAuthSuccessful(resultUri)) { + discoveryResult.successAuthData = resultUrl; + respondingEndpoint.onAuthSuccess(resultUri, extensionResponses); - // If the OP supports the UI extension it MAY close its own window - // for a negative assertion. We must be able to recover from that scenario. - var localSelf = self; - self.popupCloseChecker = window.setInterval(function() { - if (localSelf.popup && localSelf.popup.closed) { + var parsedPositiveAssertion = new window.dnoa_internal.PositiveAssertion(resultUri); + if (parsedPositiveAssertion.claimedIdentifier && parsedPositiveAssertion.claimedIdentifier != discoveryResult.claimedIdentifier) { + discoveryResult.claimedIdentifier = parsedPositiveAssertion.claimedIdentifier; + trace('Authenticated as ' + parsedPositiveAssertion.claimedIdentifier); + } + } else { + respondingEndpoint.onAuthFailed(); + } +}; + +window.dnoa_internal.isAuthSuccessful = function(resultUri) { + if (window.dnoa_internal.isOpenID2Response(resultUri)) { + return resultUri.getQueryArgValue("openid.mode") == "id_res"; + } else { + return resultUri.getQueryArgValue("openid.mode") == "id_res" && !resultUri.containsQueryArg("openid.user_setup_url"); + } +}; + +window.dnoa_internal.isOpenID2Response = function(resultUri) { + return resultUri.containsQueryArg("openid.ns"); +}; + +/// <summary>Instantiates an object that stores discovery results of some identifier.</summary> +window.dnoa_internal.DiscoveryResult = function(identifier, discoveryInfo) { + var thisDiscoveryResult = this; + + /// <summary> + /// Instantiates an object that describes an OpenID service endpoint and facilitates + /// initiating and tracking an authentication request. + /// </summary> + function ServiceEndpoint(requestInfo, userSuppliedIdentifier) { + this.immediate = requestInfo.immediate ? new window.dnoa_internal.Uri(requestInfo.immediate) : null; + this.setup = requestInfo.setup ? new window.dnoa_internal.Uri(requestInfo.setup) : null; + this.endpoint = new window.dnoa_internal.Uri(requestInfo.endpoint); + this.host = this.endpoint.getHost(); + this.userSuppliedIdentifier = userSuppliedIdentifier; + var thisServiceEndpoint = this; // closure so that delegates have the right instance + this.loginPopup = function(onAuthSuccess, onAuthFailed) { + thisServiceEndpoint.abort(); // ensure no concurrent attempts + window.dnoa_internal.fireAuthStarted(thisDiscoveryResult, thisServiceEndpoint, { background: false }); + thisDiscoveryResult.onAuthSuccess = onAuthSuccess; + thisDiscoveryResult.onAuthFailed = onAuthFailed; + var chromeHeight = 55; // estimated height of browser title bar and location bar + var bottomMargin = 45; // estimated bottom space on screen likely to include a task bar + var width = 1000; + var height = 600; + if (thisServiceEndpoint.setup.getQueryArgValue("openid.return_to").indexOf("dnoa.popupUISupported") >= 0) { + trace('This OP supports the UI extension. Using smaller window size.'); + width = 500; // spec calls for 450px, but Yahoo needs 500px + height = 500; + } else { + trace("This OP doesn't appear to support the UI extension. Using larger window size."); + } + + var left = (screen.width - width) / 2; + var top = (screen.height - bottomMargin - height - chromeHeight) / 2; + thisServiceEndpoint.popup = window.open(thisServiceEndpoint.setup, 'opLogin', 'status=0,toolbar=0,location=1,resizable=1,scrollbars=1,left=' + left + ',top=' + top + ',width=' + width + ',height=' + height); + + // If the OP supports the UI extension it MAY close its own window + // for a negative assertion. We must be able to recover from that scenario. + var thisServiceEndpointLocal = thisServiceEndpoint; + thisServiceEndpoint.popupCloseChecker = window.setInterval(function() { + if (thisServiceEndpointLocal.popup) { + try { + if (thisServiceEndpointLocal.popup.closed) { // The window closed, either because the user closed it, canceled at the OP, // or approved at the OP and the popup window closed itself due to our script. // If we were graying out the entire page while the child window was up, // we would probably revert that here. - window.clearInterval(localSelf.popupCloseChecker); - localSelf.popup = null; + window.clearInterval(thisServiceEndpointLocal.popupCloseChecker); + thisServiceEndpointLocal.popup = null; // The popup may have managed to inform us of the result already, // so check whether the callback method was cleared already, which // would indicate we've already processed this. if (window.dnoa_internal.processAuthorizationResult) { trace('User or OP canceled by closing the window.'); - if (onFailure) { - onFailure(); + window.dnoa_internal.fireAuthFailed(thisDiscoveryResult, thisServiceEndpoint, { background: false }); + if (thisDiscoveryResult.onAuthFailed) { + thisDiscoveryResult.onAuthFailed(thisDiscoveryResult, thisServiceEndpoint); } - window.dnoa_internal.processAuthorizationResult = null; } } - }, 250); - }; + } catch (e) { + // This usually happens because the popup is currently displaying the OP's + // page from another domain, which makes the popup temporarily off limits to us. + // Just skip this interval and wait for the next callback. + } + } else { + // if there's no popup, there's no reason to keep this timer up. + window.clearInterval(thisServiceEndpointLocal.popupCloseChecker); + } + }, 250); + }; + + this.loginBackgroundJob = function(iframe, timeout) { + thisServiceEndpoint.abort(); // ensure no concurrent attempts + if (timeout) { + thisServiceEndpoint.timeout = setTimeout(function() { thisServiceEndpoint.onAuthenticationTimedOut(); }, timeout); + } + window.dnoa_internal.fireAuthStarted(thisDiscoveryResult, thisServiceEndpoint, { background: true }); + trace('iframe hosting ' + thisServiceEndpoint.endpoint + ' now OPENING (timeout ' + timeout + ').'); + //trace('initiating auth attempt with: ' + thisServiceEndpoint.immediate); + thisServiceEndpoint.iframe = iframe; + return { + url: thisServiceEndpoint.immediate.toString(), + onCanceled: function() { + thisServiceEndpoint.abort(); + window.dnoa_internal.fireAuthFailed(thisDiscoveryResult, thisServiceEndpoint, { background: true }); + } }; + }; - this.userSuppliedIdentifier = identifier; - this.claimedIdentifier = discoveryInfo.claimedIdentifier; // The claimed identifier may be null if the user provided an OP Identifier. - trace('Discovered claimed identifier: ' + (this.claimedIdentifier ? this.claimedIdentifier : "(directed identity)")); + this.busy = function() { + return thisServiceEndpoint.iframe || thisServiceEndpoint.popup; + }; - if (discoveryInfo) { - this.length = discoveryInfo.requests.length; - for (var i = 0; i < discoveryInfo.requests.length; i++) { - this[i] = new ServiceEndpoint(discoveryInfo.requests[i], identifier); + this.completeAttempt = function(successful) { + if (!thisServiceEndpoint.busy()) { return false; } + window.clearInterval(thisServiceEndpoint.timeout); + var background = thisServiceEndpoint.iframe !== null; + if (thisServiceEndpoint.iframe) { + trace('iframe hosting ' + thisServiceEndpoint.endpoint + ' now CLOSING.'); + thisDiscoveryResult.frameManager.closeFrame(thisServiceEndpoint.iframe); + thisServiceEndpoint.iframe = null; + } + if (thisServiceEndpoint.popup) { + thisServiceEndpoint.popup.close(); + thisServiceEndpoint.popup = null; + } + if (thisServiceEndpoint.timeout) { + window.clearTimeout(thisServiceEndpoint.timeout); + thisServiceEndpoint.timeout = null; + } + + if (!successful && !thisDiscoveryResult.busy() && !thisDiscoveryResult.findSuccessfulRequest()) { + // fire the failed event with NO service endpoint indicating the entire auth attempt has failed. + window.dnoa_internal.fireAuthFailed(thisDiscoveryResult, null, { background: background }); + if (thisDiscoveryResult.onLastAttemptFailed) { + thisDiscoveryResult.onLastAttemptFailed(thisDiscoveryResult); } - } else { - this.length = 0; } - }; - /// <summary>Receives the results of a successful discovery (even if it yielded 0 results).</summary> - function successCallback(discoveryResult, identifier) { - trace('Discovery completed for: ' + identifier); + return true; + }; - // Deserialize the JSON object and store the result if it was a successful discovery. - discoveryResult = eval('(' + discoveryResult + ')'); + this.onAuthenticationTimedOut = function() { + var background = thisServiceEndpoint.iframe !== null; + if (thisServiceEndpoint.completeAttempt()) { + trace(thisServiceEndpoint.host + " timed out"); + thisServiceEndpoint.result = window.dnoa_internal.timedOut; + } + window.dnoa_internal.fireAuthFailed(thisDiscoveryResult, thisServiceEndpoint, { background: background }); + }; - // Add behavior for later use. - discoveryResult = new DiscoveryResult(identifier, discoveryResult); - window.dnoa_internal.discoveryResults[identifier] = discoveryResult; + this.onAuthSuccess = function(authUri, extensionResponses) { + var background = thisServiceEndpoint.iframe !== null; + if (thisServiceEndpoint.completeAttempt(true)) { + trace(thisServiceEndpoint.host + " authenticated!"); + thisServiceEndpoint.result = window.dnoa_internal.authSuccess; + thisServiceEndpoint.successReceived = new Date(); + thisServiceEndpoint.claimedIdentifier = authUri.getQueryArgValue('openid.claimed_id'); + thisServiceEndpoint.response = authUri; + thisServiceEndpoint.extensionResponses = extensionResponses; + thisDiscoveryResult.abortAll(); + if (thisDiscoveryResult.onAuthSuccess) { + thisDiscoveryResult.onAuthSuccess(thisDiscoveryResult, thisServiceEndpoint, extensionResponses); + } + window.dnoa_internal.fireAuthSuccess(thisDiscoveryResult, thisServiceEndpoint, extensionResponses, { background: background }); + } + }; - if (onCompleted) { - onCompleted(discoveryResult); + this.onAuthFailed = function() { + var background = thisServiceEndpoint.iframe !== null; + if (thisServiceEndpoint.completeAttempt()) { + trace(thisServiceEndpoint.host + " failed authentication"); + thisServiceEndpoint.result = window.dnoa_internal.authRefused; + window.dnoa_internal.fireAuthFailed(thisDiscoveryResult, thisServiceEndpoint, { background: background }); + if (thisDiscoveryResult.onAuthFailed) { + thisDiscoveryResult.onAuthFailed(thisDiscoveryResult, thisServiceEndpoint); + } } }; - /// <summary>Receives the discovery failure notification.</summary> - failureCallback = function(message, userSuppliedIdentifier) { - trace('Discovery failed for: ' + identifier); + this.abort = function() { + if (thisServiceEndpoint.completeAttempt()) { + trace(thisServiceEndpoint.host + " aborted"); + // leave the result as whatever it was before. + } + }; - if (onCompleted) { - onCompleted(); + this.clear = function() { + thisServiceEndpoint.result = null; + thisServiceEndpoint.extensionResponses = null; + thisServiceEndpoint.successReceived = null; + thisServiceEndpoint.claimedIdentifier = null; + thisServiceEndpoint.response = null; + if (this.onCleared) { + this.onCleared(thisServiceEndpoint, thisDiscoveryResult); + } + if (thisDiscoveryResult.onCleared) { + thisDiscoveryResult.onCleared(thisDiscoveryResult, thisServiceEndpoint); } + window.dnoa_internal.fireAuthCleared(thisDiscoveryResult, thisServiceEndpoint); }; - if (window.dnoa_internal.discoveryResults[identifier]) { - trace("We've already discovered " + identifier + " so we're skipping it this time."); - onCompleted(window.dnoa_internal.discoveryResults[identifier]); + this.toString = function() { + return "[ServiceEndpoint: " + thisServiceEndpoint.host + "]"; + }; + } + + this.cloneWithOneServiceEndpoint = function(serviceEndpoint) { + var clone = window.dnoa_internal.clone(this); + clone.userSuppliedIdentifier = serviceEndpoint.claimedIdentifier; + + // Erase all SEPs except the given one, and put it into first position. + clone.length = 1; + for (var i = 0; i < this.length; i++) { + if (clone[i].endpoint.toString() == serviceEndpoint.endpoint.toString()) { + var tmp = clone[i]; + clone[i] = null; + clone[0] = tmp; + } else { + clone[i] = null; + } } - trace('starting discovery on ' + identifier); - window.dnoa_internal.callbackAsync(identifier, successCallback, failureCallback); + return clone; }; - /// <summary>Performs discovery and immediately begins checkid_setup to authenticate the user using a given identifier.</summary> - this.login = function(onSuccess, onFailure) { - this.discover(function(discoveryResult) { - if (discoveryResult) { - trace('Discovery succeeded and found ' + discoveryResult.length + ' OpenID service endpoints.'); - if (discoveryResult.length > 0) { - discoveryResult[0].loginPopup(onSuccess, onFailure); - } else { - trace("This doesn't look like an OpenID Identifier. Aborting login."); - if (onFailure) { - onFailure(); - } + this.userSuppliedIdentifier = identifier; + this.error = discoveryInfo.error; + + if (discoveryInfo) { + this.claimedIdentifier = discoveryInfo.claimedIdentifier; // The claimed identifier may be null if the user provided an OP Identifier. + this.length = discoveryInfo.requests.length; + for (var i = 0; i < discoveryInfo.requests.length; i++) { + this[i] = new ServiceEndpoint(discoveryInfo.requests[i], identifier); + } + } else { + this.length = 0; + } + + if (this.length === 0) { + trace('Discovery completed, but yielded no service endpoints.'); + } else { + trace('Discovered claimed identifier: ' + (this.claimedIdentifier ? this.claimedIdentifier : "(directed identity)")); + } + + // Add extra tracking bits and behaviors. + this.findByEndpoint = function(opEndpoint) { + for (var i = 0; i < thisDiscoveryResult.length; i++) { + if (thisDiscoveryResult[i].endpoint == opEndpoint) { + return thisDiscoveryResult[i]; + } + } + }; + + this.busy = function() { + for (var i = 0; i < thisDiscoveryResult.length; i++) { + if (thisDiscoveryResult[i].busy()) { + return true; + } + } + }; + + // Add extra tracking bits and behaviors. + this.findSuccessfulRequest = function() { + for (var i = 0; i < thisDiscoveryResult.length; i++) { + if (thisDiscoveryResult[i].result === window.dnoa_internal.authSuccess) { + return thisDiscoveryResult[i]; + } + } + }; + + this.abortAll = function() { + if (thisDiscoveryResult.frameManager) { + // Abort all other asynchronous authentication attempts that may be in progress + // for this particular claimed identifier. + thisDiscoveryResult.frameManager.cancelAllWork(); + for (var i = 0; i < thisDiscoveryResult.length; i++) { + thisDiscoveryResult[i].abort(); + } + } else { + trace('abortAll called without a frameManager being previously set.'); + } + }; + + /// <summary>Initiates an asynchronous checkid_immediate login attempt against all possible service endpoints for an Identifier.</summary> + /// <param name="frameManager">The work queue for authentication iframes.</param> + /// <param name="onAuthSuccess">Fired when an endpoint responds affirmatively.</param> + /// <param name="onAuthFailed">Fired when an endpoint responds negatively.</param> + /// <param name="onLastAuthFailed">Fired when all authentication attempts have responded negatively or timed out.</param> + /// <param name="timeout">Timeout for an individual service endpoint to respond before the iframe closes.</param> + this.loginBackground = function(frameManager, onAuthSuccess, onAuthFailed, onLastAuthFailed, timeout) { + if (!frameManager) { + throw "No frameManager specified."; + } + var priorSuccessRespondingEndpoint = thisDiscoveryResult.findSuccessfulRequest(); + if (priorSuccessRespondingEndpoint) { + // In this particular case, we do not fire an AuthStarted event. + window.dnoa_internal.fireAuthSuccess(thisDiscoveryResult, priorSuccessRespondingEndpoint, priorSuccessRespondingEndpoint.extensionResponses, { background: true }); + if (onAuthSuccess) { + onAuthSuccess(thisDiscoveryResult, priorSuccessRespondingEndpoint); + } + } else { + if (thisDiscoveryResult.busy()) { + trace('Warning: DiscoveryResult.loginBackground invoked while a login attempt is already in progress. Discarding second login request.', 'red'); + return; + } + thisDiscoveryResult.frameManager = frameManager; + thisDiscoveryResult.onAuthSuccess = onAuthSuccess; + thisDiscoveryResult.onAuthFailed = onAuthFailed; + thisDiscoveryResult.onLastAttemptFailed = onLastAuthFailed; + // Notify listeners that general authentication is beginning. Individual ServiceEndpoints + // will fire their own events as each of them begin their iframe 'job'. + window.dnoa_internal.fireAuthStarted(thisDiscoveryResult, null, { background: true }); + if (thisDiscoveryResult.length > 0) { + for (var i = 0; i < thisDiscoveryResult.length; i++) { + thisDiscoveryResult.frameManager.enqueueWork(thisDiscoveryResult[i].loginBackgroundJob, timeout); } } - }); + } + }; + + this.toString = function() { + return "[DiscoveryResult: " + thisDiscoveryResult.userSuppliedIdentifier + "]"; + }; +}; + +/// <summary> +/// Called in a page had an AJAX control that had already obtained a positive assertion +/// when a postback occurred, and now that control wants to restore its 'authenticated' state. +/// </summary> +/// <param name="positiveAssertion">The string form of the URI that contains the positive assertion.</param> +window.dnoa_internal.deserializePreviousAuthentication = function(positiveAssertion) { + if (!positiveAssertion || positiveAssertion.length === 0) { + return; + } + + trace('Revitalizing an old positive assertion from a prior postback.'); + + // The control ensures that we ALWAYS have an OpenID 2.0-style claimed_id attribute, even against + // 1.0 Providers via the return_to URL mechanism. + var parsedPositiveAssertion = new window.dnoa_internal.PositiveAssertion(positiveAssertion); + + // We weren't given a full discovery history, but we can spoof this much from the + // authentication assertion. + trace('Deserialized claimed_id: ' + parsedPositiveAssertion.claimedIdentifier + ' and endpoint: ' + parsedPositiveAssertion.endpoint); + var discoveryInfo = { + claimedIdentifier: parsedPositiveAssertion.claimedIdentifier, + requests: [{ endpoint: parsedPositiveAssertion.endpoint}] }; + + discoveryResult = new window.dnoa_internal.DiscoveryResult(parsedPositiveAssertion.userSuppliedIdentifier, discoveryInfo); + window.dnoa_internal.discoveryResults[parsedPositiveAssertion.userSuppliedIdentifier] = discoveryResult; + discoveryResult[0].result = window.dnoa_internal.authSuccess; + discoveryResult.successAuthData = positiveAssertion; + + // restore old state from before postback + window.dnoa_internal.fireAuthSuccess(discoveryResult, discoveryResult[0], null, { background: true, deserialized: true }); +}; + +window.dnoa_internal.PositiveAssertion = function(uri) { + uri = new window.dnoa_internal.Uri(uri.toString()); + this.endpoint = new window.dnoa_internal.Uri(uri.getQueryArgValue("dnoa.op_endpoint")); + this.userSuppliedIdentifier = uri.getQueryArgValue('dnoa.userSuppliedIdentifier'); + this.claimedIdentifier = uri.getQueryArgValue('openid.claimed_id'); + if (!this.claimedIdentifier) { + this.claimedIdentifier = uri.getQueryArgValue('dnoa.claimed_id'); + } + this.toString = function() { return uri.toString(); }; +}; + +window.dnoa_internal.clone = function(obj) { + if (obj === null || typeof (obj) != 'object') { + return obj; + } + + var temp = {}; + for (var key in obj) { + temp[key] = window.dnoa_internal.clone(obj[key]); + } + + return temp; +}; + +// Deserialized the preloaded discovery results +window.dnoa_internal.loadPreloadedDiscoveryResults = function(preloadedDiscoveryResults) { + trace('found ' + preloadedDiscoveryResults.length + ' preloaded discovery results.'); + for (var i = 0; i < preloadedDiscoveryResults.length; i++) { + var result = preloadedDiscoveryResults[i]; + if (!window.dnoa_internal.discoveryResults[result.userSuppliedIdentifier]) { + window.dnoa_internal.discoveryResults[result.userSuppliedIdentifier] = new window.dnoa_internal.DiscoveryResult(result.userSuppliedIdentifier, result.discoveryResult); + trace('Preloaded discovery on: ' + window.dnoa_internal.discoveryResults[result.userSuppliedIdentifier].userSuppliedIdentifier); + } else { + trace('Skipped preloaded discovery on: ' + window.dnoa_internal.discoveryResults[result.userSuppliedIdentifier].userSuppliedIdentifier + ' because we have a cached discovery result on it.'); + } + } +}; + +window.dnoa_internal.clearExpiredPositiveAssertions = function() { + for (identifier in window.dnoa_internal.discoveryResults) { + var discoveryResult = window.dnoa_internal.discoveryResults[identifier]; + if (typeof (discoveryResult) != 'object') { continue; } // skip functions + for (var i = 0; i < discoveryResult.length; i++) { + if (discoveryResult[i].result === window.dnoa_internal.authSuccess) { + if (new Date() - discoveryResult[i].successReceived > window.dnoa_internal.maxPositiveAssertionLifetime) { + // This positive assertion is too old, and may eventually be rejected by DNOA during verification. + // Let's clear out the positive assertion so it can be renewed. + trace('Clearing out expired positive assertion from ' + discoveryResult[i].host); + discoveryResult[i].clear(); + } + } + } + } }; +window.setInterval(window.dnoa_internal.clearExpiredPositiveAssertions, 1000); diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs index 0efabd2..09fcbcb 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs @@ -9,6 +9,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { using System; using System.Collections.Generic; + using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; @@ -25,17 +26,72 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.Extensions; using DotNetOpenAuth.OpenId.Extensions.UI; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// Methods of indicating to the rest of the web site that the user has logged in. + /// </summary> + [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "OnSite", Justification = "Two words intended.")] + public enum LogOnSiteNotification { + /// <summary> + /// The rest of the web site is unaware that the user just completed an OpenID login. + /// </summary> + None, + + /// <summary> + /// After the <see cref="OpenIdRelyingPartyControlBase.LoggedIn"/> event is fired + /// the control automatically calls <see cref="System.Web.Security.FormsAuthentication.RedirectFromLoginPage(string, bool)"/> + /// with the <see cref="IAuthenticationResponse.ClaimedIdentifier"/> as the username + /// unless the <see cref="OpenIdRelyingPartyControlBase.LoggedIn"/> event handler sets + /// <see cref="OpenIdEventArgs.Cancel"/> property to true. + /// </summary> + FormsAuthentication, + } + + /// <summary> + /// How an OpenID user session should be persisted across visits. + /// </summary> + public enum LogOnPersistence { + /// <summary> + /// The user should only be logged in as long as the browser window remains open. + /// Nothing is persisted to help the user on a return visit. Public kiosk mode. + /// </summary> + Session, + + /// <summary> + /// The user should only be logged in as long as the browser window remains open. + /// The OpenID Identifier is persisted to help expedite re-authentication when + /// the user visits the next time. + /// </summary> + SessionAndPersistentIdentifier, + + /// <summary> + /// The user is issued a persistent authentication ticket so that no login is + /// necessary on their return visit. + /// </summary> + PersistentAuthentication, + } /// <summary> /// A common base class for OpenID Relying Party controls. /// </summary> [DefaultProperty("Identifier"), ValidationProperty("Identifier")] - public abstract class OpenIdRelyingPartyControlBase : Control { + public abstract class OpenIdRelyingPartyControlBase : Control, IPostBackEventHandler, IDisposable { /// <summary> /// The manifest resource name of the javascript file to include on the hosting page. /// </summary> internal const string EmbeddedJavascriptResource = Util.DefaultNamespace + ".OpenId.RelyingParty.OpenIdRelyingPartyControlBase.js"; + /// <summary> + /// The cookie used to persist the Identifier the user logged in with. + /// </summary> + internal const string PersistentIdentifierCookieName = OpenIdUtilities.CustomParameterPrefix + "OpenIDIdentifier"; + + /// <summary> + /// The callback parameter name to use to store which control initiated the auth request. + /// </summary> + internal const string ReturnToReceivingControlId = OpenIdUtilities.CustomParameterPrefix + "receiver"; + #region Property category constants /// <summary> @@ -55,6 +111,31 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { #endregion + #region Callback parameter names + + /// <summary> + /// The callback parameter to use for recognizing when the callback is in a popup window or hidden iframe. + /// </summary> + protected const string UIPopupCallbackKey = OpenIdUtilities.CustomParameterPrefix + "uipopup"; + + /// <summary> + /// The parameter name to include in the formulated auth request so that javascript can know whether + /// the OP advertises support for the UI extension. + /// </summary> + protected const string PopupUISupportedJSHint = OpenIdUtilities.CustomParameterPrefix + "popupUISupported"; + + /// <summary> + /// The callback parameter for use with persisting the <see cref="UsePersistentCookie"/> property. + /// </summary> + private const string UsePersistentCookieCallbackKey = OpenIdUtilities.CustomParameterPrefix + "UsePersistentCookie"; + + /// <summary> + /// The callback parameter to use for recognizing when the callback is in the parent window. + /// </summary> + private const string UIPopupCallbackParentKey = OpenIdUtilities.CustomParameterPrefix + "uipopupParent"; + + #endregion + #region Property default values /// <summary> @@ -70,7 +151,12 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// Default value of <see cref="UsePersistentCookie"/>. /// </summary> - private const bool UsePersistentCookieDefault = false; + private const LogOnPersistence UsePersistentCookieDefault = LogOnPersistence.Session; + + /// <summary> + /// Default value of <see cref="LogOnMode"/>. + /// </summary> + private const LogOnSiteNotification LogOnModeDefault = LogOnSiteNotification.FormsAuthentication; /// <summary> /// The default value for the <see cref="RealmUrl"/> property. @@ -92,6 +178,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { #region Property view state keys /// <summary> + /// The viewstate key to use for storing the value of the <see cref="Extensions"/> property. + /// </summary> + private const string ExtensionsViewStateKey = "Extensions"; + + /// <summary> /// The viewstate key to use for the <see cref="Stateless"/> property. /// </summary> private const string StatelessViewStateKey = "Stateless"; @@ -102,6 +193,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { private const string UsePersistentCookieViewStateKey = "UsePersistentCookie"; /// <summary> + /// The viewstate key to use for the <see cref="LogOnMode"/> property. + /// </summary> + private const string LogOnModeViewStateKey = "LogOnMode"; + + /// <summary> /// The viewstate key to use for the <see cref="RealmUrl"/> property. /// </summary> private const string RealmUrlViewStateKey = "RealmUrl"; @@ -128,40 +224,21 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { #endregion - #region Callback parameter names - - /// <summary> - /// The callback parameter for use with persisting the <see cref="UsePersistentCookie"/> property. - /// </summary> - private const string UsePersistentCookieCallbackKey = OpenIdUtilities.CustomParameterPrefix + "UsePersistentCookie"; - /// <summary> - /// The callback parameter to use for recognizing when the callback is in a popup window. + /// The lifetime of the cookie used to persist the Identifier the user logged in with. /// </summary> - private const string UIPopupCallbackKey = OpenIdUtilities.CustomParameterPrefix + "uipopup"; + private static readonly TimeSpan PersistentIdentifierTimeToLiveDefault = TimeSpan.FromDays(14); /// <summary> - /// The callback parameter to use for recognizing when the callback is in the parent window. - /// </summary> - private const string UIPopupCallbackParentKey = OpenIdUtilities.CustomParameterPrefix + "uipopupParent"; - - /// <summary> - /// The callback parameter name to use to store which control initiated the auth request. - /// </summary> - private const string ReturnToReceivingControlId = OpenIdUtilities.CustomParameterPrefix + "receiver"; - - /// <summary> - /// The parameter name to include in the formulated auth request so that javascript can know whether - /// the OP advertises support for the UI extension. + /// Backing field for the <see cref="RelyingParty"/> property. /// </summary> - private const string PopupUISupportedJsHint = "dotnetopenid.popupUISupported"; - - #endregion + private OpenIdRelyingParty relyingParty; /// <summary> - /// Backing field for the <see cref="RelyingParty"/> property. + /// A value indicating whether the <see cref="relyingParty"/> field contains + /// an instance that we own and should Dispose. /// </summary> - private OpenIdRelyingParty relyingParty; + private bool relyingPartyOwned; /// <summary> /// Initializes a new instance of the <see cref="OpenIdRelyingPartyControlBase"/> class. @@ -172,11 +249,10 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { #region Events /// <summary> - /// Fired after the user clicks the log in button, but before the authentication - /// process begins. Offers a chance for the web application to disallow based on - /// OpenID URL before redirecting the user to the OpenID Provider. + /// Fired when the user has typed in their identifier, discovery was successful + /// and a login attempt is about to begin. /// </summary> - [Description("Fired after the user clicks the log in button, but before the authentication process begins. Offers a chance for the web application to disallow based on OpenID URL before redirecting the user to the OpenID Provider."), Category(OpenIdCategory)] + [Description("Fired when the user has typed in their identifier, discovery was successful and a login attempt is about to begin."), Category(OpenIdCategory)] public event EventHandler<OpenIdEventArgs> LoggingIn; /// <summary> @@ -197,6 +273,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { [Description("Fired when an authentication attempt is canceled at the OpenID Provider."), Category(OpenIdCategory)] public event EventHandler<OpenIdEventArgs> Canceled; + /// <summary> + /// Occurs when the <see cref="Identifier"/> property is changed. + /// </summary> + protected event EventHandler IdentifierChanged; + #endregion /// <summary> @@ -215,12 +296,34 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { get { if (this.relyingParty == null) { this.relyingParty = this.CreateRelyingParty(); + this.relyingPartyOwned = true; } return this.relyingParty; } set { + if (this.relyingPartyOwned && this.relyingParty != null) { + this.relyingParty.Dispose(); + } + this.relyingParty = value; + this.relyingPartyOwned = false; + } + } + + /// <summary> + /// Gets the collection of extension requests this selector should include in generated requests. + /// </summary> + [PersistenceMode(PersistenceMode.InnerProperty)] + public Collection<IOpenIdMessageExtension> Extensions { + get { + if (this.ViewState[ExtensionsViewStateKey] == null) { + var extensions = new Collection<IOpenIdMessageExtension>(); + this.ViewState[ExtensionsViewStateKey] = extensions; + return extensions; + } else { + return (Collection<IOpenIdMessageExtension>)this.ViewState[ExtensionsViewStateKey]; + } } } @@ -306,12 +409,22 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { [Bindable(true), DefaultValue(UsePersistentCookieDefault), Category(BehaviorCategory)] [Description("Whether to send a persistent cookie upon successful " + "login so the user does not have to log in upon returning to this site.")] - public virtual bool UsePersistentCookie { - get { return (bool)(this.ViewState[UsePersistentCookieViewStateKey] ?? UsePersistentCookieDefault); } + public virtual LogOnPersistence UsePersistentCookie { + get { return (LogOnPersistence)(this.ViewState[UsePersistentCookieViewStateKey] ?? UsePersistentCookieDefault); } set { this.ViewState[UsePersistentCookieViewStateKey] = value; } } /// <summary> + /// Gets or sets the way a completed login is communicated to the rest of the web site. + /// </summary> + [Bindable(true), DefaultValue(LogOnModeDefault), Category(BehaviorCategory)] + [Description("The way a completed login is communicated to the rest of the web site.")] + public virtual LogOnSiteNotification LogOnMode { + get { return (LogOnSiteNotification)(this.ViewState[LogOnModeViewStateKey] ?? LogOnModeDefault); } + set { this.ViewState[LogOnModeViewStateKey] = value; } + } + + /// <summary> /// Gets or sets a value indicating when to use a popup window to complete the login experience. /// </summary> /// <value>The default value is <see cref="PopupBehavior.Never"/>.</value> @@ -334,15 +447,20 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> - /// Gets or sets the URL to your privacy policy page that describes how - /// claims will be used and/or shared. + /// Gets or sets the Identifier that will be used to initiate login. /// </summary> [Bindable(true), Category(OpenIdCategory)] [Description("The OpenID Identifier that this button will use to initiate login.")] [TypeConverter(typeof(IdentifierConverter))] - public Identifier Identifier { - get { return (Identifier)ViewState[IdentifierViewStateKey]; } - set { ViewState[IdentifierViewStateKey] = value; } + public virtual Identifier Identifier { + get { + return (Identifier)ViewState[IdentifierViewStateKey]; + } + + set { + ViewState[IdentifierViewStateKey] = value; + this.OnIdentifierChanged(); + } } /// <summary> @@ -351,11 +469,42 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { internal AssociationPreference AssociationPreference { get; set; } /// <summary> + /// Gets ancestor controls, starting with the immediate parent, and progressing to more distant ancestors. + /// </summary> + protected IEnumerable<Control> ParentControls { + get { + Control parent = this; + while ((parent = parent.Parent) != null) { + yield return parent; + } + } + } + + /// <summary> + /// Clears any cookie set by this control to help the user on a returning visit next time. + /// </summary> + public static void LogOff() { + HttpContext.Current.Response.SetCookie(CreateIdentifierPersistingCookie(null)); + } + + /// <summary> /// Immediately redirects to the OpenID Provider to verify the Identifier /// provided in the text box. /// </summary> public void LogOn() { IAuthenticationRequest request = this.CreateRequests().FirstOrDefault(); + ErrorUtilities.VerifyProtocol(request != null, OpenIdStrings.OpenIdEndpointNotFound); + this.LogOn(request); + } + + /// <summary> + /// Immediately redirects to the OpenID Provider to verify the Identifier + /// provided in the text box. + /// </summary> + /// <param name="request">The request.</param> + public void LogOn(IAuthenticationRequest request) { + Contract.Requires<ArgumentNullException>(request != null); + if (this.IsPopupAppropriate(request)) { this.ScriptPopupWindow(request); } else { @@ -363,73 +512,74 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } } + #region IPostBackEventHandler Members + /// <summary> - /// Creates the authentication requests for a given user-supplied Identifier. + /// When implemented by a class, enables a server control to process an event raised when a form is posted to the server. /// </summary> - /// <returns>A sequence of authentication requests, any one of which may be - /// used to determine the user's control of the <see cref="IAuthenticationRequest.ClaimedIdentifier"/>.</returns> - protected virtual IEnumerable<IAuthenticationRequest> CreateRequests() { - Contract.Requires(this.Identifier != null, OpenIdStrings.NoIdentifierSet); - ErrorUtilities.VerifyOperation(this.Identifier != null, OpenIdStrings.NoIdentifierSet); - IEnumerable<IAuthenticationRequest> requests; - - // Approximate the returnTo (either based on the customize property or the page URL) - // so we can use it to help with Realm resolution. - Uri returnToApproximation = this.ReturnToUrl != null ? new Uri(this.RelyingParty.Channel.GetRequestFromContext().UrlBeforeRewriting, this.ReturnToUrl) : this.Page.Request.Url; - - // Resolve the trust root, and swap out the scheme and port if necessary to match the - // return_to URL, since this match is required by OpenId, and the consumer app - // may be using HTTP at some times and HTTPS at others. - UriBuilder realm = OpenIdUtilities.GetResolvedRealm(this.Page, this.RealmUrl, this.RelyingParty.Channel.GetRequestFromContext()); - realm.Scheme = returnToApproximation.Scheme; - realm.Port = returnToApproximation.Port; - - // Initiate openid request - // We use TryParse here to avoid throwing an exception which - // might slip through our validator control if it is disabled. - Realm typedRealm = new Realm(realm); - if (string.IsNullOrEmpty(this.ReturnToUrl)) { - requests = this.RelyingParty.CreateRequests(this.Identifier, typedRealm); - } else { - // Since the user actually gave us a return_to value, - // the "approximation" is exactly what we want. - requests = this.RelyingParty.CreateRequests(this.Identifier, typedRealm, returnToApproximation); - } + /// <param name="eventArgument">A <see cref="T:System.String"/> that represents an optional event argument to be passed to the event handler.</param> + void IPostBackEventHandler.RaisePostBackEvent(string eventArgument) { + this.RaisePostBackEvent(eventArgument); + } - // Some OPs may be listed multiple times (one with HTTPS and the other with HTTP, for example). - // Since we're gathering OPs to try one after the other, just take the first choice of each OP - // and don't try it multiple times. - requests = requests.Distinct(DuplicateRequestedHostsComparer.Instance); + #endregion - // Configure each generated request. - foreach (var req in requests) { - if (this.IsPopupAppropriate(req)) { - // Inform the OP that we'll be using a popup window. - req.AddExtension(new UIRequest()); + /// <summary> + /// Enables a server control to perform final clean up before it is released from memory. + /// </summary> + [SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "Base class doesn't implement virtual Dispose(bool), so we must call its Dispose() method.")] + public sealed override void Dispose() { + this.Dispose(true); + base.Dispose(); + GC.SuppressFinalize(this); + } - // Inform ourselves in return_to that we're in a popup. - req.AddCallbackArguments(UIPopupCallbackKey, "1"); + /// <summary> + /// Creates the authentication requests for a given user-supplied Identifier. + /// </summary> + /// <param name="identifier">The identifier to create a request for.</param> + /// <returns> + /// A sequence of authentication requests, any one of which may be + /// used to determine the user's control of the <see cref="IAuthenticationRequest.ClaimedIdentifier"/>. + /// </returns> + protected internal virtual IEnumerable<IAuthenticationRequest> CreateRequests(Identifier identifier) { + Contract.Requires<ArgumentNullException>(identifier != null); - if (req.Provider.IsExtensionSupported<UIRequest>()) { - // Provide a hint for the client javascript about whether the OP supports the UI extension. - // This is so the window can be made the correct size for the extension. - // If the OP doesn't advertise support for the extension, the javascript will use - // a bigger popup window. - req.AddCallbackArguments(PopupUISupportedJsHint, "1"); - } - } + // Delegate to a private method to keep 'yield return' and Code Contract separate. + return this.CreateRequestsCore(identifier); + } - // Add state that needs to survive across the redirect. - if (!this.Stateless) { - req.AddCallbackArguments(UsePersistentCookieCallbackKey, this.UsePersistentCookie.ToString(CultureInfo.InvariantCulture)); - req.AddCallbackArguments(ReturnToReceivingControlId, this.ClientID); + /// <summary> + /// Releases unmanaged and - optionally - managed resources + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool disposing) { + if (disposing) { + if (this.relyingPartyOwned && this.relyingParty != null) { + this.relyingParty.Dispose(); + this.relyingParty = null; } + } + } - ((AuthenticationRequest)req).AssociationPreference = this.AssociationPreference; - this.OnLoggingIn(req); + /// <summary> + /// When implemented by a class, enables a server control to process an event raised when a form is posted to the server. + /// </summary> + /// <param name="eventArgument">A <see cref="T:System.String"/> that represents an optional event argument to be passed to the event handler.</param> + [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "Predefined signature.")] + protected virtual void RaisePostBackEvent(string eventArgument) { + } - yield return req; - } + /// <summary> + /// Creates the authentication requests for the value set in the <see cref="Identifier"/> property. + /// </summary> + /// <returns> + /// A sequence of authentication requests, any one of which may be + /// used to determine the user's control of the <see cref="IAuthenticationRequest.ClaimedIdentifier"/>. + /// </returns> + protected IEnumerable<IAuthenticationRequest> CreateRequests() { + Contract.Requires<InvalidOperationException>(this.Identifier != null, OpenIdStrings.NoIdentifierSet); + return this.CreateRequests(this.Identifier); } /// <summary> @@ -444,12 +594,16 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { return; } + if (this.Identifier == null) { + this.TryPresetIdentifierWithCookie(); + } + // Take an unreliable sneek peek to see if we're in a popup and an OpenID // assertion is coming in. We shouldn't process assertions in a popup window. if (this.Page.Request.QueryString[UIPopupCallbackKey] == "1" && this.Page.Request.QueryString[UIPopupCallbackParentKey] == null) { // We're in a popup window. We need to close it and pass the // message back to the parent window for processing. - this.ScriptClosingPopup(); + this.ScriptClosingPopupOrIFrame(); return; // don't do any more processing on it now } @@ -458,33 +612,51 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { string receiver = this.Page.Request.QueryString[ReturnToReceivingControlId] ?? this.Page.Request.Form[ReturnToReceivingControlId]; if (receiver == null || receiver == this.ClientID) { var response = this.RelyingParty.GetResponse(); - if (response != null) { - string persistentString = response.GetUntrustedCallbackArgument(UsePersistentCookieCallbackKey); - bool persistentBool; - if (persistentString != null && bool.TryParse(persistentString, out persistentBool)) { - this.UsePersistentCookie = persistentBool; - } + this.ProcessResponse(response); + } + } - switch (response.Status) { - case AuthenticationStatus.Authenticated: - this.OnLoggedIn(response); - break; - case AuthenticationStatus.Canceled: - this.OnCanceled(response); - break; - case AuthenticationStatus.Failed: - this.OnFailed(response); - break; - case AuthenticationStatus.SetupRequired: - case AuthenticationStatus.ExtensionsOnly: - default: - // The NotApplicable (extension-only assertion) is NOT one that we support - // in this control because that scenario is primarily interesting to RPs - // that are asking a specific OP, and it is not user-initiated as this textbox - // is designed for. - throw new InvalidOperationException(MessagingStrings.UnexpectedMessageReceivedOfMany); - } - } + /// <summary> + /// Called when the <see cref="Identifier"/> property is changed. + /// </summary> + protected virtual void OnIdentifierChanged() { + var identifierChanged = this.IdentifierChanged; + if (identifierChanged != null) { + identifierChanged(this, EventArgs.Empty); + } + } + + /// <summary> + /// Processes the response. + /// </summary> + /// <param name="response">The response.</param> + protected virtual void ProcessResponse(IAuthenticationResponse response) { + if (response == null) { + return; + } + string persistentString = response.GetUntrustedCallbackArgument(UsePersistentCookieCallbackKey); + if (persistentString != null) { + this.UsePersistentCookie = (LogOnPersistence)Enum.Parse(typeof(LogOnPersistence), persistentString); + } + + switch (response.Status) { + case AuthenticationStatus.Authenticated: + this.OnLoggedIn(response); + break; + case AuthenticationStatus.Canceled: + this.OnCanceled(response); + break; + case AuthenticationStatus.Failed: + this.OnFailed(response); + break; + case AuthenticationStatus.SetupRequired: + case AuthenticationStatus.ExtensionsOnly: + default: + // The NotApplicable (extension-only assertion) is NOT one that we support + // in this control because that scenario is primarily interesting to RPs + // that are asking a specific OP, and it is not user-initiated as this textbox + // is designed for. + throw new InvalidOperationException(MessagingStrings.UnexpectedMessageReceivedOfMany); } } @@ -503,10 +675,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> /// <param name="response">The response.</param> protected virtual void OnLoggedIn(IAuthenticationResponse response) { - Contract.Requires(response != null); - Contract.Requires(response.Status == AuthenticationStatus.Authenticated); - ErrorUtilities.VerifyArgumentNotNull(response, "response"); - ErrorUtilities.VerifyInternal(response.Status == AuthenticationStatus.Authenticated, "Firing OnLoggedIn event without an authenticated response."); + Contract.Requires<ArgumentNullException>(response != null); + Contract.Requires<ArgumentException>(response.Status == AuthenticationStatus.Authenticated); var loggedIn = this.LoggedIn; OpenIdEventArgs args = new OpenIdEventArgs(response); @@ -515,7 +685,18 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } if (!args.Cancel) { - FormsAuthentication.RedirectFromLoginPage(response.ClaimedIdentifier, this.UsePersistentCookie); + if (this.UsePersistentCookie == LogOnPersistence.SessionAndPersistentIdentifier) { + Page.Response.SetCookie(CreateIdentifierPersistingCookie(response)); + } + + switch (this.LogOnMode) { + case LogOnSiteNotification.FormsAuthentication: + FormsAuthentication.RedirectFromLoginPage(response.ClaimedIdentifier, this.UsePersistentCookie == LogOnPersistence.PersistentAuthentication); + break; + case LogOnSiteNotification.None: + default: + break; + } } } @@ -527,8 +708,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// Returns whether the login should proceed. False if some event handler canceled the request. /// </returns> protected virtual bool OnLoggingIn(IAuthenticationRequest request) { - Contract.Requires(request != null); - ErrorUtilities.VerifyArgumentNotNull(request, "request"); + Contract.Requires<ArgumentNullException>(request != null); EventHandler<OpenIdEventArgs> loggingIn = this.LoggingIn; @@ -545,10 +725,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> /// <param name="response">The response.</param> protected virtual void OnCanceled(IAuthenticationResponse response) { - Contract.Requires(response != null); - Contract.Requires(response.Status == AuthenticationStatus.Canceled); - ErrorUtilities.VerifyArgumentNotNull(response, "response"); - ErrorUtilities.VerifyInternal(response.Status == AuthenticationStatus.Canceled, "Firing Canceled event for the wrong response type."); + Contract.Requires<ArgumentNullException>(response != null); + Contract.Requires<ArgumentException>(response.Status == AuthenticationStatus.Canceled); var canceled = this.Canceled; if (canceled != null) { @@ -561,10 +739,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> /// <param name="response">The response.</param> protected virtual void OnFailed(IAuthenticationResponse response) { - Contract.Requires(response != null); - Contract.Requires(response.Status == AuthenticationStatus.Failed); - ErrorUtilities.VerifyArgumentNotNull(response, "response"); - ErrorUtilities.VerifyInternal(response.Status == AuthenticationStatus.Failed, "Firing Failed event for the wrong response type."); + Contract.Requires<ArgumentNullException>(response != null); + Contract.Requires<ArgumentException>(response.Status == AuthenticationStatus.Failed); var failed = this.Failed; if (failed != null) { @@ -577,8 +753,20 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> /// <returns>The instantiated relying party.</returns> protected virtual OpenIdRelyingParty CreateRelyingParty() { + return this.CreateRelyingParty(true); + } + + /// <summary> + /// Creates the relying party instance used to generate authentication requests. + /// </summary> + /// <param name="verifySignature"> + /// A value indicating whether message protections should be applied to the processed messages. + /// Use <c>false</c> to postpone verification to a later time without invalidating nonces. + /// </param> + /// <returns>The instantiated relying party.</returns> + protected virtual OpenIdRelyingParty CreateRelyingParty(bool verifySignature) { IRelyingPartyApplicationStore store = this.Stateless ? null : DotNetOpenAuthSection.Configuration.OpenId.RelyingParty.ApplicationStore.CreateInstance(OpenIdRelyingParty.HttpApplicationStore); - var rp = new OpenIdRelyingParty(store); + var rp = verifySignature ? new OpenIdRelyingParty(store) : OpenIdRelyingParty.CreateNonVerifying(); // Only set RequireSsl to true, as we don't want to override // a .config setting of true with false. @@ -597,8 +785,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <c>true</c> if a popup should be used; <c>false</c> otherwise. /// </returns> protected virtual bool IsPopupAppropriate(IAuthenticationRequest request) { - Contract.Requires(request != null); - ErrorUtilities.VerifyArgumentNotNull(request, "request"); + Contract.Requires<ArgumentNullException>(request != null); switch (this.Popup) { case PopupBehavior.Never: @@ -620,10 +807,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <param name="request">The outgoing authentication request.</param> /// <param name="windowStatus">The text to try to display in the status bar on mouse hover.</param> protected void RenderOpenIdMessageTransmissionAsAnchorAttributes(HtmlTextWriter writer, IAuthenticationRequest request, string windowStatus) { - Contract.Requires(writer != null); - Contract.Requires(request != null); - ErrorUtilities.VerifyArgumentNotNull(writer, "writer"); - ErrorUtilities.VerifyArgumentNotNull(request, "request"); + Contract.Requires<ArgumentNullException>(writer != null); + Contract.Requires<ArgumentNullException>(request != null); // We render a standard HREF attribute for non-javascript browsers. writer.AddAttribute(HtmlTextWriterAttribute.Href, request.RedirectingResponse.GetDirectUriRequest(this.RelyingParty.Channel).AbsoluteUri); @@ -639,13 +824,146 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> + /// Wires the popup window to close itself and pass the authentication result to the parent window. + /// </summary> + protected virtual void ScriptClosingPopupOrIFrame() { + StringBuilder startupScript = new StringBuilder(); + startupScript.AppendLine("window.opener.dnoa_internal.processAuthorizationResult(document.URL);"); + startupScript.AppendLine("window.close();"); + + this.Page.ClientScript.RegisterStartupScript(typeof(OpenIdRelyingPartyControlBase), "loginPopupClose", startupScript.ToString(), true); + + // TODO: alternately we should probably take over rendering this page here to avoid + // a lot of unnecessary work on the server and possible momentary display of the + // page in the popup window. + } + + /// <summary> + /// Creates the identifier-persisting cookie, either for saving or deleting. + /// </summary> + /// <param name="response">The positive authentication response; or <c>null</c> to clear the cookie.</param> + /// <returns>An persistent cookie.</returns> + private static HttpCookie CreateIdentifierPersistingCookie(IAuthenticationResponse response) { + HttpCookie cookie = new HttpCookie(PersistentIdentifierCookieName); + bool clearingCookie = false; + + // We'll try to store whatever it was the user originally typed in, but fallback + // to the final claimed_id. + if (response != null && response.Status == AuthenticationStatus.Authenticated) { + var positiveResponse = (PositiveAuthenticationResponse)response; + + // We must escape the value because XRIs start with =, and any leading '=' gets dropped (by ASP.NET?) + cookie.Value = Uri.EscapeDataString(positiveResponse.Endpoint.UserSuppliedIdentifier ?? response.ClaimedIdentifier); + } else { + clearingCookie = true; + cookie.Value = string.Empty; + if (HttpContext.Current.Request.Browser["supportsEmptyStringInCookieValue"] == "false") { + cookie.Value = "NoCookie"; + } + } + + if (clearingCookie) { + // mark the cookie has having already expired to cause the user agent to delete + // the old persisted cookie. + cookie.Expires = DateTime.Now.Subtract(TimeSpan.FromDays(1)); + } else { + // Make the cookie persistent by setting an expiration date + cookie.Expires = DateTime.Now + PersistentIdentifierTimeToLiveDefault; + } + + return cookie; + } + + /// <summary> + /// Creates the authentication requests for a given user-supplied Identifier. + /// </summary> + /// <param name="identifier">The identifier to create a request for.</param> + /// <returns> + /// A sequence of authentication requests, any one of which may be + /// used to determine the user's control of the <see cref="IAuthenticationRequest.ClaimedIdentifier"/>. + /// </returns> + private IEnumerable<IAuthenticationRequest> CreateRequestsCore(Identifier identifier) { + ErrorUtilities.VerifyArgumentNotNull(identifier, "identifier"); // NO CODE CONTRACTS! (yield return used here) + IEnumerable<IAuthenticationRequest> requests; + + // Approximate the returnTo (either based on the customize property or the page URL) + // so we can use it to help with Realm resolution. + Uri returnToApproximation = this.ReturnToUrl != null ? new Uri(this.RelyingParty.Channel.GetRequestFromContext().UrlBeforeRewriting, this.ReturnToUrl) : this.Page.Request.Url; + + // Resolve the trust root, and swap out the scheme and port if necessary to match the + // return_to URL, since this match is required by OpenId, and the consumer app + // may be using HTTP at some times and HTTPS at others. + UriBuilder realm = OpenIdUtilities.GetResolvedRealm(this.Page, this.RealmUrl, this.RelyingParty.Channel.GetRequestFromContext()); + realm.Scheme = returnToApproximation.Scheme; + realm.Port = returnToApproximation.Port; + + // Initiate openid request + // We use TryParse here to avoid throwing an exception which + // might slip through our validator control if it is disabled. + Realm typedRealm = new Realm(realm); + if (string.IsNullOrEmpty(this.ReturnToUrl)) { + requests = this.RelyingParty.CreateRequests(identifier, typedRealm); + } else { + // Since the user actually gave us a return_to value, + // the "approximation" is exactly what we want. + requests = this.RelyingParty.CreateRequests(identifier, typedRealm, returnToApproximation); + } + + // Some OPs may be listed multiple times (one with HTTPS and the other with HTTP, for example). + // Since we're gathering OPs to try one after the other, just take the first choice of each OP + // and don't try it multiple times. + requests = requests.Distinct(DuplicateRequestedHostsComparer.Instance); + + // Configure each generated request. + foreach (var req in requests) { + if (this.IsPopupAppropriate(req)) { + // Inform ourselves in return_to that we're in a popup. + req.SetUntrustedCallbackArgument(UIPopupCallbackKey, "1"); + + if (req.Provider.IsExtensionSupported<UIRequest>()) { + // Inform the OP that we'll be using a popup window consistent with the UI extension. + req.AddExtension(new UIRequest()); + + // Provide a hint for the client javascript about whether the OP supports the UI extension. + // This is so the window can be made the correct size for the extension. + // If the OP doesn't advertise support for the extension, the javascript will use + // a bigger popup window. + req.SetUntrustedCallbackArgument(PopupUISupportedJSHint, "1"); + } + } + + // Add the extensions injected into the control. + foreach (var extension in this.Extensions) { + req.AddExtension(extension); + } + + // Add state that needs to survive across the redirect. + if (!this.Stateless) { + req.SetUntrustedCallbackArgument(UsePersistentCookieCallbackKey, this.UsePersistentCookie.ToString()); + req.SetUntrustedCallbackArgument(ReturnToReceivingControlId, this.ClientID); + } + + // Apply the control's association preference to this auth request, but only if + // it is less demanding (greater ordinal value) than the existing one. + // That way, we protect against retrying an association that was already attempted. + var authReq = ((AuthenticationRequest)req); + if (authReq.AssociationPreference < this.AssociationPreference) { + authReq.AssociationPreference = this.AssociationPreference; + } + + if (this.OnLoggingIn(req)) { + yield return req; + } + } + } + + /// <summary> /// Gets the javascript to executee to redirect or POST an OpenID message to a remote party. /// </summary> /// <param name="request">The authentication request to send.</param> /// <returns>The javascript that should execute.</returns> private string CreateGetOrPostAHrefValue(IAuthenticationRequest request) { - Contract.Requires(request != null); - ErrorUtilities.VerifyArgumentNotNull(request, "request"); + Contract.Requires<ArgumentNullException>(request != null); Uri directUri = request.RedirectingResponse.GetDirectUriRequest(this.RelyingParty.Channel); return "window.dnoa_internal.GetOrPost(" + MessagingUtilities.GetSafeJavascriptValue(directUri.AbsoluteUri) + ");"; @@ -656,37 +974,40 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> /// <param name="request">The request.</param> private void ScriptPopupWindow(IAuthenticationRequest request) { - Contract.Requires(request != null); - Contract.Requires(this.RelyingParty != null); + Contract.Requires<ArgumentNullException>(request != null); + Contract.Requires<InvalidOperationException>(this.RelyingParty != null); StringBuilder startupScript = new StringBuilder(); // Add a callback function that the popup window can call on this, the // parent window, to pass back the authentication result. - startupScript.AppendLine("window.dnoa_internal = new Object();"); + startupScript.AppendLine("window.dnoa_internal = {};"); startupScript.AppendLine("window.dnoa_internal.processAuthorizationResult = function(uri) { window.location = uri; };"); startupScript.AppendLine("window.dnoa_internal.popupWindow = function() {"); - startupScript.AppendFormat( - @"\tvar openidPopup = {0}", - UIUtilities.GetWindowPopupScript(this.RelyingParty, request, "openidPopup")); + startupScript.AppendFormat( + @"\tvar openidPopup = {0}", + UIUtilities.GetWindowPopupScript(this.RelyingParty, request, "openidPopup")); startupScript.AppendLine("};"); this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "loginPopup", startupScript.ToString(), true); } /// <summary> - /// Wires the popup window to close itself and pass the authentication result to the parent window. + /// Tries to preset the <see cref="Identifier"/> property based on a persistent + /// cookie on the browser. /// </summary> - private void ScriptClosingPopup() { - StringBuilder startupScript = new StringBuilder(); - startupScript.AppendLine("window.opener.dnoa_internal.processAuthorizationResult(document.URL + '&" + UIPopupCallbackParentKey + "=1');"); - startupScript.AppendLine("window.close();"); - - this.Page.ClientScript.RegisterStartupScript(typeof(OpenIdRelyingPartyControlBase), "loginPopupClose", startupScript.ToString(), true); + /// <returns> + /// A value indicating whether the <see cref="Identifier"/> property was + /// successfully preset to some non-empty value. + /// </returns> + private bool TryPresetIdentifierWithCookie() { + HttpCookie cookie = this.Page.Request.Cookies[PersistentIdentifierCookieName]; + if (cookie != null) { + this.Identifier = Uri.UnescapeDataString(cookie.Value); + return true; + } - // TODO: alternately we should probably take over rendering this page here to avoid - // a lot of unnecessary work on the server and possible momentary display of the - // page in the popup window. + return false; } /// <summary> diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.js b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.js index 3a17b7b..58b283d 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.js +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.js @@ -1,6 +1,8 @@ //----------------------------------------------------------------------- // <copyright file="OpenIdRelyingPartyControlBase.js" company="Andrew Arnott"> // Copyright (c) Andrew Arnott. All rights reserved. +// This file may be used and redistributed under the terms of the +// Microsoft Public License (Ms-PL) http://opensource.org/licenses/ms-pl.html // </copyright> //----------------------------------------------------------------------- @@ -8,13 +10,14 @@ //window.openid_visible_iframe = true; // causes the hidden iframe to show up //window.openid_trace = true; // causes lots of messages -trace = function(msg) { +trace = function(msg, color) { if (window.openid_trace) { if (!window.openid_tracediv) { window.openid_tracediv = document.createElement("ol"); document.body.appendChild(window.openid_tracediv); } var el = document.createElement("li"); + if (color) { el.style.color = color; } el.appendChild(document.createTextNode(msg)); window.openid_tracediv.appendChild(el); //alert(msg); @@ -22,13 +25,8 @@ trace = function(msg) { }; if (window.dnoa_internal === undefined) { - window.dnoa_internal = new Object(); -}; - -// The possible authentication results -window.dnoa_internal.authSuccess = new Object(); -window.dnoa_internal.authRefused = new Object(); -window.dnoa_internal.timedOut = new Object(); + window.dnoa_internal = {}; +} /// <summary>Instantiates an object that provides string manipulation services for URIs.</summary> window.dnoa_internal.Uri = function(url) { @@ -41,20 +39,20 @@ window.dnoa_internal.Uri = function(url) { this.getAuthority = function() { var authority = this.getScheme() + "://" + this.getHost(); return authority; - } + }; this.getHost = function() { var hostStartIdx = this.originalUri.indexOf("://") + 3; var hostEndIndex = this.originalUri.indexOf("/", hostStartIdx); - if (hostEndIndex < 0) hostEndIndex = this.originalUri.length; + if (hostEndIndex < 0) { hostEndIndex = this.originalUri.length; } var host = this.originalUri.substr(hostStartIdx, hostEndIndex - hostStartIdx); return host; - } + }; this.getScheme = function() { var schemeStartIdx = this.indexOf("://"); return this.originalUri.substr(this.originalUri, schemeStartIdx); - } + }; this.trimFragment = function() { var hashmark = this.originalUri.indexOf('#'); @@ -76,13 +74,13 @@ window.dnoa_internal.Uri = function(url) { function KeyValuePair(key, value) { this.key = key; this.value = value; - }; + } - this.pairs = new Array(); + this.pairs = []; var queryBeginsAt = this.originalUri.indexOf('?'); if (queryBeginsAt >= 0) { - this.queryString = url.substr(queryBeginsAt + 1); + this.queryString = this.originalUri.substr(queryBeginsAt + 1); var queryStringPairs = this.queryString.split('&'); for (var i = 0; i < queryStringPairs.length; i++) { @@ -91,7 +89,7 @@ window.dnoa_internal.Uri = function(url) { right = (equalsAt >= 0) ? queryStringPairs[i].substring(equalsAt + 1) : queryStringPairs[i]; this.pairs.push(new KeyValuePair(unescape(left), unescape(right))); } - }; + } this.getQueryArgValue = function(key) { for (var i = 0; i < this.pairs.length; i++) { @@ -103,7 +101,7 @@ window.dnoa_internal.Uri = function(url) { this.getPairs = function() { return this.pairs; - } + }; this.containsQueryArg = function(key) { return this.getQueryArgValue(key); @@ -141,7 +139,7 @@ window.dnoa_internal.createHiddenIFrame = function() { } return iframe; -} +}; /// <summary>Redirects the current window/frame to the given URI, /// either using a GET or a POST as required by the length of the URL.</summary> diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.cs new file mode 100644 index 0000000..ed83412 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.cs @@ -0,0 +1,370 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdSelector.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +[assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdSelector.EmbeddedScriptResourceName, "text/javascript")] +[assembly: System.Web.UI.WebResource(DotNetOpenAuth.OpenId.RelyingParty.OpenIdSelector.EmbeddedStylesheetResourceName, "text/css")] + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.ComponentModel; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.IdentityModel.Claims; + using System.Linq; + using System.Text; + using System.Web; + using System.Web.UI; + using System.Web.UI.HtmlControls; + using System.Web.UI.WebControls; + using DotNetOpenAuth.ComponentModel; + using DotNetOpenAuth.InfoCard; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// An ASP.NET control that provides a user-friendly way of logging into a web site using OpenID. + /// </summary> + [ToolboxData("<{0}:OpenIdSelector runat=\"server\"></{0}:OpenIdSelector>")] + [ParseChildren(true), PersistChildren(false)] + public class OpenIdSelector : OpenIdRelyingPartyAjaxControlBase { + /// <summary> + /// The name of the manifest stream containing the OpenIdButtonPanel.js file. + /// </summary> + internal const string EmbeddedScriptResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.OpenIdSelector.js"; + + /// <summary> + /// The name of the manifest stream containing the OpenIdButtonPanel.css file. + /// </summary> + internal const string EmbeddedStylesheetResourceName = Util.DefaultNamespace + ".OpenId.RelyingParty.OpenIdSelector.css"; + + /// <summary> + /// The substring to append to the end of the id or name of this control to form the + /// unique name of the hidden field that will carry the positive assertion on postback. + /// </summary> + private const string AuthDataFormKeySuffix = "_openidAuthData"; + + #region ViewState keys + + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="Buttons"/> property. + /// </summary> + private const string ButtonsViewStateKey = "Buttons"; + + /// <summary> + /// The viewstate key to use for storing the value of the <see cref="AuthenticatedAsToolTip"/> property. + /// </summary> + private const string AuthenticatedAsToolTipViewStateKey = "AuthenticatedAsToolTip"; + + #endregion + + #region Property defaults + + /// <summary> + /// The default value for the <see cref="AuthenticatedAsToolTip"/> property. + /// </summary> + private const string AuthenticatedAsToolTipDefault = "We recognize you!"; + + #endregion + + /// <summary> + /// The OpenIdAjaxTextBox that remains hidden until the user clicks the OpenID button. + /// </summary> + private OpenIdAjaxTextBox textBox; + + /// <summary> + /// The hidden field that will transmit the positive assertion to the RP. + /// </summary> + private HiddenField positiveAssertionField; + + /// <summary> + /// Initializes a new instance of the <see cref="OpenIdSelector"/> class. + /// </summary> + public OpenIdSelector() { + } + + /// <summary> + /// Occurs when an InfoCard has been submitted and decoded. + /// </summary> + public event EventHandler<ReceivedTokenEventArgs> ReceivedToken; + + /// <summary> + /// Occurs when [token processing error]. + /// </summary> + public event EventHandler<TokenProcessingErrorEventArgs> TokenProcessingError; + + /// <summary> + /// Gets or sets the tool tip text that appears on the green checkmark when authentication succeeds. + /// </summary> + [Bindable(true), DefaultValue(AuthenticatedAsToolTipDefault), Localizable(true), Category(AppearanceCategory)] + [Description("The tool tip text that appears on the green checkmark when authentication succeeds.")] + public string AuthenticatedAsToolTip { + get { return (string)(this.ViewState[AuthenticatedAsToolTipViewStateKey] ?? AuthenticatedAsToolTipDefault); } + set { this.ViewState[AuthenticatedAsToolTipViewStateKey] = value ?? string.Empty; } + } + + /// <summary> + /// Gets or sets a value indicating whether the Yahoo! User Interface Library (YUI) + /// will be downloaded in order to provide a login split button. + /// </summary> + /// <value> + /// <c>true</c> to use a split button; otherwise, <c>false</c> to use a standard HTML button + /// or a split button by downloading the YUI library yourself on the hosting web page. + /// </value> + /// <remarks> + /// The split button brings in about 180KB of YUI javascript dependencies. + /// </remarks> + [Bindable(true), DefaultValue(OpenIdAjaxTextBox.DownloadYahooUILibraryDefault), Category(BehaviorCategory)] + [Description("Whether a split button will be used for the \"log in\" when the user provides an identifier that delegates to more than one Provider.")] + public bool DownloadYahooUILibrary { + get { + this.EnsureChildControls(); + return this.textBox.DownloadYahooUILibrary; + } + + set { + this.EnsureChildControls(); + this.textBox.DownloadYahooUILibrary = value; + } + } + + /// <summary> + /// Gets the collection of buttons this selector should render to the browser. + /// </summary> + [PersistenceMode(PersistenceMode.InnerProperty)] + public Collection<SelectorButton> Buttons { + get { + if (this.ViewState[ButtonsViewStateKey] == null) { + var providers = new Collection<SelectorButton>(); + this.ViewState[ButtonsViewStateKey] = providers; + return providers; + } else { + return (Collection<SelectorButton>)this.ViewState[ButtonsViewStateKey]; + } + } + } + + /// <summary> + /// Gets a <see cref="T:System.Web.UI.ControlCollection"/> object that represents the child controls for a specified server control in the UI hierarchy. + /// </summary> + /// <returns> + /// The collection of child controls for the specified server control. + /// </returns> + public override ControlCollection Controls { + get { + this.EnsureChildControls(); + return base.Controls; + } + } + + /// <summary> + /// Gets the name of the open id auth data form key (for the value as stored at the user agent as a FORM field). + /// </summary> + /// <value> + /// Usually a concatenation of the control's name and <c>"_openidAuthData"</c>. + /// </value> + protected override string OpenIdAuthDataFormKey { + get { return this.UniqueID + AuthDataFormKeySuffix; } + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected override void Dispose(bool disposing) { + if (disposing) { + foreach (var button in this.Buttons.OfType<IDisposable>()) { + button.Dispose(); + } + } + + base.Dispose(disposing); + } + + /// <summary> + /// Called by the ASP.NET page framework to notify server controls that use composition-based implementation to create any child controls they contain in preparation for posting back or rendering. + /// </summary> + protected override void CreateChildControls() { + base.CreateChildControls(); + this.EnsureID(); + + var selectorButton = this.Buttons.OfType<SelectorInfoCardButton>().FirstOrDefault(); + if (selectorButton != null) { + var selector = selectorButton.InfoCardSelector; + selector.ClaimsRequested.Add(new ClaimType { Name = ClaimTypes.PPID }); + selector.ImageSize = InfoCardImageSize.Size60x42; + selector.ReceivedToken += this.InfoCardSelector_ReceivedToken; + selector.TokenProcessingError += this.InfoCardSelector_TokenProcessingError; + this.Controls.Add(selector); + } + + this.textBox = new OpenIdAjaxTextBox(); + this.textBox.ID = "openid_identifier"; + this.textBox.HookFormSubmit = false; + this.textBox.ShowLogOnPostBackButton = true; + this.Controls.Add(this.textBox); + + this.positiveAssertionField = new HiddenField(); + this.positiveAssertionField.ID = this.ID + AuthDataFormKeySuffix; + this.Controls.Add(this.positiveAssertionField); + } + + /// <summary> + /// Raises the <see cref="E:System.Web.UI.Control.Init"/> event. + /// </summary> + /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param> + protected override void OnInit(EventArgs e) { + base.OnInit(e); + + // We force child control creation here so that they can get postback events. + EnsureChildControls(); + } + + /// <summary> + /// Raises the <see cref="E:System.Web.UI.Control.PreRender"/> event. + /// </summary> + /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param> + protected override void OnPreRender(EventArgs e) { + base.OnPreRender(e); + + this.EnsureValidButtons(); + + var css = new HtmlLink(); + css.Href = this.Page.ClientScript.GetWebResourceUrl(this.GetType(), EmbeddedStylesheetResourceName); + css.Attributes["rel"] = "stylesheet"; + css.Attributes["type"] = "text/css"; + ErrorUtilities.VerifyHost(this.Page.Header != null, OpenIdStrings.HeadTagMustIncludeRunatServer); + this.Page.Header.Controls.AddAt(0, css); // insert at top so host page can override + + // Import the .js file where most of the code is. + this.Page.ClientScript.RegisterClientScriptResource(typeof(OpenIdSelector), EmbeddedScriptResourceName); + + // Provide javascript with a way to post the login assertion. + const string PostLoginAssertionMethodName = "postLoginAssertion"; + const string PositiveAssertionParameterName = "positiveAssertion"; + const string ScriptFormat = @"window.{2} = function({0}) {{ + $('#{3}')[0].setAttribute('value', {0}); + {1}; +}};"; + string script = string.Format( + CultureInfo.InvariantCulture, + ScriptFormat, + PositiveAssertionParameterName, + this.Page.ClientScript.GetPostBackEventReference(this, null, false), + PostLoginAssertionMethodName, + this.positiveAssertionField.ClientID); + this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "Postback", script, true); + + this.PreloadDiscovery(this.Buttons.OfType<SelectorProviderButton>().Select(op => op.OPIdentifier).Where(id => id != null)); + } + + /// <summary> + /// Sends server control content to a provided <see cref="T:System.Web.UI.HtmlTextWriter"/> object, which writes the content to be rendered on the client. + /// </summary> + /// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the server control content.</param> + protected override void Render(HtmlTextWriter writer) { + writer.AddAttribute(HtmlTextWriterAttribute.Class, "OpenIdProviders"); + writer.RenderBeginTag(HtmlTextWriterTag.Ul); + + foreach (var button in this.Buttons) { + button.RenderLeadingAttributes(writer); + + writer.RenderBeginTag(HtmlTextWriterTag.Li); + + writer.AddAttribute(HtmlTextWriterAttribute.Href, "#"); + writer.RenderBeginTag(HtmlTextWriterTag.A); + + writer.RenderBeginTag(HtmlTextWriterTag.Div); + writer.RenderBeginTag(HtmlTextWriterTag.Div); + + button.RenderButtonContent(writer, this); + + writer.RenderEndTag(); // </div> + + writer.AddAttribute(HtmlTextWriterAttribute.Class, "ui-widget-overlay"); + writer.RenderBeginTag(HtmlTextWriterTag.Div); + writer.RenderEndTag(); + + writer.RenderEndTag(); // </div> + writer.RenderEndTag(); // </a> + writer.RenderEndTag(); // </li> + } + + writer.RenderEndTag(); // </ul> + + writer.AddStyleAttribute(HtmlTextWriterStyle.Display, "none"); + writer.AddAttribute(HtmlTextWriterAttribute.Id, "OpenIDForm"); + writer.RenderBeginTag(HtmlTextWriterTag.Div); + + this.textBox.RenderControl(writer); + + writer.RenderEndTag(); // </div> + + this.positiveAssertionField.RenderControl(writer); + } + + /// <summary> + /// Fires the <see cref="ReceivedToken"/> event. + /// </summary> + /// <param name="e">The token, if it was decrypted.</param> + protected virtual void OnReceivedToken(ReceivedTokenEventArgs e) { + Contract.Requires(e != null); + ErrorUtilities.VerifyArgumentNotNull(e, "e"); + + var receivedInfoCard = this.ReceivedToken; + if (receivedInfoCard != null) { + receivedInfoCard(this, e); + } + } + + /// <summary> + /// Raises the <see cref="E:TokenProcessingError"/> event. + /// </summary> + /// <param name="e">The <see cref="DotNetOpenAuth.InfoCard.TokenProcessingErrorEventArgs"/> instance containing the event data.</param> + protected virtual void OnTokenProcessingError(TokenProcessingErrorEventArgs e) { + Contract.Requires(e != null); + ErrorUtilities.VerifyArgumentNotNull(e, "e"); + + var tokenProcessingError = this.TokenProcessingError; + if (tokenProcessingError != null) { + tokenProcessingError(this, e); + } + } + + /// <summary> + /// Handles the ReceivedToken event of the infoCardSelector control. + /// </summary> + /// <param name="sender">The source of the event.</param> + /// <param name="e">The <see cref="DotNetOpenAuth.InfoCard.ReceivedTokenEventArgs"/> instance containing the event data.</param> + private void InfoCardSelector_ReceivedToken(object sender, ReceivedTokenEventArgs e) { + this.Page.Response.SetCookie(new HttpCookie("openid_identifier", "infocard") { + Path = this.Page.Request.ApplicationPath, + }); + this.OnReceivedToken(e); + } + + /// <summary> + /// Handles the TokenProcessingError event of the infoCardSelector control. + /// </summary> + /// <param name="sender">The source of the event.</param> + /// <param name="e">The <see cref="DotNetOpenAuth.InfoCard.TokenProcessingErrorEventArgs"/> instance containing the event data.</param> + private void InfoCardSelector_TokenProcessingError(object sender, TokenProcessingErrorEventArgs e) { + this.OnTokenProcessingError(e); + } + + /// <summary> + /// Ensures the <see cref="Buttons"/> collection has a valid set of buttons. + /// </summary> + private void EnsureValidButtons() { + foreach (var button in this.Buttons) { + button.EnsureValid(); + } + + // Also make sure that there are appropriate numbers of each type of button. + // TODO: code here + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.css b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.css new file mode 100644 index 0000000..e7eafc7 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.css @@ -0,0 +1,109 @@ +ul.OpenIdProviders +{ + padding: 0; + margin: 0px 0px 0px 0px; + list-style-type: none; + text-align: center; +} + +ul.OpenIdProviders li +{ + background-color: White; + display: inline-block; + border: 1px solid #DDD; + margin: 0px 2px 4px 2px; + height: 50px; + width: 100px; + text-align: center; + vertical-align: middle; +} + +ul.OpenIdProviders li div +{ + margin: 0; + padding: 0; + height: 50px; + width: 100px; + text-align: center; + display: table; + position: relative; + overflow: hidden; +} + +ul.OpenIdProviders li div div +{ + margin: 0; + padding: 0; + top: 50%; + display: table-cell; + vertical-align: middle; + position: static; +} + +ul.OpenIdProviders li img +{ +} + +ul.OpenIdProviders li a img +{ + border-width: 0; +} + +ul.OpenIdProviders li img.loginSuccess +{ + position: absolute; + right: 0; + bottom: 0; + display: none; +} + +ul.OpenIdProviders li.loginSuccess img.loginSuccess +{ + display: inline; +} + +ul.OpenIdProviders li a +{ + display: block; /* Chrome needs this for proper position of grayed out overlay */ + position: relative; +} + +ul.OpenIdProviders li div.ui-widget-overlay +{ + display: none; + position: absolute; + top: 0; + bottom: 0; + left: 0; + bottom: 0; +} + +ul.OpenIdProviders li.grayedOut div.ui-widget-overlay +{ + display: block; +} + +ul.OpenIdProviders li.focused +{ + border: solid 2px yellow; +} + +ul.OpenIdProviders li.infocard +{ + display: none; /* default to hiding InfoCard until the user agent determines it's supported */ + cursor: pointer; +} + +#openid_identifier +{ + width: 298px; +} + +#OpenIDForm +{ + text-align: center; +} + +#openid_login_button +{ +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.js b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.js new file mode 100644 index 0000000..6271952 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdSelector.js @@ -0,0 +1,180 @@ +//----------------------------------------------------------------------- +// <copyright file="OpenIdSelector.js" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// This file may be used and redistributed under the terms of the +// Microsoft Public License (Ms-PL) http://opensource.org/licenses/ms-pl.html +// </copyright> +//----------------------------------------------------------------------- + +$(function() { + var hint = $.cookie('openid_identifier') || ''; + + var ajaxbox = document.getElementsByName('openid_identifier')[0]; + if (hint != 'infocard') { + ajaxbox.setValue(hint); + } + + if (document.infoCard && document.infoCard.isSupported()) { + $('ul.OpenIdProviders li.infocard')[0].style.display = 'inline-block'; + } + + if (hint.length > 0) { + var ops = $('ul.OpenIdProviders li'); + ops.addClass('grayedOut'); + var matchFound = false; + ops.each(function(i, li) { + if (li.id == hint || (hint == 'infocard' && $(li).hasClass('infocard'))) { + $(li) + .removeClass('grayedOut') + .addClass('focused'); + matchFound = true; + } + }); + if (!matchFound) { + $('#OpenIDButton') + .removeClass('grayedOut') + .addClass('focused'); + $('#OpenIDForm').show('slow', function() { + ajaxbox.focus(); + }); + } + } + + function showLoginSuccess(userSuppliedIdentifier, success) { + var li = document.getElementById(userSuppliedIdentifier); + if (li) { + if (success) { + $(li).addClass('loginSuccess'); + } else { + $(li).removeClass('loginSuccess'); + } + } + } + + window.dnoa_internal.addAuthSuccess(function(discoveryResult, serviceEndpoint, extensionResponses, state) { + showLoginSuccess(discoveryResult.userSuppliedIdentifier, true); + }); + + window.dnoa_internal.addAuthCleared(function(discoveryResult, serviceEndpoint) { + showLoginSuccess(discoveryResult.userSuppliedIdentifier, false); + + // If this is an OP button, renew the positive assertion. + var li = document.getElementById(discoveryResult.userSuppliedIdentifier); + if (li) { + li.loginBackground(); + } + }); + + ajaxbox.onStateChanged = function(state) { + if (state == "authenticated") { + showLoginSuccess('OpenIDButton', true); + } else { + showLoginSuccess('OpenIDButton', false); // hide checkmark + } + }; + + function checkidSetup(identifier, timerBased) { + var openid = new window.OpenIdIdentifier(identifier); + if (!openid) { throw 'checkidSetup called without an identifier.'; } + openid.login(function(discoveryResult, respondingEndpoint, extensionResponses) { + doLogin(discoveryResult, respondingEndpoint); + }); + } + + // Sends the positive assertion we've collected to the server and actually logs the user into the RP. + function doLogin(discoveryResult, respondingEndpoint) { + var retain = true; //!$('#NotMyComputer')[0].selected; + $.cookie('openid_identifier', retain ? discoveryResult.userSuppliedIdentifier : null, { path: window.aspnetapppath }); + window.postLoginAssertion(respondingEndpoint.response.toString(), window.parent.location.href); + } + + // take over how the text box does postbacks. + ajaxbox.dnoi_internal.postback = doLogin; + + // This FrameManager will be used for background logins for the OP buttons + // and the last used identifier. It is NOT the frame manager used by the + // OpenIdAjaxTextBox, as it has its own. + var backgroundTimeout = 3000; + + $(document).ready(function() { + var ops = $('ul.OpenIdProviders li'); + ops.each(function(i, li) { + if ($(li).hasClass('OPButton')) { + li.authenticationIFrames = new window.dnoa_internal.FrameManager(1/*throttle*/); + var openid = new window.OpenIdIdentifier(li.id); + var authFrames = li.authenticationIFrames; + if ($(li).hasClass('NoAsyncAuth')) { + li.loginBackground = function() { }; + } else { + li.loginBackground = function() { + openid.loginBackground(authFrames, null, null, backgroundTimeout); + }; + } + li.loginBackground(); + } + }); + }); + + $('ul.OpenIdProviders li').click(function() { + var lastFocus = $('.focused')[0]; + if (lastFocus != $(this)[0]) { + $('ul.OpenIdProviders li').removeClass('focused'); + $(this).addClass('focused'); + } + + // Make sure we're not graying out any OPs if the user clicked on a gray button. + var wasGrayedOut = false; + if ($(this).hasClass('grayedOut')) { + wasGrayedOut = true; + $('ul.OpenIdProviders li').removeClass('grayedOut'); + } + + // Be sure to hide the openid_identifier text box unless the OpenID button is selected. + if ($(this)[0] != $('#OpenIDButton')[0] && $('#OpenIDForm').is(':visible')) { + $('#OpenIDForm').hide('slow'); + } + + var relevantUserSuppliedIdentifier = null; + // Don't immediately login if the user clicked OpenID and he can't see the identifier box. + if ($(this)[0].id != 'OpenIDButton') { + relevantUserSuppliedIdentifier = $(this)[0].id; + } else if ($('#OpenIDForm').is(':visible')) { + relevantUserSuppliedIdentifier = ajaxbox.value; + } + + var discoveryResult = window.dnoa_internal.discoveryResults[relevantUserSuppliedIdentifier]; + var respondingEndpoint = discoveryResult ? discoveryResult.findSuccessfulRequest() : null; + + // If the user clicked on a button that has the "we're ready to log you in immediately", + // then log them in! + if (respondingEndpoint) { + doLogin(discoveryResult, respondingEndpoint); + } else if ($(this).hasClass('OPButton')) { + checkidSetup($(this)[0].id); + } else if ($(this).hasClass('infocard') && wasGrayedOut) { + // we need to forward the click onto the InfoCard image so it is handled, since our + // gray overlaying div captured the click event. + $('img', this)[0].click(); + } + }); + $('#OpenIDButton').click(function() { + // Be careful to only try to select the text box once it is available. + if ($('#OpenIDForm').is(':hidden')) { + $('#OpenIDForm').show('slow', function() { + ajaxbox.focus(); + }); + } else { + ajaxbox.focus(); + } + }); + + // Make popup window close on escape (the dialog style is already taken care of) + $(document).keydown(function(e) { + if (e.keyCode == $.ui.keyCode.ESCAPE) { + window.close(); + } else if (e.keyCode == $.ui.keyCode.ENTER) { + // we do NOT want to submit the form on ENTER. + e.preventDefault(); + } + }); +});
\ No newline at end of file diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs index 0723f55..4d635fb 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs @@ -40,7 +40,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </remarks> [DefaultProperty("Text"), ValidationProperty("Text")] [ToolboxData("<{0}:OpenIdTextBox runat=\"server\" />")] - public class OpenIdTextBox : CompositeControl, IEditableTextControl, ITextControl { + public class OpenIdTextBox : OpenIdRelyingPartyControlBase, IEditableTextControl, ITextControl, IPostBackDataHandler { /// <summary> /// The name of the manifest stream containing the /// OpenID logo that is placed inside the text box. @@ -52,38 +52,18 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> protected const short TabIndexDefault = 0; - /// <summary> - /// Default value of <see cref="UsePersistentCookie"/>. - /// </summary> - protected const bool UsePersistentCookieDefault = false; - #region Property category constants /// <summary> - /// The "Appearance" category for properties. - /// </summary> - private const string AppearanceCategory = "Appearance"; - - /// <summary> /// The "Simple Registration" category for properties. /// </summary> private const string ProfileCategory = "Simple Registration"; - /// <summary> - /// The "Behavior" category for properties. - /// </summary> - private const string BehaviorCategory = "Behavior"; - #endregion #region Property viewstate keys /// <summary> - /// The viewstate key to use for the <see cref="Popup"/> property. - /// </summary> - private const string PopupViewStateKey = "Popup"; - - /// <summary> /// The viewstate key to use for the <see cref="RequestEmail"/> property. /// </summary> private const string RequestEmailViewStateKey = "RequestEmail"; @@ -104,11 +84,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { private const string RequestCountryViewStateKey = "RequestCountry"; /// <summary> - /// The viewstate key to use for the <see cref="RequireSsl"/> property. - /// </summary> - private const string RequireSslViewStateKey = "RequireSsl"; - - /// <summary> /// The viewstate key to use for the <see cref="RequestLanguage"/> property. /// </summary> private const string RequestLanguageViewStateKey = "RequestLanguage"; @@ -144,43 +119,53 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { private const string ShowLogoViewStateKey = "ShowLogo"; /// <summary> - /// The viewstate key to use for the <see cref="UsePersistentCookie"/> property. + /// The viewstate key to use for the <see cref="RequestGender"/> property. /// </summary> - private const string UsePersistentCookieViewStateKey = "UsePersistentCookie"; + private const string RequestGenderViewStateKey = "RequestGender"; /// <summary> - /// The viewstate key to use for the <see cref="RequestGender"/> property. + /// The viewstate key to use for the <see cref="RequestBirthDate"/> property. /// </summary> - private const string RequestGenderViewStateKey = "RequestGender"; + private const string RequestBirthDateViewStateKey = "RequestBirthDate"; /// <summary> - /// The viewstate key to use for the <see cref="ReturnToUrl"/> property. + /// The viewstate key to use for the <see cref="CssClass"/> property. /// </summary> - private const string ReturnToUrlViewStateKey = "ReturnToUrl"; + private const string CssClassViewStateKey = "CssClass"; /// <summary> - /// The viewstate key to use for the <see cref="Stateless"/> property. + /// The viewstate key to use for the <see cref="MaxLength"/> property. /// </summary> - private const string StatelessViewStateKey = "Stateless"; + private const string MaxLengthViewStateKey = "MaxLength"; /// <summary> - /// The viewstate key to use for the <see cref="RequestBirthDate"/> property. + /// The viewstate key to use for the <see cref="Columns"/> property. /// </summary> - private const string RequestBirthDateViewStateKey = "RequestBirthDate"; + private const string ColumnsViewStateKey = "Columns"; /// <summary> - /// The viewstate key to use for the <see cref="RealmUrl"/> property. + /// The viewstate key to use for the <see cref="TabIndex"/> property. /// </summary> - private const string RealmUrlViewStateKey = "RealmUrl"; + private const string TabIndexViewStateKey = "TabIndex"; - #endregion + /// <summary> + /// The viewstate key to use for the <see cref="Enabled"/> property. + /// </summary> + private const string EnabledViewStateKey = "Enabled"; - #region Property defaults + /// <summary> + /// The viewstate key to use for the <see cref="Name"/> property. + /// </summary> + private const string NameViewStateKey = "Name"; /// <summary> - /// The default value for the <see cref="Popup"/> property. + /// The viewstate key to use for the <see cref="Text"/> property. /// </summary> - private const PopupBehavior PopupDefault = PopupBehavior.Never; + private const string TextViewStateKey = "Text"; + + #endregion + + #region Property defaults /// <summary> /// The default value for the <see cref="Columns"/> property. @@ -193,19 +178,14 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { private const int MaxLengthDefault = 40; /// <summary> - /// The default value for the <see cref="EnableRequestProfile"/> property. - /// </summary> - private const bool EnableRequestProfileDefault = true; - - /// <summary> - /// The default value for the <see cref="RequireSsl"/> property. + /// The default value for the <see cref="Name"/> property. /// </summary> - private const bool RequireSslDefault = false; + private const string NameDefault = "openid_identifier"; /// <summary> - /// The default value for the <see cref="Stateless"/> property. + /// The default value for the <see cref="EnableRequestProfile"/> property. /// </summary> - private const bool StatelessDefault = false; + private const bool EnableRequestProfileDefault = true; /// <summary> /// The default value for the <see cref="ShowLogo"/> property. @@ -228,21 +208,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { private const string CssClassDefault = "openid"; /// <summary> - /// The default value for the <see cref="ReturnToUrl"/> property. - /// </summary> - private const string ReturnToUrlDefault = ""; - - /// <summary> /// The default value for the <see cref="Text"/> property. /// </summary> private const string TextDefault = ""; /// <summary> - /// The default value for the <see cref="RealmUrl"/> property. - /// </summary> - private const string RealmUrlDefault = "~/"; - - /// <summary> /// The default value for the <see cref="RequestEmail"/> property. /// </summary> private const DemandLevel RequestEmailDefault = DemandLevel.NoRequest; @@ -290,79 +260,22 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { #endregion /// <summary> - /// The callback parameter to use for recognizing when the callback is in a popup window. - /// </summary> - private const string UIPopupCallbackKey = OpenIdUtilities.CustomParameterPrefix + "uipopup"; - - /// <summary> - /// The callback parameter to use for recognizing when the callback is in the parent window. - /// </summary> - private const string UIPopupCallbackParentKey = OpenIdUtilities.CustomParameterPrefix + "uipopupParent"; - - /// <summary> - /// The callback parameter for use with persisting the <see cref="UsePersistentCookie"/> property. - /// </summary> - private const string UsePersistentCookieCallbackKey = "OpenIdTextBox_UsePersistentCookie"; - - /// <summary> - /// The text in the text box before the text box is instantiated. + /// An empty sreg request, used to compare with others to see if they too are empty. /// </summary> - private string text = TextDefault; - - /// <summary> - /// The text box itself. - /// </summary> - private TextBox wrappedTextBox; - - /// <summary> - /// Backing field for the <see cref="RelyingParty"/> property. - /// </summary> - private OpenIdRelyingParty relyingParty; + private static readonly ClaimsRequest EmptyClaimsRequest = new ClaimsRequest(); /// <summary> /// Initializes a new instance of the <see cref="OpenIdTextBox"/> class. /// </summary> public OpenIdTextBox() { - this.InitializeControls(); } - #region Events - - /// <summary> - /// Fired upon completion of a successful login. - /// </summary> - [Description("Fired upon completion of a successful login.")] - public event EventHandler<OpenIdEventArgs> LoggedIn; - - /// <summary> - /// Fired when a login attempt fails. - /// </summary> - [Description("Fired when a login attempt fails.")] - public event EventHandler<OpenIdEventArgs> Failed; - - /// <summary> - /// Fired when an authentication attempt is canceled at the OpenID Provider. - /// </summary> - [Description("Fired when an authentication attempt is canceled at the OpenID Provider.")] - public event EventHandler<OpenIdEventArgs> Canceled; - - /// <summary> - /// Fired when an Immediate authentication attempt fails, and the Provider suggests using non-Immediate mode. - /// </summary> - [Description("Fired when an Immediate authentication attempt fails, and the Provider suggests using non-Immediate mode.")] - public event EventHandler<OpenIdEventArgs> SetupRequired; - - #endregion - #region IEditableTextControl Members /// <summary> /// Occurs when the content of the text changes between posts to the server. /// </summary> - public event EventHandler TextChanged { - add { this.WrappedTextBox.TextChanged += value; } - remove { this.WrappedTextBox.TextChanged -= value; } - } + public event EventHandler TextChanged; #endregion @@ -375,101 +288,32 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { [Description("The content of the text box.")] public string Text { get { - return this.WrappedTextBox != null ? this.WrappedTextBox.Text : this.text; - } - - set { - this.text = value; - if (this.WrappedTextBox != null) { - this.WrappedTextBox.Text = value; - } - } - } - - /// <summary> - /// Gets or sets the OpenID <see cref="Realm"/> of the relying party web site. - /// </summary> - [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Uri", Justification = "Using Uri.ctor for validation.")] - [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "DotNetOpenAuth.OpenId.Realm", Justification = "Using ctor for validation.")] - [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Bindable property must be simple type")] - [Bindable(true), DefaultValue(RealmUrlDefault), Category(BehaviorCategory)] - [Description("The OpenID Realm of the relying party web site.")] - [UrlProperty, Editor("System.Web.UI.Design.UrlEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))] - public string RealmUrl { - get { - return (string)(ViewState[RealmUrlViewStateKey] ?? RealmUrlDefault); - } - - set { - if (Page != null && !DesignMode) { - // Validate new value by trying to construct a Realm object based on it. - new Realm(OpenIdUtilities.GetResolvedRealm(this.Page, value, this.RelyingParty.Channel.GetRequestFromContext())); // throws an exception on failure. - } else { - // We can't fully test it, but it should start with either ~/ or a protocol. - if (Regex.IsMatch(value, @"^https?://")) { - new Uri(value.Replace("*.", string.Empty)); // make sure it's fully-qualified, but ignore wildcards - } else if (value.StartsWith("~/", StringComparison.Ordinal)) { - // this is valid too - } else { - throw new UriFormatException(); - } - } - ViewState[RealmUrlViewStateKey] = value; - } - } - - /// <summary> - /// Gets or sets the OpenID ReturnTo of the relying party web site. - /// </summary> - [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Bindable property must be simple type")] - [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Uri", Justification = "Using Uri.ctor for validation.")] - [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Bindable property must be simple type")] - [Bindable(true), DefaultValue(ReturnToUrlDefault), Category(BehaviorCategory)] - [Description("The OpenID ReturnTo of the relying party web site.")] - [UrlProperty, Editor("System.Web.UI.Design.UrlEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))] - public string ReturnToUrl { - get { - return (string)(this.ViewState[ReturnToUrlViewStateKey] ?? ReturnToUrlDefault); + return this.Identifier != null ? this.Identifier.OriginalString : (this.ViewState[TextViewStateKey] as string ?? string.Empty); } set { - if (this.Page != null && !this.DesignMode) { - // Validate new value by trying to construct a Uri based on it. - new Uri(this.RelyingParty.Channel.GetRequestFromContext().UrlBeforeRewriting, this.Page.ResolveUrl(value)); // throws an exception on failure. + // Try to store it as a validated identifier, + // but failing that at least store the text. + Identifier id; + if (Identifier.TryParse(value, out id)) { + this.Identifier = id; } else { - // We can't fully test it, but it should start with either ~/ or a protocol. - if (Regex.IsMatch(value, @"^https?://")) { - new Uri(value); // make sure it's fully-qualified, but ignore wildcards - } else if (value.StartsWith("~/", StringComparison.Ordinal)) { - // this is valid too - } else { - throw new UriFormatException(); - } + // Be sure to set the viewstate AFTER setting the Identifier, + // since setting the Identifier clears the viewstate in OnIdentifierChanged. + this.Identifier = null; + this.ViewState[TextViewStateKey] = value; } - - this.ViewState[ReturnToUrlViewStateKey] = value; } } /// <summary> - /// Gets or sets a value indicating when to use a popup window to complete the login experience. + /// Gets or sets the form name to use for this input field. /// </summary> - /// <value>The default value is <see cref="PopupBehavior.Never"/>.</value> - [Bindable(true), DefaultValue(PopupDefault), Category(BehaviorCategory)] - [Description("When to use a popup window to complete the login experience.")] - public PopupBehavior Popup { - get { return (PopupBehavior)(ViewState[PopupViewStateKey] ?? PopupDefault); } - set { ViewState[PopupViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets a value indicating whether stateless mode is used. - /// </summary> - [Bindable(true), DefaultValue(StatelessDefault), Category(BehaviorCategory)] - [Description("Controls whether stateless mode is used.")] - public bool Stateless { - get { return (bool)(ViewState[StatelessViewStateKey] ?? StatelessDefault); } - set { ViewState[StatelessViewStateKey] = value; } + [Bindable(true), DefaultValue(NameDefault), Category(BehaviorCategory)] + [Description("The form name of this input field.")] + public string Name { + get { return (string)(this.ViewState[NameViewStateKey] ?? NameDefault); } + set { this.ViewState[NameViewStateKey] = value; } } /// <summary> @@ -477,9 +321,9 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> [Bindable(true), DefaultValue(CssClassDefault), Category(AppearanceCategory)] [Description("The CSS class assigned to the text box.")] - public override string CssClass { - get { return this.WrappedTextBox.CssClass; } - set { this.WrappedTextBox.CssClass = value; } + public string CssClass { + get { return (string)this.ViewState[CssClassViewStateKey]; } + set { this.ViewState[CssClassViewStateKey] = value; } } /// <summary> @@ -503,25 +347,13 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> - /// Gets or sets a value indicating whether to send a persistent cookie upon successful - /// login so the user does not have to log in upon returning to this site. - /// </summary> - [Bindable(true), DefaultValue(UsePersistentCookieDefault), Category(BehaviorCategory)] - [Description("Whether to send a persistent cookie upon successful " + - "login so the user does not have to log in upon returning to this site.")] - public virtual bool UsePersistentCookie { - get { return (bool)(this.ViewState[UsePersistentCookieViewStateKey] ?? UsePersistentCookieDefault); } - set { this.ViewState[UsePersistentCookieViewStateKey] = value; } - } - - /// <summary> /// Gets or sets the width of the text box in characters. /// </summary> [Bindable(true), DefaultValue(ColumnsDefault), Category(AppearanceCategory)] [Description("The width of the text box in characters.")] public int Columns { - get { return this.WrappedTextBox.Columns; } - set { this.WrappedTextBox.Columns = value; } + get { return (int)(this.ViewState[ColumnsViewStateKey] ?? ColumnsDefault); } + set { this.ViewState[ColumnsViewStateKey] = value; } } /// <summary> @@ -530,8 +362,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { [Bindable(true), DefaultValue(MaxLengthDefault), Category(AppearanceCategory)] [Description("The maximum number of characters the browser should allow.")] public int MaxLength { - get { return this.WrappedTextBox.MaxLength; } - set { this.WrappedTextBox.MaxLength = value; } + get { return (int)(this.ViewState[MaxLengthViewStateKey] ?? MaxLengthDefault); } + set { this.ViewState[MaxLengthViewStateKey] = value; } } /// <summary> @@ -546,9 +378,21 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </exception> [Bindable(true), DefaultValue(TabIndexDefault), Category(BehaviorCategory)] [Description("The tab index of the text box control.")] - public override short TabIndex { - get { return this.WrappedTextBox.TabIndex; } - set { this.WrappedTextBox.TabIndex = value; } + public virtual short TabIndex { + get { return (short)(this.ViewState[TabIndexViewStateKey] ?? TabIndexDefault); } + set { this.ViewState[TabIndexViewStateKey] = value; } + } + + /// <summary> + /// Gets or sets a value indicating whether this <see cref="OpenIdTextBox"/> is enabled + /// in the browser for editing and will respond to incoming OpenID messages. + /// </summary> + /// <value><c>true</c> if enabled; otherwise, <c>false</c>.</value> + [Bindable(true), DefaultValue(true), Category(BehaviorCategory)] + [Description("Whether the control is editable in the browser and will respond to OpenID messages.")] + public bool Enabled { + get { return (bool)(this.ViewState[EnabledViewStateKey] ?? true); } + set { this.ViewState[EnabledViewStateKey] = value; } } /// <summary> @@ -673,344 +517,45 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { set { ViewState[EnableRequestProfileViewStateKey] = value; } } - /// <summary> - /// Gets or sets a value indicating whether to enforce on high security mode, - /// which requires the full authentication pipeline to be protected by SSL. - /// </summary> - [Bindable(true), DefaultValue(RequireSslDefault), Category(BehaviorCategory)] - [Description("Turns on high security mode, requiring the full authentication pipeline to be protected by SSL.")] - public bool RequireSsl { - get { return (bool)(ViewState[RequireSslViewStateKey] ?? RequireSslDefault); } - set { ViewState[RequireSslViewStateKey] = value; } - } - - /// <summary> - /// Gets or sets the type of the custom application store to use, or <c>null</c> to use the default. - /// </summary> - /// <remarks> - /// If set, this property must be set in each Page Load event - /// as it is not persisted across postbacks. - /// </remarks> - public IRelyingPartyApplicationStore CustomApplicationStore { get; set; } - #endregion - #region Properties to hide - - /// <summary> - /// Gets or sets the foreground color (typically the color of the text) of the Web server control. - /// </summary> - /// <returns> - /// A <see cref="T:System.Drawing.Color"/> that represents the foreground color of the control. The default is <see cref="F:System.Drawing.Color.Empty"/>. - /// </returns> - [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)] - public override System.Drawing.Color ForeColor { - get { throw new NotSupportedException(); } - set { throw new NotSupportedException(); } - } - - /// <summary> - /// Gets or sets the background color of the Web server control. - /// </summary> - /// <returns> - /// A <see cref="T:System.Drawing.Color"/> that represents the background color of the control. The default is <see cref="F:System.Drawing.Color.Empty"/>, which indicates that this property is not set. - /// </returns> - [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)] - public override System.Drawing.Color BackColor { - get { throw new NotSupportedException(); } - set { throw new NotSupportedException(); } - } - - /// <summary> - /// Gets or sets the border color of the Web control. - /// </summary> - /// <returns> - /// A <see cref="T:System.Drawing.Color"/> that represents the border color of the control. The default is <see cref="F:System.Drawing.Color.Empty"/>, which indicates that this property is not set. - /// </returns> - [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)] - public override System.Drawing.Color BorderColor { - get { throw new NotSupportedException(); } - set { throw new NotSupportedException(); } - } + #region IPostBackDataHandler Members /// <summary> - /// Gets or sets the border width of the Web server control. + /// When implemented by a class, processes postback data for an ASP.NET server control. /// </summary> + /// <param name="postDataKey">The key identifier for the control.</param> + /// <param name="postCollection">The collection of all incoming name values.</param> /// <returns> - /// A <see cref="T:System.Web.UI.WebControls.Unit"/> that represents the border width of a Web server control. The default value is <see cref="F:System.Web.UI.WebControls.Unit.Empty"/>, which indicates that this property is not set. + /// true if the server control's state changes as a result of the postback; otherwise, false. /// </returns> - /// <exception cref="T:System.ArgumentException"> - /// The specified border width is a negative value. - /// </exception> - [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)] - public override Unit BorderWidth { - get { return Unit.Empty; } - set { throw new NotSupportedException(); } + bool IPostBackDataHandler.LoadPostData(string postDataKey, NameValueCollection postCollection) { + return this.LoadPostData(postDataKey, postCollection); } /// <summary> - /// Gets or sets the border style of the Web server control. + /// When implemented by a class, signals the server control to notify the ASP.NET application that the state of the control has changed. /// </summary> - /// <returns> - /// One of the <see cref="T:System.Web.UI.WebControls.BorderStyle"/> enumeration values. The default is NotSet. - /// </returns> - [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)] - public override BorderStyle BorderStyle { - get { return BorderStyle.None; } - set { throw new NotSupportedException(); } + void IPostBackDataHandler.RaisePostDataChangedEvent() { + this.RaisePostDataChangedEvent(); } - /// <summary> - /// Gets the font properties associated with the Web server control. - /// </summary> - /// <returns> - /// A <see cref="T:System.Web.UI.WebControls.FontInfo"/> that represents the font properties of the Web server control. - /// </returns> - [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)] - public override FontInfo Font { - get { return null; } - } - - /// <summary> - /// Gets or sets the height of the Web server control. - /// </summary> - /// <returns> - /// A <see cref="T:System.Web.UI.WebControls.Unit"/> that represents the height of the control. The default is <see cref="F:System.Web.UI.WebControls.Unit.Empty"/>. - /// </returns> - /// <exception cref="T:System.ArgumentException"> - /// The height was set to a negative value. - /// </exception> - [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)] - public override Unit Height { - get { return Unit.Empty; } - set { throw new NotSupportedException(); } - } - - /// <summary> - /// Gets or sets the width of the Web server control. - /// </summary> - /// <returns> - /// A <see cref="T:System.Web.UI.WebControls.Unit"/> that represents the width of the control. The default is <see cref="F:System.Web.UI.WebControls.Unit.Empty"/>. - /// </returns> - /// <exception cref="T:System.ArgumentException"> - /// The width of the Web server control was set to a negative value. - /// </exception> - [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)] - public override Unit Width { - get { return Unit.Empty; } - set { throw new NotSupportedException(); } - } - - /// <summary> - /// Gets or sets the text displayed when the mouse pointer hovers over the Web server control. - /// </summary> - /// <returns> - /// The text displayed when the mouse pointer hovers over the Web server control. The default is <see cref="F:System.String.Empty"/>. - /// </returns> - [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)] - public override string ToolTip { - get { return string.Empty; } - set { throw new NotSupportedException(); } - } + #endregion /// <summary> - /// Gets or sets the skin to apply to the control. + /// Creates the authentication requests for a given user-supplied Identifier. /// </summary> + /// <param name="identifier">The identifier to create a request for.</param> /// <returns> - /// The name of the skin to apply to the control. The default is <see cref="F:System.String.Empty"/>. - /// </returns> - /// <exception cref="T:System.ArgumentException"> - /// The skin specified in the <see cref="P:System.Web.UI.WebControls.WebControl.SkinID"/> property does not exist in the theme. - /// </exception> - [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)] - public override string SkinID { - get { return string.Empty; } - set { throw new NotSupportedException(); } - } - - /// <summary> - /// Gets or sets a value indicating whether themes apply to this control. - /// </summary> - /// <returns>true to use themes; otherwise, false. The default is false. + /// A sequence of authentication requests, any one of which may be + /// used to determine the user's control of the <see cref="IAuthenticationRequest.ClaimedIdentifier"/>. /// </returns> - [Obsolete("This property does not do anything."), Browsable(false), Bindable(false)] - public override bool EnableTheming { - get { return false; } - set { throw new NotSupportedException(); } - } - - #endregion - - /// <summary> - /// Gets or sets the <see cref="OpenIdRelyingParty"/> instance to use. - /// </summary> - /// <value>The default value is an <see cref="OpenIdRelyingParty"/> instance initialized according to the web.config file.</value> - /// <remarks> - /// A performance optimization would be to store off the - /// instance as a static member in your web site and set it - /// to this property in your <see cref="Control.Load">Page.Load</see> - /// event since instantiating these instances can be expensive on - /// heavily trafficked web pages. - /// </remarks> - public OpenIdRelyingParty RelyingParty { - get { - if (this.relyingParty == null) { - this.relyingParty = this.CreateRelyingParty(); - } - return this.relyingParty; - } - - set { - this.relyingParty = value; - } - } - - /// <summary> - /// Gets the <see cref="TextBox"/> control that this control wraps. - /// </summary> - protected TextBox WrappedTextBox { - get { return this.wrappedTextBox; } - } - - /// <summary> - /// Gets or sets a value indicating whether the text box should - /// receive input focus when the web page appears. - /// </summary> - protected bool ShouldBeFocused { get; set; } - - /// <summary> - /// Gets or sets the OpenID authentication request that is about to be sent. - /// </summary> - protected IAuthenticationRequest Request { get; set; } + protected internal override IEnumerable<IAuthenticationRequest> CreateRequests(Identifier identifier) { + ErrorUtilities.VerifyArgumentNotNull(identifier, "identifier"); - /// <summary> - /// Sets the input focus to start on the text box when the page appears - /// in the user's browser. - /// </summary> - public override void Focus() { - if (Controls.Count == 0) { - this.ShouldBeFocused = true; - } else { - this.WrappedTextBox.Focus(); - } - } - - /// <summary> - /// Constructs the authentication request and returns it. - /// </summary> - /// <returns>The instantiated authentication request.</returns> - /// <remarks> - /// <para>This method need not be called before calling the <see cref="LogOn"/> method, - /// but is offered in the event that adding extensions to the request is desired.</para> - /// <para>The Simple Registration extension arguments are added to the request - /// before returning if <see cref="EnableRequestProfile"/> is set to true.</para> - /// </remarks> - [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Uri(Uri, string) accepts second arguments that Uri(Uri, new Uri(string)) does not that we must support.")] - public IAuthenticationRequest CreateRequest() { - ErrorUtilities.VerifyOperation(this.Request == null, OpenIdStrings.CreateRequestAlreadyCalled); - ErrorUtilities.VerifyOperation(!string.IsNullOrEmpty(this.Text), OpenIdStrings.OpenIdTextBoxEmpty); - - try { - // Approximate the returnTo (either based on the customize property or the page URL) - // so we can use it to help with Realm resolution. - var requestContext = this.RelyingParty.Channel.GetRequestFromContext(); - Uri returnToApproximation = this.ReturnToUrl != null ? new Uri(requestContext.UrlBeforeRewriting, this.ReturnToUrl) : this.Page.Request.Url; - - // Resolve the trust root, and swap out the scheme and port if necessary to match the - // return_to URL, since this match is required by OpenId, and the consumer app - // may be using HTTP at some times and HTTPS at others. - UriBuilder realm = OpenIdUtilities.GetResolvedRealm(this.Page, this.RealmUrl, this.RelyingParty.Channel.GetRequestFromContext()); - realm.Scheme = returnToApproximation.Scheme; - realm.Port = returnToApproximation.Port; - - // Initiate openid request - // We use TryParse here to avoid throwing an exception which - // might slip through our validator control if it is disabled. - Identifier userSuppliedIdentifier; - if (Identifier.TryParse(this.Text, out userSuppliedIdentifier)) { - Realm typedRealm = new Realm(realm); - if (string.IsNullOrEmpty(this.ReturnToUrl)) { - this.Request = this.RelyingParty.CreateRequest(userSuppliedIdentifier, typedRealm); - } else { - // Since the user actually gave us a return_to value, - // the "approximation" is exactly what we want. - this.Request = this.RelyingParty.CreateRequest(userSuppliedIdentifier, typedRealm, returnToApproximation); - } - - if (this.EnableRequestProfile) { - this.AddProfileArgs(this.Request); - } - - if (this.IsPopupAppropriate()) { - // Inform the OP that it will appear in a popup window. - this.Request.AddExtension(new UIRequest()); - } - - // Add state that needs to survive across the redirect. - if (!this.Stateless) { - this.Request.AddCallbackArguments(UsePersistentCookieCallbackKey, this.UsePersistentCookie.ToString(CultureInfo.InvariantCulture)); - } - } else { - Logger.OpenId.WarnFormat("An invalid identifier was entered ({0}), but not caught by any validation routine.", this.Text); - this.Request = null; - } - } catch (ProtocolException ex) { - this.OnFailed(new FailedAuthenticationResponse(ex)); - } - - return this.Request; - } - - /// <summary> - /// Immediately redirects to the OpenID Provider to verify the Identifier - /// provided in the text box. - /// </summary> - public void LogOn() { - if (this.Request == null) { - this.CreateRequest(); // sets this.Request - } - - if (this.Request != null) { - if (this.IsPopupAppropriate()) { - this.ScriptPopupWindow(); - } else { - this.Request.RedirectToProvider(); - } - } - } - - /// <summary> - /// Enables a server control to perform final clean up before it is released from memory. - /// </summary> - [SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "Base class doesn't implement virtual Dispose(bool), so we must call its Dispose() method.")] - public sealed override void Dispose() { - this.Dispose(true); - base.Dispose(); - GC.SuppressFinalize(this); - } - - /// <summary> - /// Creates the text box control. - /// </summary> - protected override void CreateChildControls() { - base.CreateChildControls(); - - this.Controls.Add(this.wrappedTextBox); - if (this.ShouldBeFocused) { - this.WrappedTextBox.Focus(); - } - } - - /// <summary> - /// Initializes the text box control. - /// </summary> - protected virtual void InitializeControls() { - this.wrappedTextBox = new TextBox(); - this.wrappedTextBox.ID = "wrappedTextBox"; - this.wrappedTextBox.CssClass = CssClassDefault; - this.wrappedTextBox.Columns = ColumnsDefault; - this.wrappedTextBox.Text = this.text; - this.wrappedTextBox.TabIndex = TabIndexDefault; + // We delegate all our logic to another method, since invoking base. methods + // within an iterator method results in unverifiable code. + return this.CreateRequestsCore(base.CreateRequests(identifier)); } /// <summary> @@ -1018,165 +563,125 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> /// <param name="e">The <see cref="T:System.EventArgs"/> object that contains the event data.</param> protected override void OnLoad(EventArgs e) { - base.OnLoad(e); - - if (!Enabled || Page.IsPostBack) { + if (!this.Enabled) { return; } - // Take an unreliable sneek peek to see if we're in a popup and an OpenID - // assertion is coming in. We shouldn't process assertions in a popup window. - if (this.Page.Request.QueryString[UIPopupCallbackKey] == "1" && this.Page.Request.QueryString[UIPopupCallbackParentKey] == null) { - // We're in a popup window. We need to close it and pass the - // message back to the parent window for processing. - this.ScriptClosingPopup(); - return; // don't do any more processing on it now - } - - var response = this.RelyingParty.GetResponse(); - if (response != null) { - string persistentString = response.GetUntrustedCallbackArgument(UsePersistentCookieCallbackKey); - bool persistentBool; - if (persistentString != null && bool.TryParse(persistentString, out persistentBool)) { - this.UsePersistentCookie = persistentBool; - } - - switch (response.Status) { - case AuthenticationStatus.Canceled: - this.OnCanceled(response); - break; - case AuthenticationStatus.Authenticated: - this.OnLoggedIn(response); - break; - case AuthenticationStatus.SetupRequired: - this.OnSetupRequired(response); - break; - case AuthenticationStatus.Failed: - this.OnFailed(response); - break; - case AuthenticationStatus.ExtensionsOnly: - default: - // The NotApplicable (extension-only assertion) is NOT one that we support - // in this control because that scenario is primarily interesting to RPs - // that are asking a specific OP, and it is not user-initiated as this textbox - // is designed for. - throw new InvalidOperationException(MessagingStrings.UnexpectedMessageReceivedOfMany); - } - } + this.Page.RegisterRequiresPostBack(this); + base.OnLoad(e); } /// <summary> - /// Prepares the text box to be rendered. + /// Called when the <see cref="Identifier"/> property is changed. /// </summary> - /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param> - protected override void OnPreRender(EventArgs e) { - base.OnPreRender(e); + protected override void OnIdentifierChanged() { + this.ViewState.Remove(TextViewStateKey); + base.OnIdentifierChanged(); + } + /// <summary> + /// Sends server control content to a provided <see cref="T:System.Web.UI.HtmlTextWriter"/> object, which writes the content to be rendered on the client. + /// </summary> + /// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the server control content.</param> + protected override void Render(HtmlTextWriter writer) { if (this.ShowLogo) { string logoUrl = Page.ClientScript.GetWebResourceUrl( typeof(OpenIdTextBox), EmbeddedLogoResourceName); - this.WrappedTextBox.Style[HtmlTextWriterStyle.BackgroundImage] = string.Format( - CultureInfo.InvariantCulture, "url({0})", HttpUtility.HtmlEncode(logoUrl)); - this.WrappedTextBox.Style["background-repeat"] = "no-repeat"; - this.WrappedTextBox.Style["background-position"] = "0 50%"; - this.WrappedTextBox.Style[HtmlTextWriterStyle.PaddingLeft] = "18px"; + writer.AddStyleAttribute( + HtmlTextWriterStyle.BackgroundImage, + string.Format(CultureInfo.InvariantCulture, "url({0})", HttpUtility.HtmlEncode(logoUrl))); + writer.AddStyleAttribute("background-repeat", "no-repeat"); + writer.AddStyleAttribute("background-position", "0 50%"); + writer.AddStyleAttribute(HtmlTextWriterStyle.PaddingLeft, "18px"); } if (this.PresetBorder) { - this.WrappedTextBox.Style[HtmlTextWriterStyle.BorderStyle] = "solid"; - this.WrappedTextBox.Style[HtmlTextWriterStyle.BorderWidth] = "1px"; - this.WrappedTextBox.Style[HtmlTextWriterStyle.BorderColor] = "lightgray"; + writer.AddStyleAttribute(HtmlTextWriterStyle.BorderStyle, "solid"); + writer.AddStyleAttribute(HtmlTextWriterStyle.BorderWidth, "1px"); + writer.AddStyleAttribute(HtmlTextWriterStyle.BorderColor, "lightgray"); } - } - /// <summary> - /// Releases unmanaged and - optionally - managed resources - /// </summary> - /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected virtual void Dispose(bool disposing) { - if (disposing) { - if (this.relyingParty != null) { - this.relyingParty.Dispose(); - this.relyingParty = null; - } + if (!string.IsNullOrEmpty(this.CssClass)) { + writer.AddAttribute(HtmlTextWriterAttribute.Class, this.CssClass); } - } - #region Events + writer.AddAttribute(HtmlTextWriterAttribute.Id, this.ClientID); + writer.AddAttribute(HtmlTextWriterAttribute.Name, HttpUtility.HtmlEncode(this.Name)); + writer.AddAttribute(HtmlTextWriterAttribute.Type, "text"); + writer.AddAttribute(HtmlTextWriterAttribute.Size, this.Columns.ToString(CultureInfo.InvariantCulture)); + writer.AddAttribute(HtmlTextWriterAttribute.Value, HttpUtility.HtmlEncode(this.Text)); + writer.AddAttribute(HtmlTextWriterAttribute.Tabindex, this.TabIndex.ToString(CultureInfo.CurrentCulture)); + + writer.RenderBeginTag(HtmlTextWriterTag.Input); + writer.RenderEndTag(); + } /// <summary> - /// Fires the <see cref="LoggedIn"/> event. + /// When implemented by a class, processes postback data for an ASP.NET server control. /// </summary> - /// <param name="response">The response.</param> - protected virtual void OnLoggedIn(IAuthenticationResponse response) { - ErrorUtilities.VerifyArgumentNotNull(response, "response"); - ErrorUtilities.VerifyInternal(response.Status == AuthenticationStatus.Authenticated, "Firing OnLoggedIn event without an authenticated response."); - - var loggedIn = this.LoggedIn; - OpenIdEventArgs args = new OpenIdEventArgs(response); - if (loggedIn != null) { - loggedIn(this, args); + /// <param name="postDataKey">The key identifier for the control.</param> + /// <param name="postCollection">The collection of all incoming name values.</param> + /// <returns> + /// true if the server control's state changes as a result of the postback; otherwise, false. + /// </returns> + protected virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection) { + // If the control was temporarily hidden, it won't be in the Form data, + // and we'll just implicitly keep the last Text setting. + if (postCollection[this.Name] != null) { + this.Text = postCollection[this.Name]; + return true; } - if (!args.Cancel) { - FormsAuthentication.RedirectFromLoginPage(response.ClaimedIdentifier, this.UsePersistentCookie); - } + return false; } /// <summary> - /// Fires the <see cref="Failed"/> event. + /// When implemented by a class, signals the server control to notify the ASP.NET application that the state of the control has changed. /// </summary> - /// <param name="response">The response.</param> - protected virtual void OnFailed(IAuthenticationResponse response) { - ErrorUtilities.VerifyArgumentNotNull(response, "response"); - ErrorUtilities.VerifyInternal(response.Status == AuthenticationStatus.Failed, "Firing Failed event for the wrong response type."); - - var failed = this.Failed; - if (failed != null) { - failed(this, new OpenIdEventArgs(response)); - } + [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "Preserve signature of interface we're implementing.")] + protected virtual void RaisePostDataChangedEvent() { + this.OnTextChanged(); } /// <summary> - /// Fires the <see cref="Canceled"/> event. + /// Called on a postback when the Text property has changed. /// </summary> - /// <param name="response">The response.</param> - protected virtual void OnCanceled(IAuthenticationResponse response) { - ErrorUtilities.VerifyArgumentNotNull(response, "response"); - ErrorUtilities.VerifyInternal(response.Status == AuthenticationStatus.Canceled, "Firing Canceled event for the wrong response type."); - - var canceled = this.Canceled; - if (canceled != null) { - canceled(this, new OpenIdEventArgs(response)); + protected virtual void OnTextChanged() { + EventHandler textChanged = this.TextChanged; + if (textChanged != null) { + textChanged(this, EventArgs.Empty); } } /// <summary> - /// Fires the <see cref="SetupRequired"/> event. + /// Creates the authentication requests for a given user-supplied Identifier. /// </summary> - /// <param name="response">The response.</param> - protected virtual void OnSetupRequired(IAuthenticationResponse response) { - ErrorUtilities.VerifyArgumentNotNull(response, "response"); - ErrorUtilities.VerifyInternal(response.Status == AuthenticationStatus.SetupRequired, "Firing SetupRequired event for the wrong response type."); + /// <param name="requests">The authentication requests to prepare.</param> + /// <returns> + /// A sequence of authentication requests, any one of which may be + /// used to determine the user's control of the <see cref="IAuthenticationRequest.ClaimedIdentifier"/>. + /// </returns> + private IEnumerable<IAuthenticationRequest> CreateRequestsCore(IEnumerable<IAuthenticationRequest> requests) { + Contract.Requires(requests != null); + + foreach (var request in requests) { + if (this.EnableRequestProfile) { + this.AddProfileArgs(request); + } - // Why are we firing Failed when we're OnSetupRequired? Backward compatibility. - var setupRequired = this.SetupRequired; - if (setupRequired != null) { - setupRequired(this, new OpenIdEventArgs(response)); + yield return request; } } - #endregion - /// <summary> /// Adds extensions to a given authentication request to ask the Provider /// for user profile data. /// </summary> /// <param name="request">The authentication request to add the extensions to.</param> private void AddProfileArgs(IAuthenticationRequest request) { - ErrorUtilities.VerifyArgumentNotNull(request, "request"); + Contract.Requires<ArgumentNullException>(request != null); - request.AddExtension(new ClaimsRequest() { + var sreg = new ClaimsRequest() { Nickname = this.RequestNickname, Email = this.RequestEmail, FullName = this.RequestFullName, @@ -1188,84 +693,12 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { TimeZone = this.RequestTimeZone, PolicyUrl = string.IsNullOrEmpty(this.PolicyUrl) ? null : new Uri(this.RelyingParty.Channel.GetRequestFromContext().UrlBeforeRewriting, this.Page.ResolveUrl(this.PolicyUrl)), - }); - } + }; - /// <summary> - /// Creates the relying party instance used to generate authentication requests. - /// </summary> - /// <returns>The instantiated relying party.</returns> - private OpenIdRelyingParty CreateRelyingParty() { - // If we're in stateful mode, first use the explicitly given one on this control if there - // is one. Then try the configuration file specified one. Finally, use the default - // in-memory one that's built into OpenIdRelyingParty. - IRelyingPartyApplicationStore store = this.Stateless ? null : - (this.CustomApplicationStore ?? DotNetOpenAuthSection.Configuration.OpenId.RelyingParty.ApplicationStore.CreateInstance(OpenIdRelyingParty.HttpApplicationStore)); - var rp = new OpenIdRelyingParty(store); - - // Only set RequireSsl to true, as we don't want to override - // a .config setting of true with false. - if (this.RequireSsl) { - rp.SecuritySettings.RequireSsl = true; + // Only actually add the extension request if fields are actually being requested. + if (!sreg.Equals(EmptyClaimsRequest)) { + request.AddExtension(sreg); } - return rp; - } - - /// <summary> - /// Detects whether a popup window should be used to show the Provider's UI - /// and applies the UI extension to the request when appropriate. - /// </summary> - /// <returns><c>true</c> if a popup should be used; <c>false</c> otherwise.</returns> - private bool IsPopupAppropriate() { - Contract.Requires(this.Request != null); - - switch (this.Popup) { - case PopupBehavior.Never: - return false; - case PopupBehavior.Always: - return true; - case PopupBehavior.IfProviderSupported: - return this.Request.Provider.IsExtensionSupported<UIRequest>(); - default: - throw new InternalErrorException(); - } - } - - /// <summary> - /// Wires the return page to immediately display a popup window with the Provider in it. - /// </summary> - private void ScriptPopupWindow() { - Contract.Requires(this.Request != null); - Contract.Requires(this.RelyingParty != null); - - this.Request.AddCallbackArguments(UIPopupCallbackKey, "1"); - - StringBuilder startupScript = new StringBuilder(); - - // Add a callback function that the popup window can call on this, the - // parent window, to pass back the authentication result. - startupScript.AppendLine("window.dnoa_internal = new Object();"); - startupScript.AppendLine("window.dnoa_internal.processAuthorizationResult = function(uri) { window.location = uri; };"); - - // Open the popup window. - startupScript.AppendFormat( - @"var openidPopup = {0}", - UIUtilities.GetWindowPopupScript(this.RelyingParty, this.Request, "openidPopup")); - - this.Page.ClientScript.RegisterStartupScript(this.GetType(), "loginPopup", startupScript.ToString(), true); - } - - /// <summary> - /// Wires the popup window to close itself and pass the authentication result to the parent window. - /// </summary> - private void ScriptClosingPopup() { - StringBuilder startupScript = new StringBuilder(); - startupScript.AppendLine("window.opener.dnoa_internal.processAuthorizationResult(document.URL + '&" + UIPopupCallbackParentKey + "=1');"); - startupScript.AppendLine("window.close();"); - - // We're referencing the OpenIdRelyingPartyControlBase type here to avoid double-registering this script - // if the other control exists on the page. - this.Page.ClientScript.RegisterStartupScript(typeof(OpenIdRelyingPartyControlBase), "loginPopupClose", startupScript.ToString(), true); } } } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs index baf30da..1bc306c 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs @@ -33,8 +33,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> /// <param name="response">The response message.</param> protected internal PositiveAnonymousResponse(IndirectSignedResponse response) { - Contract.Requires(response != null); - ErrorUtilities.VerifyArgumentNotNull(response, "response"); + Contract.Requires<ArgumentNullException>(response != null); this.response = response; if (response.ProviderEndpoint != null && response.Version != null) { @@ -272,7 +271,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// have not been tampered with since the Provider sent the message.</para> /// </remarks> public IOpenIdMessageExtension GetExtension(Type extensionType) { - ErrorUtilities.VerifyArgumentNotNull(extensionType, "extensionType"); return this.response.SignedExtensions.OfType<IOpenIdMessageExtension>().Where(ext => extensionType.IsInstanceOfType(ext)).FirstOrDefault(); } @@ -322,7 +320,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// have not been tampered with since the Provider sent the message.</para> /// </remarks> public IOpenIdMessageExtension GetUntrustedExtension(Type extensionType) { - ErrorUtilities.VerifyArgumentNotNull(extensionType, "extensionType"); return this.response.Extensions.OfType<IOpenIdMessageExtension>().Where(ext => extensionType.IsInstanceOfType(ext)).FirstOrDefault(); } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs index 69a6eaa..e809205 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs @@ -5,6 +5,7 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; using System.Diagnostics; using System.Diagnostics.Contracts; using System.Linq; @@ -19,27 +20,15 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { [DebuggerDisplay("Status: {Status}, ClaimedIdentifier: {ClaimedIdentifier}")] internal class PositiveAuthenticationResponse : PositiveAnonymousResponse { /// <summary> - /// The OpenID service endpoint reconstructed from the assertion message. - /// </summary> - /// <remarks> - /// This information is straight from the Provider, and therefore must not - /// be trusted until verified as matching the discovery information for - /// the claimed identifier to avoid a Provider asserting an Identifier - /// for which it has no authority. - /// </remarks> - private readonly ServiceEndpoint endpoint; - - /// <summary> /// Initializes a new instance of the <see cref="PositiveAuthenticationResponse"/> class. /// </summary> /// <param name="response">The positive assertion response that was just received by the Relying Party.</param> /// <param name="relyingParty">The relying party.</param> internal PositiveAuthenticationResponse(PositiveAssertionResponse response, OpenIdRelyingParty relyingParty) : base(response) { - Contract.Requires(relyingParty != null); - ErrorUtilities.VerifyArgumentNotNull(relyingParty, "relyingParty"); + Contract.Requires<ArgumentNullException>(relyingParty != null); - this.endpoint = ServiceEndpoint.CreateForClaimedIdentifier( + this.Endpoint = ServiceEndpoint.CreateForClaimedIdentifier( this.Response.ClaimedIdentifier, this.Response.GetReturnToArgument(AuthenticationRequest.UserSuppliedIdentifierParameterName), this.Response.LocalIdentifier, @@ -69,7 +58,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </para> /// </remarks> public override Identifier ClaimedIdentifier { - get { return this.endpoint.ClaimedIdentifier; } + get { return this.Endpoint.ClaimedIdentifier; } } /// <summary> @@ -102,7 +91,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </para> /// </remarks> public override string FriendlyIdentifierForDisplay { - get { return this.endpoint.FriendlyIdentifierForDisplay; } + get { return this.Endpoint.FriendlyIdentifierForDisplay; } } /// <summary> @@ -115,6 +104,17 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { #endregion /// <summary> + /// Gets the OpenID service endpoint reconstructed from the assertion message. + /// </summary> + /// <remarks> + /// This information is straight from the Provider, and therefore must not + /// be trusted until verified as matching the discovery information for + /// the claimed identifier to avoid a Provider asserting an Identifier + /// for which it has no authority. + /// </remarks> + internal ServiceEndpoint Endpoint { get; private set; } + + /// <summary> /// Gets the positive assertion response message. /// </summary> protected internal new PositiveAssertionResponse Response { @@ -155,9 +155,9 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { // is merely a hint that must be verified by performing discovery again here. var discoveryResults = claimedId.Discover(relyingParty.WebRequestHandler); ErrorUtilities.VerifyProtocol( - discoveryResults.Contains(this.endpoint), + discoveryResults.Contains(this.Endpoint), OpenIdStrings.IssuedAssertionFailsIdentifierDiscovery, - this.endpoint, + this.Endpoint, discoveryResults.ToStringDeferred(true)); } } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationResponseSnapshot.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponseSnapshot.cs index 973687f..32c8af9 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationResponseSnapshot.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponseSnapshot.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------- -// <copyright file="AuthenticationResponseSnapshot.cs" company="Andrew Arnott"> +// <copyright file="PositiveAuthenticationResponseSnapshot.cs" company="Andrew Arnott"> // Copyright (c) Andrew Arnott. All rights reserved. // </copyright> //----------------------------------------------------------------------- @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.Text; using System.Web; using DotNetOpenAuth.Messaging; @@ -16,7 +17,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// A serializable snapshot of a verified authentication message. /// </summary> [Serializable] - internal class AuthenticationResponseSnapshot : IAuthenticationResponse { + internal class PositiveAuthenticationResponseSnapshot : IAuthenticationResponse { /// <summary> /// The callback arguments that came with the authentication response. /// </summary> @@ -28,11 +29,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { private IDictionary<string, string> untrustedCallbackArguments; /// <summary> - /// Initializes a new instance of the <see cref="AuthenticationResponseSnapshot"/> class. + /// Initializes a new instance of the <see cref="PositiveAuthenticationResponseSnapshot"/> class. /// </summary> /// <param name="copyFrom">The authentication response to copy from.</param> - internal AuthenticationResponseSnapshot(IAuthenticationResponse copyFrom) { - ErrorUtilities.VerifyArgumentNotNull(copyFrom, "copyFrom"); + internal PositiveAuthenticationResponseSnapshot(IAuthenticationResponse copyFrom) { + Contract.Requires<ArgumentNullException>(copyFrom != null); this.ClaimedIdentifier = copyFrom.ClaimedIdentifier; this.FriendlyIdentifierForDisplay = copyFrom.FriendlyIdentifierForDisplay; @@ -266,8 +267,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <para>Note that these values are NOT protected against tampering in transit.</para> /// </remarks> public string GetCallbackArgument(string key) { - ErrorUtilities.VerifyArgumentNotNull(key, "key"); - string value; this.callbackArguments.TryGetValue(key, out value); return value; @@ -288,8 +287,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// used to make a security-sensitive decision. /// </remarks> public string GetUntrustedCallbackArgument(string key) { - ErrorUtilities.VerifyArgumentNotNull(key, "key"); - string value; this.untrustedCallbackArguments.TryGetValue(key, out value); return value; diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/PrivateSecretManager.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/PrivateSecretManager.cs index 0895d77..6d55e39 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/PrivateSecretManager.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/PrivateSecretManager.cs @@ -6,6 +6,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { using System; + using System.Diagnostics.Contracts; using DotNetOpenAuth.Configuration; using DotNetOpenAuth.Messaging; @@ -43,8 +44,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <param name="securitySettings">The security settings.</param> /// <param name="store">The association store.</param> internal PrivateSecretManager(RelyingPartySecuritySettings securitySettings, IAssociationStore<Uri> store) { - ErrorUtilities.VerifyArgumentNotNull(securitySettings, "securitySettings"); - ErrorUtilities.VerifyArgumentNotNull(store, "store"); + Contract.Requires<ArgumentNullException>(securitySettings != null); + Contract.Requires<ArgumentNullException>(store != null); this.securitySettings = securitySettings; this.store = store; @@ -74,8 +75,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <exception cref="ProtocolException">Thrown when an association with the given handle could not be found. /// This most likely happens if the association was near the end of its life and the user took too long to log in.</exception> internal byte[] Sign(byte[] buffer, string handle) { - ErrorUtilities.VerifyArgumentNotNull(buffer, "buffer"); - ErrorUtilities.VerifyNonZeroLength(handle, "handle"); + Contract.Requires<ArgumentNullException>(buffer != null); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(handle)); Association association = this.store.GetAssociation(SecretUri, handle); ErrorUtilities.VerifyProtocol(association != null, OpenIdStrings.PrivateRPSecretNotFound, handle); diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorButton.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorButton.cs new file mode 100644 index 0000000..0be3a5f --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorButton.cs @@ -0,0 +1,46 @@ +//----------------------------------------------------------------------- +// <copyright file="SelectorButton.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Diagnostics.Contracts; + using System.Web.UI; + + /// <summary> + /// A button that would appear in the <see cref="OpenIdSelector"/> control via its <see cref="OpenIdSelector.Buttons"/> collection. + /// </summary> + [ContractClass(typeof(SelectorButtonContract))] + public abstract class SelectorButton { + /// <summary> + /// Initializes a new instance of the <see cref="SelectorButton"/> class. + /// </summary> + protected SelectorButton() { + } + + /// <summary> + /// Ensures that this button has been initialized to a valid state. + /// </summary> + /// <remarks> + /// This is "internal" -- NOT "protected internal" deliberately. It makes it impossible + /// to derive from this class outside the assembly, which suits our purposes since the + /// <see cref="OpenIdSelector"/> control is not designed for an extensible set of button types. + /// </remarks> + internal abstract void EnsureValid(); + + /// <summary> + /// Renders the leading attributes for the LI tag. + /// </summary> + /// <param name="writer">The writer.</param> + protected internal abstract void RenderLeadingAttributes(HtmlTextWriter writer); + + /// <summary> + /// Renders the content of the button. + /// </summary> + /// <param name="writer">The writer.</param> + /// <param name="selector">The containing selector control.</param> + protected internal abstract void RenderButtonContent(HtmlTextWriter writer, OpenIdSelector selector); + } +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorButtonContract.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorButtonContract.cs new file mode 100644 index 0000000..c70218a --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorButtonContract.cs @@ -0,0 +1,46 @@ +//----------------------------------------------------------------------- +// <copyright file="SelectorButtonContract.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Diagnostics.Contracts; + using System.Web.UI; + + /// <summary> + /// The contract class for the <see cref="SelectorButton"/> class. + /// </summary> + [ContractClassFor(typeof(SelectorButton))] + internal abstract class SelectorButtonContract : SelectorButton { + /// <summary> + /// Ensures that this button has been initialized to a valid state. + /// </summary> + /// <remarks> + /// This is "internal" -- NOT "protected internal" deliberately. It makes it impossible + /// to derive from this class outside the assembly, which suits our purposes since the + /// <see cref="OpenIdSelector"/> control is not designed for an extensible set of button types. + /// </remarks> + internal override void EnsureValid() { + } + + /// <summary> + /// Renders the leading attributes for the LI tag. + /// </summary> + /// <param name="writer">The writer.</param> + protected internal override void RenderLeadingAttributes(HtmlTextWriter writer) { + Contract.Requires<ArgumentNullException>(writer != null); + } + + /// <summary> + /// Renders the content of the button. + /// </summary> + /// <param name="writer">The writer.</param> + /// <param name="selector">The containing selector control.</param> + protected internal override void RenderButtonContent(HtmlTextWriter writer, OpenIdSelector selector) { + Contract.Requires<ArgumentNullException>(writer != null); + Contract.Requires<ArgumentNullException>(selector != null); + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorInfoCardButton.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorInfoCardButton.cs new file mode 100644 index 0000000..74e37a6 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorInfoCardButton.cs @@ -0,0 +1,101 @@ +//----------------------------------------------------------------------- +// <copyright file="SelectorInfoCardButton.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Collections.ObjectModel; + using System.ComponentModel; + using System.Diagnostics.Contracts; + using System.Web.UI; + using DotNetOpenAuth.InfoCard; + + /// <summary> + /// A button that appears in the <see cref="OpenIdSelector"/> control that + /// activates the Information Card selector on the browser, if one is available. + /// </summary> + public class SelectorInfoCardButton : SelectorButton, IDisposable { + /// <summary> + /// The backing field for the <see cref="InfoCardSelector"/> property. + /// </summary> + private InfoCardSelector infoCardSelector; + + /// <summary> + /// Initializes a new instance of the <see cref="SelectorInfoCardButton"/> class. + /// </summary> + public SelectorInfoCardButton() { + } + + /// <summary> + /// Gets or sets the InfoCard selector which may be displayed alongside the OP buttons. + /// </summary> + [PersistenceMode(PersistenceMode.InnerProperty)] + public InfoCardSelector InfoCardSelector { + get { + if (this.infoCardSelector == null) { + this.infoCardSelector = new InfoCardSelector(); + } + + return this.infoCardSelector; + } + + set { + Contract.Requires<ArgumentNullException>(value != null); + if (this.infoCardSelector != null) { + Logger.Library.WarnFormat("{0}.InfoCardSelector property is being set multiple times.", GetType().Name); + } + + this.infoCardSelector = value; + } + } + + #region IDisposable Members + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + #endregion + + /// <summary> + /// Ensures that this button has been initialized to a valid state. + /// </summary> + internal override void EnsureValid() { + } + + /// <summary> + /// Renders the leading attributes for the LI tag. + /// </summary> + /// <param name="writer">The writer.</param> + protected internal override void RenderLeadingAttributes(HtmlTextWriter writer) { + writer.AddAttribute(HtmlTextWriterAttribute.Class, "infocard"); + } + + /// <summary> + /// Renders the content of the button. + /// </summary> + /// <param name="writer">The writer.</param> + /// <param name="selector">The containing selector control.</param> + protected internal override void RenderButtonContent(HtmlTextWriter writer, OpenIdSelector selector) { + this.InfoCardSelector.RenderControl(writer); + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool disposing) { + if (disposing) { + if (this.infoCardSelector != null) { + this.infoCardSelector.Dispose(); + } + } + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorOpenIdButton.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorOpenIdButton.cs new file mode 100644 index 0000000..d20bc2b --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorOpenIdButton.cs @@ -0,0 +1,69 @@ +//----------------------------------------------------------------------- +// <copyright file="SelectorOpenIdButton.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System.ComponentModel; + using System.Diagnostics.Contracts; + using System.Drawing.Design; + using System.Web.UI; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A button that appears in the <see cref="OpenIdSelector"/> control that + /// allows the user to type in a user-supplied identifier. + /// </summary> + public class SelectorOpenIdButton : SelectorButton { + /// <summary> + /// Initializes a new instance of the <see cref="SelectorOpenIdButton"/> class. + /// </summary> + public SelectorOpenIdButton() { + } + + /// <summary> + /// Gets or sets the path to the image to display on the button's surface. + /// </summary> + /// <value>The virtual path to the image.</value> + [Editor("System.Web.UI.Design.ImageUrlEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))] + [UrlProperty] + public string Image { get; set; } + + /// <summary> + /// Ensures that this button has been initialized to a valid state. + /// </summary> + internal override void EnsureValid() { + Contract.Ensures(!string.IsNullOrEmpty(this.Image)); + + // Every button must have an image. + ErrorUtilities.VerifyOperation(!string.IsNullOrEmpty(this.Image), OpenIdStrings.PropertyNotSet, "SelectorButton.Image"); + } + + /// <summary> + /// Renders the leading attributes for the LI tag. + /// </summary> + /// <param name="writer">The writer.</param> + protected internal override void RenderLeadingAttributes(HtmlTextWriter writer) { + writer.AddAttribute(HtmlTextWriterAttribute.Id, "OpenIDButton"); + writer.AddAttribute(HtmlTextWriterAttribute.Class, "OpenIDButton"); + } + + /// <summary> + /// Renders the content of the button. + /// </summary> + /// <param name="writer">The writer.</param> + /// <param name="selector">The containing selector control.</param> + protected internal override void RenderButtonContent(HtmlTextWriter writer, OpenIdSelector selector) { + writer.AddAttribute(HtmlTextWriterAttribute.Src, selector.Page.ResolveUrl(this.Image)); + writer.RenderBeginTag(HtmlTextWriterTag.Img); + writer.RenderEndTag(); + + writer.AddAttribute(HtmlTextWriterAttribute.Src, selector.Page.ClientScript.GetWebResourceUrl(typeof(OpenIdAjaxTextBox), OpenIdAjaxTextBox.EmbeddedLoginSuccessResourceName)); + writer.AddAttribute(HtmlTextWriterAttribute.Class, "loginSuccess"); + writer.AddAttribute(HtmlTextWriterAttribute.Title, selector.AuthenticatedAsToolTip); + writer.RenderBeginTag(HtmlTextWriterTag.Img); + writer.RenderEndTag(); + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorProviderButton.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorProviderButton.cs new file mode 100644 index 0000000..d6d1339 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/SelectorProviderButton.cs @@ -0,0 +1,97 @@ +//----------------------------------------------------------------------- +// <copyright file="SelectorProviderButton.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System.ComponentModel; + using System.Diagnostics.Contracts; + using System.Drawing.Design; + using System.Web.UI; + using DotNetOpenAuth.ComponentModel; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A button that appears in the <see cref="OpenIdSelector"/> control that + /// provides one-click access to a popular OpenID Provider. + /// </summary> + public class SelectorProviderButton : SelectorButton { + /// <summary> + /// Initializes a new instance of the <see cref="SelectorProviderButton"/> class. + /// </summary> + public SelectorProviderButton() { + } + + /// <summary> + /// Gets or sets the path to the image to display on the button's surface. + /// </summary> + /// <value>The virtual path to the image.</value> + [Editor("System.Web.UI.Design.ImageUrlEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))] + [UrlProperty] + public string Image { get; set; } + + /// <summary> + /// Gets or sets the OP Identifier represented by the button. + /// </summary> + /// <value> + /// The OP identifier, which may be provided in the easiest "user-supplied identifier" form, + /// but for security should be provided with a leading https:// if possible. + /// For example: "yahoo.com" or "https://me.yahoo.com/". + /// </value> + [TypeConverter(typeof(IdentifierConverter))] + public Identifier OPIdentifier { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this Provider doesn't handle + /// checkid_immediate messages correctly and background authentication + /// should not be attempted. + /// </summary> + public bool SkipBackgroundAuthentication { get; set; } + + /// <summary> + /// Ensures that this button has been initialized to a valid state. + /// </summary> + internal override void EnsureValid() { + Contract.Ensures(!string.IsNullOrEmpty(this.Image)); + Contract.Ensures(this.OPIdentifier != null); + + // Every button must have an image. + ErrorUtilities.VerifyOperation(!string.IsNullOrEmpty(this.Image), OpenIdStrings.PropertyNotSet, "SelectorButton.Image"); + + // Every button must have exactly one purpose. + ErrorUtilities.VerifyOperation(this.OPIdentifier != null, OpenIdStrings.PropertyNotSet, "SelectorButton.OPIdentifier"); + } + + /// <summary> + /// Renders the leading attributes for the LI tag. + /// </summary> + /// <param name="writer">The writer.</param> + protected internal override void RenderLeadingAttributes(HtmlTextWriter writer) { + writer.AddAttribute(HtmlTextWriterAttribute.Id, this.OPIdentifier); + + string style = "OPButton"; + if (this.SkipBackgroundAuthentication) { + style += " NoAsyncAuth"; + } + writer.AddAttribute(HtmlTextWriterAttribute.Class, style); + } + + /// <summary> + /// Renders the content of the button. + /// </summary> + /// <param name="writer">The writer.</param> + /// <param name="selector">The containing selector control.</param> + protected internal override void RenderButtonContent(HtmlTextWriter writer, OpenIdSelector selector) { + writer.AddAttribute(HtmlTextWriterAttribute.Src, selector.Page.ResolveUrl(this.Image)); + writer.RenderBeginTag(HtmlTextWriterTag.Img); + writer.RenderEndTag(); + + writer.AddAttribute(HtmlTextWriterAttribute.Src, selector.Page.ClientScript.GetWebResourceUrl(typeof(OpenIdAjaxTextBox), OpenIdAjaxTextBox.EmbeddedLoginSuccessResourceName)); + writer.AddAttribute(HtmlTextWriterAttribute.Class, "loginSuccess"); + writer.AddAttribute(HtmlTextWriterAttribute.Title, selector.AuthenticatedAsToolTip); + writer.RenderBeginTag(HtmlTextWriterTag.Img); + writer.RenderEndTag(); + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/ServiceEndpoint.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/ServiceEndpoint.cs index 88264f5..f8744d0 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/ServiceEndpoint.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/ServiceEndpoint.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { using System; using System.Collections.ObjectModel; using System.Diagnostics; + using System.Diagnostics.Contracts; using System.Globalization; using System.IO; using System.Linq; @@ -57,8 +58,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <param name="servicePriority">The service priority.</param> /// <param name="uriPriority">The URI priority.</param> private ServiceEndpoint(ProviderEndpointDescription providerEndpoint, Identifier claimedIdentifier, Identifier userSuppliedIdentifier, Identifier providerLocalIdentifier, int? servicePriority, int? uriPriority) { - ErrorUtilities.VerifyArgumentNotNull(claimedIdentifier, "claimedIdentifier"); - ErrorUtilities.VerifyArgumentNotNull(providerEndpoint, "providerEndpoint"); + Contract.Requires<ArgumentNullException>(claimedIdentifier != null); + Contract.Requires<ArgumentNullException>(providerEndpoint != null); this.ProviderDescription = providerEndpoint; this.ClaimedIdentifier = claimedIdentifier; this.UserSuppliedIdentifier = userSuppliedIdentifier; @@ -79,10 +80,10 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// Used for deserializing <see cref="ServiceEndpoint"/> from authentication responses. /// </remarks> private ServiceEndpoint(Uri providerEndpoint, Identifier claimedIdentifier, Identifier userSuppliedIdentifier, Identifier providerLocalIdentifier, Protocol protocol) { - ErrorUtilities.VerifyArgumentNotNull(providerEndpoint, "providerEndpoint"); - ErrorUtilities.VerifyArgumentNotNull(claimedIdentifier, "claimedIdentifier"); - ErrorUtilities.VerifyArgumentNotNull(providerLocalIdentifier, "providerLocalIdentifier"); - ErrorUtilities.VerifyArgumentNotNull(protocol, "protocol"); + Contract.Requires<ArgumentNullException>(providerEndpoint != null); + Contract.Requires<ArgumentNullException>(claimedIdentifier != null); + Contract.Requires<ArgumentNullException>(providerLocalIdentifier != null); + Contract.Requires<ArgumentNullException>(protocol != null); this.ClaimedIdentifier = claimedIdentifier; this.UserSuppliedIdentifier = userSuppliedIdentifier; @@ -148,7 +149,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } } else if (uri != null) { if (uri != this.Protocol.ClaimedIdentifierForOPIdentifier) { - string displayUri = uri.Uri.Authority + uri.Uri.PathAndQuery; + string displayUri = uri.Uri.Host + uri.Uri.AbsolutePath; displayUri = displayUri.TrimEnd('/'); // Multi-byte unicode characters get encoded by the Uri class for transit. @@ -427,13 +428,13 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// Creates a <see cref="ServiceEndpoint"/> instance to represent some OP Identifier. /// </summary> - /// <param name="providerIdentifier">The provider identifier.</param> + /// <param name="providerIdentifier">The provider identifier (actually the user-supplied identifier).</param> /// <param name="providerEndpoint">The provider endpoint.</param> /// <param name="servicePriority">The service priority.</param> /// <param name="uriPriority">The URI priority.</param> /// <returns>The created <see cref="ServiceEndpoint"/> instance</returns> internal static ServiceEndpoint CreateForProviderIdentifier(Identifier providerIdentifier, ProviderEndpointDescription providerEndpoint, int? servicePriority, int? uriPriority) { - ErrorUtilities.VerifyArgumentNotNull(providerEndpoint, "providerEndpoint"); + Contract.Requires<ArgumentNullException>(providerEndpoint != null); Protocol protocol = Protocol.Detect(providerEndpoint.Capabilities); diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/WellKnownProviders.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/WellKnownProviders.cs index 2e2ab61..ad1a11a 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/WellKnownProviders.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/WellKnownProviders.cs @@ -30,6 +30,20 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { public static readonly Identifier MyOpenId = "https://www.myopenid.com/"; /// <summary> + /// The Verisign OP Identifier. + /// </summary> + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Verisign", Justification = "The spelling is correct.")] + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "Immutable type")] + public static readonly Identifier Verisign = "https://pip.verisignlabs.com/"; + + /// <summary> + /// The MyVidoop OP Identifier. + /// </summary> + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Vidoop", Justification = "The spelling is correct.")] + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "Immutable type")] + public static readonly Identifier MyVidoop = "https://myvidoop.com/"; + + /// <summary> /// Prevents a default instance of the <see cref="WellKnownProviders"/> class from being created. /// </summary> private WellKnownProviders() { diff --git a/src/DotNetOpenAuth/OpenId/RelyingPartyDescription.cs b/src/DotNetOpenAuth/OpenId/RelyingPartyDescription.cs index 6b82966..279ca30 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingPartyDescription.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingPartyDescription.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OpenId { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.Linq; using System.Text; using DotNetOpenAuth.Messaging; @@ -26,8 +27,8 @@ namespace DotNetOpenAuth.OpenId { /// The Type URIs of supported services advertised on a relying party's XRDS document. /// </param> internal RelyingPartyEndpointDescription(Uri returnTo, string[] supportedServiceTypeUris) { - ErrorUtilities.VerifyArgumentNotNull(returnTo, "returnTo"); - ErrorUtilities.VerifyArgumentNotNull(supportedServiceTypeUris, "supportedServiceTypeUris"); + Contract.Requires<ArgumentNullException>(returnTo != null); + Contract.Requires<ArgumentNullException>(supportedServiceTypeUris != null); this.ReturnToEndpoint = returnTo; this.Protocol = GetProtocolFromServices(supportedServiceTypeUris); diff --git a/src/DotNetOpenAuth/OpenId/SecuritySettings.cs b/src/DotNetOpenAuth/OpenId/SecuritySettings.cs index d4df697..26f6d2a 100644 --- a/src/DotNetOpenAuth/OpenId/SecuritySettings.cs +++ b/src/DotNetOpenAuth/OpenId/SecuritySettings.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.OpenId { using System; using System.Collections.Generic; using System.Collections.Specialized; + using System.Diagnostics.Contracts; using DotNetOpenAuth.Messaging; /// <summary> @@ -87,7 +88,7 @@ namespace DotNetOpenAuth.OpenId { /// <c>true</c> if the association is permitted given the security requirements; otherwise, <c>false</c>. /// </returns> internal bool IsAssociationInPermittedRange(Association association) { - ErrorUtilities.VerifyArgumentNotNull(association, "association"); + Contract.Requires<ArgumentNullException>(association != null); return association.HashBitLength >= this.MinimumHashBitLength && association.HashBitLength <= this.MaximumHashBitLength; } } diff --git a/src/DotNetOpenAuth/OpenId/UriIdentifier.cs b/src/DotNetOpenAuth/OpenId/UriIdentifier.cs index 512200d..28d8b37 100644 --- a/src/DotNetOpenAuth/OpenId/UriIdentifier.cs +++ b/src/DotNetOpenAuth/OpenId/UriIdentifier.cs @@ -35,6 +35,7 @@ namespace DotNetOpenAuth.OpenId { /// <param name="uri">The value this identifier will represent.</param> internal UriIdentifier(string uri) : this(uri, false) { + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(uri)); } /// <summary> @@ -43,8 +44,8 @@ namespace DotNetOpenAuth.OpenId { /// <param name="uri">The value this identifier will represent.</param> /// <param name="requireSslDiscovery">if set to <c>true</c> [require SSL discovery].</param> internal UriIdentifier(string uri, bool requireSslDiscovery) - : base(requireSslDiscovery) { - ErrorUtilities.VerifyNonZeroLength(uri, "uri"); + : base(uri, requireSslDiscovery) { + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(uri)); Uri canonicalUri; bool schemePrepended; if (!TryCanonicalize(uri, out canonicalUri, requireSslDiscovery, out schemePrepended)) { @@ -70,8 +71,8 @@ namespace DotNetOpenAuth.OpenId { /// <param name="uri">The value this identifier will represent.</param> /// <param name="requireSslDiscovery">if set to <c>true</c> [require SSL discovery].</param> internal UriIdentifier(Uri uri, bool requireSslDiscovery) - : base(requireSslDiscovery) { - ErrorUtilities.VerifyArgumentNotNull(uri, "uri"); + : base(uri != null ? uri.OriginalString : null, requireSslDiscovery) { + Contract.Requires<ArgumentNullException>(uri != null); if (!TryCanonicalize(new UriBuilder(uri), out uri)) { throw new UriFormatException(); } @@ -416,8 +417,7 @@ namespace DotNetOpenAuth.OpenId { /// require network access are also done, such as lower-casing the hostname in the URI. /// </remarks> private static bool TryCanonicalize(string uri, out Uri canonicalUri, bool forceHttpsDefaultScheme, out bool schemePrepended) { - Contract.Requires(!string.IsNullOrEmpty(uri)); - ErrorUtilities.VerifyNonZeroLength(uri, "uri"); + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(uri)); uri = uri.Trim(); canonicalUri = null; @@ -462,6 +462,7 @@ namespace DotNetOpenAuth.OpenId { /// <summary> /// Verifies conditions that should be true for any valid state of this object. /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] [ContractInvariantMethod] private void ObjectInvariant() { diff --git a/src/DotNetOpenAuth/OpenId/XriIdentifier.cs b/src/DotNetOpenAuth/OpenId/XriIdentifier.cs index c659982..9bcb9cf 100644 --- a/src/DotNetOpenAuth/OpenId/XriIdentifier.cs +++ b/src/DotNetOpenAuth/OpenId/XriIdentifier.cs @@ -62,7 +62,8 @@ namespace DotNetOpenAuth.OpenId { /// <param name="xri">The string value of the XRI.</param> internal XriIdentifier(string xri) : this(xri, false) { - Contract.Requires((xri != null && xri.Length > 0) || !string.IsNullOrEmpty(xri)); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(xri)); + Contract.Requires<FormatException>(IsValidXri(xri), OpenIdStrings.InvalidXri); } /// <summary> @@ -74,9 +75,9 @@ namespace DotNetOpenAuth.OpenId { /// only succeed if it can be done entirely using SSL. /// </param> internal XriIdentifier(string xri, bool requireSsl) - : base(requireSsl) { - Contract.Requires((xri != null && xri.Length > 0) || !string.IsNullOrEmpty(xri)); - ErrorUtilities.VerifyFormat(IsValidXri(xri), OpenIdStrings.InvalidXri, xri); + : base(xri, requireSsl) { + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(xri)); + Contract.Requires<FormatException>(IsValidXri(xri), OpenIdStrings.InvalidXri); Contract.Assume(xri != null); // Proven by IsValidXri this.xriResolverProxy = XriResolverProxyTemplate; if (requireSsl) { @@ -167,8 +168,7 @@ namespace DotNetOpenAuth.OpenId { /// <c>true</c> if the given string constitutes a valid XRI; otherwise, <c>false</c>. /// </returns> internal static bool IsValidXri(string xri) { - Contract.Requires((xri != null && xri.Length > 0) || !string.IsNullOrEmpty(xri)); - ErrorUtilities.VerifyNonZeroLength(xri, "xri"); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(xri)); xri = xri.Trim(); // TODO: better validation code here @@ -196,8 +196,8 @@ namespace DotNetOpenAuth.OpenId { /// <param name="userSuppliedIdentifier">The user supplied identifier, which may differ from this XRI instance due to multiple discovery steps.</param> /// <returns>A list of service endpoints offered for this identifier.</returns> internal IEnumerable<ServiceEndpoint> Discover(IDirectWebRequestHandler requestHandler, XriIdentifier userSuppliedIdentifier) { - Contract.Requires(requestHandler != null); - Contract.Requires(userSuppliedIdentifier != null); + Contract.Requires<ArgumentNullException>(requestHandler != null); + Contract.Requires<ArgumentNullException>(userSuppliedIdentifier != null); return this.DownloadXrds(requestHandler).CreateServiceEndpoints(userSuppliedIdentifier); } @@ -243,7 +243,7 @@ namespace DotNetOpenAuth.OpenId { /// <returns>The canonicalized form of the XRI.</returns> /// <remarks>The canonical form, per the OpenID spec, is no scheme and no whitespace on either end.</remarks> private static string CanonicalizeXri(string xri) { - Contract.Requires(xri != null); + Contract.Requires<ArgumentNullException>(xri != null); Contract.Ensures(Contract.Result<string>() != null); xri = xri.Trim(); if (xri.StartsWith(XriScheme, StringComparison.OrdinalIgnoreCase)) { @@ -259,7 +259,7 @@ namespace DotNetOpenAuth.OpenId { /// <param name="requestHandler">The request handler.</param> /// <returns>The XRDS document.</returns> private XrdsDocument DownloadXrds(IDirectWebRequestHandler requestHandler) { - Contract.Requires(requestHandler != null); + Contract.Requires<ArgumentNullException>(requestHandler != null); Contract.Ensures(Contract.Result<XrdsDocument>() != null); XrdsDocument doc; using (var xrdsResponse = Yadis.Request(requestHandler, this.XrdsUrl, this.IsDiscoverySecureEndToEnd)) { @@ -273,6 +273,7 @@ namespace DotNetOpenAuth.OpenId { /// <summary> /// Verifies conditions that should be true for any valid state of this object. /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] [ContractInvariantMethod] private void ObjectInvariant() { diff --git a/src/DotNetOpenAuth/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth/Properties/AssemblyInfo.cs index 69d4dc4..51d146c 100644 --- a/src/DotNetOpenAuth/Properties/AssemblyInfo.cs +++ b/src/DotNetOpenAuth/Properties/AssemblyInfo.cs @@ -58,7 +58,7 @@ using System.Web.UI; // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("7d73990c-47c0-4256-9f20-a893add9e289")] -[assembly: ContractVerification(false)] +[assembly: ContractVerification(true)] #if StrongNameSigned // See comment at top of this file. We need this so that strong-naming doesn't diff --git a/src/DotNetOpenAuth/Strings.sr.resx b/src/DotNetOpenAuth/Strings.sr.resx new file mode 100644 index 0000000..5112265 --- /dev/null +++ b/src/DotNetOpenAuth/Strings.sr.resx @@ -0,0 +1,126 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="ConfigurationTypeMustBePublic" xml:space="preserve"> + <value>Konfiguraciono zavisni tip {0} mora biti javan, a on to nije.</value> + </data> + <data name="ConfigurationXamlReferenceRequiresHttpContext" xml:space="preserve"> + <value>Konfiguraciona XAML referenca na {0} zahteva da se trenutni HttpContext razreši.</value> + </data> +</root>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/UriUtil.cs b/src/DotNetOpenAuth/UriUtil.cs index d04491d..819c406 100644 --- a/src/DotNetOpenAuth/UriUtil.cs +++ b/src/DotNetOpenAuth/UriUtil.cs @@ -30,8 +30,7 @@ namespace DotNetOpenAuth { /// </returns> [ContractVerification(false)] // bugs/limitations in CC static analysis internal static bool QueryStringContainPrefixedParameters(this Uri uri, string prefix) { - Contract.Requires(prefix != null && prefix.Length > 0); - ErrorUtilities.VerifyNonZeroLength(prefix, "prefix"); + Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(prefix)); if (uri == null) { return false; } @@ -49,10 +48,7 @@ namespace DotNetOpenAuth { /// <c>true</c> if the URI represents an encrypted request; otherwise, <c>false</c>. /// </returns> internal static bool IsTransportSecure(this Uri uri) { - if (uri == null) { - throw new ArgumentNullException("uri"); - } - + Contract.Requires<ArgumentNullException>(uri != null); return string.Equals(uri.Scheme, "https", StringComparison.OrdinalIgnoreCase); } @@ -63,9 +59,8 @@ namespace DotNetOpenAuth { /// <param name="builder">The UriBuilder to render as a string.</param> /// <returns>The string version of the Uri.</returns> internal static string ToStringWithImpliedPorts(this UriBuilder builder) { - Contract.Requires(builder != null); + Contract.Requires<ArgumentNullException>(builder != null); Contract.Ensures(Contract.Result<string>() != null); - ErrorUtilities.VerifyArgumentNotNull(builder, "builder"); // We only check for implied ports on HTTP and HTTPS schemes since those // are the only ones supported by OpenID anyway. diff --git a/src/DotNetOpenAuth/Util.cs b/src/DotNetOpenAuth/Util.cs index 1a67966..9f8b30c 100644 --- a/src/DotNetOpenAuth/Util.cs +++ b/src/DotNetOpenAuth/Util.cs @@ -11,6 +11,7 @@ namespace DotNetOpenAuth { using System.Net; using System.Reflection; using System.Text; + using DotNetOpenAuth.Messaging; /// <summary> /// A grab-bag utility class. @@ -74,7 +75,8 @@ namespace DotNetOpenAuth { return new DelayedToString<IEnumerable<KeyValuePair<K, V>>>( pairs, p => { - Contract.Requires(pairs != null); + ////Contract.Requires(pairs != null); // CC: anonymous method can't handle it + ErrorUtilities.VerifyArgumentNotNull(pairs, "pairs"); var dictionary = pairs as IDictionary<K, V>; StringBuilder sb = new StringBuilder(dictionary != null ? dictionary.Count * 40 : 200); foreach (var pair in pairs) { @@ -102,13 +104,16 @@ namespace DotNetOpenAuth { /// <param name="list">The list of elements.</param> /// <param name="multiLineElements">if set to <c>true</c>, special formatting will be applied to the output to make it clear where one element ends and the next begins.</param> /// <returns>An object whose ToString method will perform the actual work of generating the string.</returns> + [ContractVerification(false)] internal static object ToStringDeferred<T>(this IEnumerable<T> list, bool multiLineElements) { return new DelayedToString<IEnumerable<T>>( list, l => { - Contract.Requires(l != null); + // Code contracts not allowed in generator methods. + ErrorUtilities.VerifyArgumentNotNull(l, "l"); + string newLine = Environment.NewLine; - Contract.Assume(newLine != null && newLine.Length > 0); + ////Contract.Assume(newLine != null && newLine.Length > 0); StringBuilder sb = new StringBuilder(); if (multiLineElements) { sb.AppendLine("[{"); @@ -169,7 +174,7 @@ namespace DotNetOpenAuth { /// <param name="obj">The object that may be serialized to string form.</param> /// <param name="toString">The method that will serialize the object if called upon.</param> public DelayedToString(T obj, Func<T, string> toString) { - Contract.Requires(toString != null); + Contract.Requires<ArgumentNullException>(toString != null); this.obj = obj; this.toString = toString; 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 e27a1b2..f8fa0af 100644 --- a/src/DotNetOpenAuth/Xrds/XrdsNode.cs +++ b/src/DotNetOpenAuth/Xrds/XrdsNode.cs @@ -5,6 +5,8 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.Xrds { + using System; + using System.Diagnostics.Contracts; using System.Xml; using System.Xml.XPath; using DotNetOpenAuth.Messaging; @@ -29,8 +31,8 @@ namespace DotNetOpenAuth.Xrds { /// <param name="node">The node represented by this instance.</param> /// <param name="parentNode">The parent node.</param> protected XrdsNode(XPathNavigator node, XrdsNode parentNode) { - ErrorUtilities.VerifyArgumentNotNull(node, "node"); - ErrorUtilities.VerifyArgumentNotNull(parentNode, "parentNode"); + Contract.Requires<ArgumentNullException>(node != null); + Contract.Requires<ArgumentNullException>(parentNode != null); this.Node = node; this.ParentNode = parentNode; @@ -42,7 +44,8 @@ namespace DotNetOpenAuth.Xrds { /// </summary> /// <param name="document">The document's root node, which this instance represents.</param> protected XrdsNode(XPathNavigator document) { - ErrorUtilities.VerifyArgumentNotNull(document, "document"); + Contract.Requires<ArgumentNullException>(document != null); + Contract.Requires<ArgumentException>(document.NameTable != null); this.Node = document; this.XmlNamespaceResolver = new XmlNamespaceManager(document.NameTable); diff --git a/src/DotNetOpenAuth/Xrds/XrdsStrings.sr.resx b/src/DotNetOpenAuth/Xrds/XrdsStrings.sr.resx new file mode 100644 index 0000000..8e2d09a --- /dev/null +++ b/src/DotNetOpenAuth/Xrds/XrdsStrings.sr.resx @@ -0,0 +1,132 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="CIDVerificationFailed" xml:space="preserve"> + <value>XRI CanonicalID provera neuspešna.</value> + </data> + <data name="InvalidXRDSDocument" xml:space="preserve"> + <value>Greška u obradi XRDS dokumenta.</value> + </data> + <data name="MissingCanonicalIDElement" xml:space="preserve"> + <value>XRDS dokumentu za XRI {0} nedostaje neophodni CanonicalID element.</value> + </data> + <data name="XriResolutionStatusMissing" xml:space="preserve"> + <value>Ne može se pronaći XRI resolution Status tag ili je code attribute neispravan.</value> + </data> +</root>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/Yadis/HtmlParser.cs b/src/DotNetOpenAuth/Yadis/HtmlParser.cs index 406cb4b..a6b64c1 100644 --- a/src/DotNetOpenAuth/Yadis/HtmlParser.cs +++ b/src/DotNetOpenAuth/Yadis/HtmlParser.cs @@ -98,8 +98,8 @@ namespace DotNetOpenAuth.Yadis { /// <param name="attribute">The attribute.</param> /// <returns>A filtered sequence of attributes.</returns> internal static IEnumerable<T> WithAttribute<T>(this IEnumerable<T> sequence, string attribute) where T : HtmlControl { - Contract.Requires(sequence != null); - Contract.Requires(!String.IsNullOrEmpty(attribute)); + Contract.Requires<ArgumentNullException>(sequence != null); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(attribute)); return sequence.Where(tag => tag.Attributes[attribute] != null); } diff --git a/src/DotNetOpenAuth/Yadis/Yadis.cs b/src/DotNetOpenAuth/Yadis/Yadis.cs index 14aea62..a9a573b 100644 --- a/src/DotNetOpenAuth/Yadis/Yadis.cs +++ b/src/DotNetOpenAuth/Yadis/Yadis.cs @@ -6,6 +6,7 @@ namespace DotNetOpenAuth.Yadis { using System; + using System.Diagnostics.Contracts; using System.IO; using System.Net; using System.Net.Cache; @@ -132,8 +133,8 @@ namespace DotNetOpenAuth.Yadis { /// <param name="acceptTypes">The value of the Accept HTTP header to include in the request.</param> /// <returns>The HTTP response retrieved from the request.</returns> internal static IncomingWebResponse Request(IDirectWebRequestHandler requestHandler, Uri uri, bool requireSsl, params string[] acceptTypes) { - ErrorUtilities.VerifyArgumentNotNull(requestHandler, "requestHandler"); - ErrorUtilities.VerifyArgumentNotNull(uri, "uri"); + Contract.Requires<ArgumentNullException>(requestHandler != null); + Contract.Requires<ArgumentNullException>(uri != null); HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri); request.CachePolicy = IdentifierDiscoveryCachePolicy; diff --git a/src/official-build-key.pub b/src/official-build-key.pub Binary files differnew file mode 100644 index 0000000..6e68f53 --- /dev/null +++ b/src/official-build-key.pub diff --git a/src/version.txt b/src/version.txt index b347b11..4772543 100644 --- a/src/version.txt +++ b/src/version.txt @@ -1 +1 @@ -3.2.3 +3.3.2 |