diff options
Diffstat (limited to 'src')
72 files changed, 4365 insertions, 2388 deletions
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..72415d9 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/ChangeProjectReferenceToAssemblyReference.cs @@ -0,0 +1,50 @@ +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) { + 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)); + 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/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/CustomMsBuildTasks.sln b/src/DotNetOpenAuth.BuildTasks/CustomMsBuildTasks.sln new file mode 100644 index 0000000..6eae4e0 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/CustomMsBuildTasks.sln @@ -0,0 +1,20 @@ + +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 +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/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/DotNetOpenAuth.BuildTasks.csproj b/src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.csproj new file mode 100644 index 0000000..4eb920e --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.csproj @@ -0,0 +1,85 @@ +<?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> + </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.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" /> + </ItemGroup> + <ItemGroup> + <Compile Include="ChangeProjectReferenceToAssemblyReference.cs" /> + <Compile Include="CompareFiles.cs" /> + <Compile Include="ChangeAssemblyReference.cs" /> + <Compile Include="CreateWebApplication.cs" /> + <Compile Include="DeleteWebApplication.cs" /> + <Compile Include="FilterItems.cs" /> + <Compile Include="GetBuildVersion.cs" /> + <Compile Include="CheckAdminRights.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/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/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/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/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..1c32632 --- /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" }; + 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..a727695 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/TaskStrings.Designer.cs @@ -0,0 +1,99 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:2.0.50727.4918 +// +// 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 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..2aaa32e --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/TaskStrings.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="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="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..1cc943e --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/Trim.cs @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------- +// <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. + /// </summary> + public class Trim : Task { + /// <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 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]); + if (!string.IsNullOrEmpty(this.StartCharacters)) { + this.Outputs[i].ItemSpec = this.Outputs[i].ItemSpec.TrimStart(this.StartCharacters.ToCharArray()); + } + if (!string.IsNullOrEmpty(this.EndCharacters)) { + this.Outputs[i].ItemSpec = this.Outputs[i].ItemSpec.TrimEnd(this.EndCharacters.ToCharArray()); + } + } + + return true; + } + } +} diff --git a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj index bd317af..bbf5d06 100644 --- a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj +++ b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj @@ -53,9 +53,10 @@ <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> </PropertyGroup> - <PropertyGroup Condition=" '$(Sign)' == 'true' "> + <PropertyGroup> <SignAssembly>true</SignAssembly> - <AssemblyOriginatorKeyFile>..\official-build-key.pfx</AssemblyOriginatorKeyFile> + <DelaySign>true</DelaySign> + <AssemblyOriginatorKeyFile>..\official-build-key.pub</AssemblyOriginatorKeyFile> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'CodeAnalysis|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -131,6 +132,7 @@ <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" /> 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/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/MockIdentifier.cs b/src/DotNetOpenAuth.Test/Mocks/MockIdentifier.cs index 669e4d3..bb52f65 100644 --- a/src/DotNetOpenAuth.Test/Mocks/MockIdentifier.cs +++ b/src/DotNetOpenAuth.Test/Mocks/MockIdentifier.cs @@ -24,7 +24,7 @@ namespace DotNetOpenAuth.Test.Mocks { private Identifier wrappedIdentifier; public MockIdentifier(Identifier wrappedIdentifier, MockHttpRequest mockHttpRequest, IEnumerable<ServiceEndpoint> endpoints) - : base(false) { + : base(wrappedIdentifier.OriginalString, false) { ErrorUtilities.VerifyArgumentNotNull(wrappedIdentifier, "wrappedIdentifier"); ErrorUtilities.VerifyArgumentNotNull(mockHttpRequest, "mockHttpRequest"); ErrorUtilities.VerifyArgumentNotNull(endpoints, "endpoints"); 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.vsmdi b/src/DotNetOpenAuth.vsmdi index a404b8e..3a5d028 100644 --- a/src/DotNetOpenAuth.vsmdi +++ b/src/DotNetOpenAuth.vsmdi @@ -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" /> @@ -124,16 +124,17 @@ <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" /> diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj index 6ee96c2..3c576a7 100644 --- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj +++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj @@ -25,7 +25,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 +48,8 @@ <CodeContractsShowSquigglies>True</CodeContractsShowSquigglies> <CodeContractsArithmeticObligations>False</CodeContractsArithmeticObligations> <CodeContractsRuntimeOnlyPublicSurface>False</CodeContractsRuntimeOnlyPublicSurface> + <CodeContractsRuntimeThrowOnFailure>False</CodeContractsRuntimeThrowOnFailure> + <CodeContractsRuntimeCallSiteRequires>False</CodeContractsRuntimeCallSiteRequires> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> @@ -60,7 +62,7 @@ <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> @@ -81,16 +83,21 @@ <CodeContractsUseBaseLine>False</CodeContractsUseBaseLine> <CodeContractsRunInBackground>True</CodeContractsRunInBackground> <CodeContractsShowSquigglies>False</CodeContractsShowSquigglies> + <CodeContractsRuntimeOnlyPublicSurface>False</CodeContractsRuntimeOnlyPublicSurface> + <CodeContractsRuntimeThrowOnFailure>False</CodeContractsRuntimeThrowOnFailure> + <CodeContractsRuntimeCallSiteRequires>False</CodeContractsRuntimeCallSiteRequires> + <CodeContractsArithmeticObligations>False</CodeContractsArithmeticObligations> </PropertyGroup> - <PropertyGroup Condition=" '$(Sign)' == 'true' "> + <PropertyGroup> <SignAssembly>true</SignAssembly> - <AssemblyOriginatorKeyFile>..\official-build-key.pfx</AssemblyOriginatorKeyFile> + <DelaySign>true</DelaySign> + <AssemblyOriginatorKeyFile>..\official-build-key.pub</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> @@ -146,9 +153,15 @@ <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> @@ -491,7 +504,7 @@ <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\ServiceEndpoint.cs" /> @@ -541,6 +554,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"> @@ -599,12 +614,19 @@ <EmbeddedResource Include="OpenId\RelyingParty\OpenIdRelyingPartyControlBase.js" /> </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\Behaviors\BehaviorStrings.sr.resx" /> + <EmbeddedResource Include="OpenId\OpenIdStrings.sr.resx" /> <EmbeddedResource Include="OpenId\RelyingParty\OpenIdRelyingPartyAjaxControlBase.js" /> + <EmbeddedResource Include="Strings.sr.resx" /> + <EmbeddedResource Include="Xrds\XrdsStrings.sr.resx" /> </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="..\..\tools\DotNetOpenAuth.Versioning.targets" /> -</Project> +</Project>
\ 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/Messaging/Channel.cs b/src/DotNetOpenAuth/Messaging/Channel.cs index 2e0f1a8..e2c1301 100644 --- a/src/DotNetOpenAuth/Messaging/Channel.cs +++ b/src/DotNetOpenAuth/Messaging/Channel.cs @@ -843,6 +843,42 @@ 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(requestMessage != null); + Contract.Ensures(Contract.Result<HttpWebRequest>() != null); + ErrorUtilities.VerifyArgumentNotNull(requestMessage, "requestMessage"); + + 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(requestMessage != null); + Contract.Ensures(Contract.Result<HttpWebRequest>() != null); + ErrorUtilities.VerifyArgumentNotNull(requestMessage, "requestMessage"); + + 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> 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 14666f2..9ffcce8 100644 --- a/src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs +++ b/src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs @@ -164,11 +164,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> diff --git a/src/DotNetOpenAuth/Messaging/MessageReceivingEndpoint.cs b/src/DotNetOpenAuth/Messaging/MessageReceivingEndpoint.cs index 79a1107..4411c92 100644 --- a/src/DotNetOpenAuth/Messaging/MessageReceivingEndpoint.cs +++ b/src/DotNetOpenAuth/Messaging/MessageReceivingEndpoint.cs @@ -30,7 +30,7 @@ namespace DotNetOpenAuth.Messaging { 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); + ErrorUtilities.VerifyArgumentInRange((method & HttpDeliveryMethods.HttpVerbMask) != 0, "method", MessagingStrings.GetOrPostFlagsRequired); this.Location = location; this.AllowedMethods = method; 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..c63ad9f 100644 --- a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs +++ b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs @@ -588,7 +588,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="HttpDeliveryMethod"/> 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> 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..afeaab3 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderTokenManager.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderTokenManager.cs @@ -50,5 +50,19 @@ 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); } } diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs index d325825..e328457 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs @@ -207,6 +207,10 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { 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(); } @@ -296,7 +300,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { 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/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/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/TokenHandlingBindingElement.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/TokenHandlingBindingElement.cs index 3e75e7b..c4e78c9 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/TokenHandlingBindingElement.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/TokenHandlingBindingElement.cs @@ -72,7 +72,9 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { 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 +82,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; } 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/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/ServiceProvider.cs b/src/DotNetOpenAuth/OAuth/ServiceProvider.cs index fe65ed7..0b6875e 100644 --- a/src/DotNetOpenAuth/OAuth/ServiceProvider.cs +++ b/src/DotNetOpenAuth/OAuth/ServiceProvider.cs @@ -257,11 +257,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 @@ -291,24 +292,43 @@ namespace DotNetOpenAuth.OAuth { return authzRequest; } - /// <summary> + /// <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="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.")] - public void AttachAuthorizationResponse(IAuthenticationRequest openIdAuthenticationRequest, string consumerKey, string scope) { + [Obsolete("Call the overload that doesn't take a consumerKey instead.")] + public void AttachAuthorizationResponse(IHostProcessedRequest 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; + var openidTokenManager = this.TokenManager as ICombinedOpenIdProviderTokenManager; ErrorUtilities.VerifyOperation(openidTokenManager != null, OAuthStrings.OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface, typeof(IOpenIdOAuthTokenManager).FullName); + 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="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(IHostProcessedRequest openIdAuthenticationRequest, string scope) { + Contract.Requires(openIdAuthenticationRequest != null); + Contract.Requires(this.TokenManager is IOpenIdOAuthTokenManager); + ErrorUtilities.VerifyArgumentNotNull(openIdAuthenticationRequest, "openIdAuthenticationRequest"); + var openidTokenManager = this.TokenManager as ICombinedOpenIdProviderTokenManager; + ErrorUtilities.VerifyOperation(openidTokenManager != null, OAuthStrings.OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface, typeof(ICombinedOpenIdProviderTokenManager).FullName); 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, diff --git a/src/DotNetOpenAuth/OAuth/WebConsumer.cs b/src/DotNetOpenAuth/OAuth/WebConsumer.cs index 56d3029..d86444d 100644 --- a/src/DotNetOpenAuth/OAuth/WebConsumer.cs +++ b/src/DotNetOpenAuth/OAuth/WebConsumer.cs @@ -120,7 +120,8 @@ namespace DotNetOpenAuth.OAuth { } // Prepare a message to exchange the request token for an access token. - var requestAccess = new AuthorizedTokenRequest(this.ServiceProvider.AccessTokenEndpoint, this.ServiceProvider.Version) { + // We are careful to use a v1.0 message version so that the oauth_verifier is not required. + var requestAccess = new AuthorizedTokenRequest(this.ServiceProvider.AccessTokenEndpoint, Protocol.V10.Version) { RequestToken = positiveAuthorization.RequestToken, ConsumerKey = this.ConsumerKey, }; 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/Identifier.cs b/src/DotNetOpenAuth/OpenId/Identifier.cs index 6e71b0a..f3de903 100644 --- a/src/DotNetOpenAuth/OpenId/Identifier.cs +++ b/src/DotNetOpenAuth/OpenId/Identifier.cs @@ -24,15 +24,20 @@ 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> + 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> diff --git a/src/DotNetOpenAuth/OpenId/IdentifierContract.cs b/src/DotNetOpenAuth/OpenId/IdentifierContract.cs index 1758f06..9ab56c5 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> diff --git a/src/DotNetOpenAuth/OpenId/NoDiscoveryIdentifier.cs b/src/DotNetOpenAuth/OpenId/NoDiscoveryIdentifier.cs index 2c8e865..72f74c8 100644 --- a/src/DotNetOpenAuth/OpenId/NoDiscoveryIdentifier.cs +++ b/src/DotNetOpenAuth/OpenId/NoDiscoveryIdentifier.cs @@ -28,7 +28,7 @@ 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) { + : base(wrappedIdentifier.OriginalString, claimSsl) { Contract.Requires(wrappedIdentifier != null); ErrorUtilities.VerifyArgumentNotNull(wrappedIdentifier, "wrappedIdentifier"); 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/OpenIdXrdsHelper.cs b/src/DotNetOpenAuth/OpenId/OpenIdXrdsHelper.cs index 2433df2..c3506fc 100644 --- a/src/DotNetOpenAuth/OpenId/OpenIdXrdsHelper.cs +++ b/src/DotNetOpenAuth/OpenId/OpenIdXrdsHelper.cs @@ -43,7 +43,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. @@ -83,7 +83,7 @@ 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); diff --git a/src/DotNetOpenAuth/OpenId/Realm.cs b/src/DotNetOpenAuth/OpenId/Realm.cs index 2f0dcb3..0baa8e1 100644 --- a/src/DotNetOpenAuth/OpenId/Realm.cs +++ b/src/DotNetOpenAuth/OpenId/Realm.cs @@ -400,6 +400,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"/>. @@ -420,17 +432,5 @@ namespace DotNetOpenAuth.OpenId { // 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/AuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs index 9912d0b..ff7741a 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs @@ -232,6 +232,28 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <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.VerifyNonZeroLength(key, "key"); + ErrorUtilities.VerifyArgumentNotNull(value, "value"); + ErrorUtilities.VerifyOperation(this.RelyingParty.CanSignCallbackArguments, OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IAssociationStore<Uri>).Name, typeof(OpenIdRelyingParty).Name); + + 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> @@ -345,11 +367,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); @@ -477,7 +500,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { request.AssociationHandle = association != null ? association.Handle : null; 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/IAuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequest.cs index 8414031..27daa46 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequest.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequest.cs @@ -125,6 +125,22 @@ 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 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 SetCallbackArgument(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/OpenIdAjaxTextBox.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.cs index 6a4413f..0933e9c 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.cs @@ -18,19 +18,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Drawing.Design; + using System.Diagnostics.Contracts; 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 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,7 +30,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> [DefaultProperty("Text"), ValidationProperty("Text")] [ToolboxData("<{0}:OpenIdAjaxTextBox runat=\"server\" />")] - public sealed class OpenIdAjaxTextBox : WebControl, ICallbackEventHandler { + public class OpenIdAjaxTextBox : OpenIdRelyingPartyAjaxControlBase, ICallbackEventHandler, IEditableTextControl, ITextControl, IPostBackDataHandler { /// <summary> /// The name of the manifest stream containing the OpenIdAjaxTextBox.js file. /// </summary> @@ -72,19 +64,14 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { private const string ColumnsViewStateKey = "Columns"; /// <summary> - /// The viewstate key to use for storing the value of the <see cref="OnClientAssertionReceived"/> property. - /// </summary> - private const string OnClientAssertionReceivedViewStateKey = "OnClientAssertionReceived"; - - /// <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="CssClass"/> property. /// </summary> - private const string AuthenticationResponseViewStateKey = "AuthenticationResponse"; + private const string CssClassViewStateKey = "CssClass"; /// <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="OnClientAssertionReceived"/> property. /// </summary> - private const string AuthDataViewStateKey = "AuthData"; + private const string OnClientAssertionReceivedViewStateKey = "OnClientAssertionReceived"; /// <summary> /// The viewstate key to use for storing the value of the <see cref="AuthenticatedAsToolTip"/> property. @@ -97,16 +84,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { private const string AuthenticationSucceededToolTipViewStateKey = "AuthenticationSucceededToolTip"; /// <summary> - /// The viewstate key to use for storing the value of the <see cref="ReturnToUrl"/> property. - /// </summary> - private const string ReturnToUrlViewStateKey = "ReturnToUrl"; - - /// <summary> - /// The viewstate key to use for storing the value of the <see cref="RealmUrl"/> property. - /// </summary> - private const string RealmUrlViewStateKey = "RealmUrl"; - - /// <summary> /// The viewstate key to use for storing the value of the <see cref="LogOnInProgressMessage"/> property. /// </summary> private const string LogOnInProgressMessageViewStateKey = "BusyToolTip"; @@ -152,14 +129,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. @@ -181,14 +158,9 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { private const int ColumnsDefault = 40; /// <summary> - /// The default value for the <see cref="ReturnToUrl"/> property. - /// </summary> - private const string ReturnToUrlDefault = ""; - - /// <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. @@ -258,64 +230,18 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { #endregion /// <summary> - /// Backing field for the <see cref="RelyingParty"/> property. - /// </summary> - private OpenIdRelyingParty relyingParty; - - /// <summary> - /// Backing field for the <see cref="RelyingPartyNonVerifying"/> property. - /// </summary> - private OpenIdRelyingParty relyingPartyNonVerifying; - - /// <summary> - /// Tracks whether the text box should receive input focus when the page is rendered. - /// </summary> - private bool focusCalled; - - /// <summary> - /// The authentication response that just came in. - /// </summary> - private IAuthenticationResponse authenticationResponse; - - /// <summary> /// A dictionary of extension response types and the javascript member /// name to map them to on the user agent. /// </summary> private Dictionary<Type, string> clientScriptExtensions = new Dictionary<Type, string>(); - /// <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; - #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 +257,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 +271,19 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { #region Properties /// <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); - - // 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.authenticationResponse; - } - } - - /// <summary> /// Gets or sets the value in the text field, completely unprocessed or normalized. /// </summary> - [Bindable(true), DefaultValue(""), Category("Appearance")] - [Description("The value in the text field, completely unprocessed or normalized.")] + [Bindable(true), DefaultValue(""), Category(AppearanceCategory)] + [Description("The content of the text box.")] public string Text { - get { return (string)(this.ViewState[TextViewStateKey] ?? string.Empty); } - set { this.ViewState[TextViewStateKey] = value ?? string.Empty; } + get { return this.Identifier != null ? this.Identifier.OriginalString : string.Empty; } + set { this.Identifier = 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 +291,45 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } set { + Contract.Requires<ArgumentOutOfRangeException>(value >= 0); ErrorUtilities.VerifyArgumentInRange(value >= 0, "value"); 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")] @@ -434,7 +348,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 { @@ -450,7 +364,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 { @@ -466,7 +380,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 { @@ -482,7 +396,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); } @@ -492,7 +406,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <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 { @@ -508,7 +422,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 +432,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 +442,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 +452,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 +462,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 +472,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,230 +482,25 @@ 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); } set { this.ViewState[LogOnInProgressMessageViewStateKey] = value ?? string.Empty; } } - /// <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.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; - } - } - - /// <summary> - /// Gets or sets the OpenID ReturnTo of the relying party web site. - /// </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; - } - } - #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(); } - } - - /// <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 +516,37 @@ 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; - } - } - - /// <summary> - /// Gets the name of the open id auth data form key. - /// </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; - } + #region IPostBackDataHandler Members /// <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. + /// When implemented by a class, processes postback data for an ASP.NET server control. /// </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. + /// Raises the <see cref="E:Load"/> event. /// </summary> - public sealed override void Dispose() { - this.Dispose(true); - base.Dispose(); - GC.SuppressFinalize(this); - } - - /// <summary> - /// Prepares the control for loading. - /// </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]; - } - - // 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(); - } - } + this.Page.RegisterRequiresPostBack(this); } /// <summary> @@ -988,180 +563,82 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// 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)); + if (!string.IsNullOrEmpty(this.CssClass)) { + writer.AddAttribute(HtmlTextWriterAttribute.Class, this.CssClass); + } + + 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; - } - /// <summary> - /// Fires the <see cref="UnconfirmedPositiveAssertion"/> event. - /// </summary> - private void OnUnconfirmedPositiveAssertion() { - var unconfirmedPositiveAssertion = this.UnconfirmedPositiveAssertion; - if (unconfirmedPositiveAssertion != null) { - unconfirmedPositiveAssertion(this, null); - } + return false; } /// <summary> - /// Fires the <see cref="LoggedIn"/> 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> - private void OnLoggedIn(IAuthenticationResponse response) { - var loggedIn = this.LoggedIn; - if (loggedIn != null) { - loggedIn(this, new OpenIdEventArgs(response)); - } + protected virtual void RaisePostDataChangedEvent() { + this.OnTextChanged(); } /// <summary> - /// Invokes a method on a parent frame/window's OpenIdAjaxTextBox, - /// and closes the calling popup window if applicable. + /// Called on a postback when the Text property has changed. /// </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)); - } + protected virtual void OnTextChanged() { + EventHandler textChanged = this.TextChanged; + if (textChanged != null) { + textChanged(this, EventArgs.Empty); } - - // 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> @@ -1186,9 +663,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { 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}", @@ -1228,106 +702,5 @@ if (!openidbox.dnoi_internal.onSubmit()) {{ return false; }} "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); - } - } - - this.CallbackUserAgentMethod("dnoi_internal.processAuthorizationResult(document.URL)", assignments.ToArray()); - } } } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.js b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.js index 1078003..531748a 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.js +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.js @@ -4,46 +4,12 @@ // </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, identifierRequiredMessage, loginInProgressMessage, authenticatedByToolTip, authenticatedAsToolTip, authenticationFailedToolTip, - discoverCallback, discoveryFailedCallback) { + discoverCallback/*removeme*/, discoveryFailedCallback) { box.dnoi_internal = new Object(); if (assertionReceivedCode) { box.dnoi_internal.onauthenticated = function(sender, e) { eval(assertionReceivedCode); } @@ -51,79 +17,8 @@ function initAjaxOpenId(box, openid_logo_url, dotnetopenid_logo_url, spinner_url 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'); @@ -199,14 +94,14 @@ function initAjaxOpenId(box, openid_logo_url, dotnetopenid_logo_url, spinner_url }; box.dnoi_internal.loginButton = box.dnoi_internal.constructButton(loginButtonText, loginButtonToolTip, function() { - var discoveryInfo = box.dnoi_internal.authenticationRequests[box.lastDiscoveredIdentifier]; + var discoveryInfo = window.dnoa_internal.discoveryResults[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(); + selectedProvider.loginPopup(box.dnoi_internal.onAuthSuccess, box.dnoi_internal.onAuthFailed); return false; }); box.dnoi_internal.retryButton = box.dnoi_internal.constructButton(retryButtonText, retryButtonToolTip, function() { @@ -250,8 +145,9 @@ function initAjaxOpenId(box, openid_logo_url, dotnetopenid_logo_url, spinner_url 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 == null) {// || 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'; @@ -291,20 +187,20 @@ function initAjaxOpenId(box, openid_logo_url, dotnetopenid_logo_url, spinner_url } 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 (window.dnoa_internal.discoveryResults[box.value] == null) 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() { @@ -316,7 +212,7 @@ function initAjaxOpenId(box, openid_logo_url, dotnetopenid_logo_url, spinner_url 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()) { @@ -380,180 +276,26 @@ 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 response = box.dnoi_internal.getUserSuppliedIdentifierResults().successAuthData; + 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); - } - } else { - box.dnoi_internal.discoveryFailed(null, this.identifier); - } - }; - - 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); - } - }; - - 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(); - - this.getDiscoveryInfo = function() { - return box.dnoi_internal.authenticationRequests[self.identifier]; + 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 return null; } - - 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. - } - }; - - 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; - } - - 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); - }; + var favicon = opUri.getAuthority() + "/favicon.ico"; + trace('Guessing favicon location of: ' + favicon); + return favicon; }; /***************************************** @@ -565,7 +307,8 @@ function initAjaxOpenId(box, openid_logo_url, dotnetopenid_logo_url, spinner_url 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); + var openid = new window.OpenIdIdentifier(identifier); + openid.discover(box.dnoi_internal.discoverySuccess, box.dnoi_internal.discoveryFailed); }; /// <summary>Callback that is invoked when discovery fails.</summary> @@ -577,95 +320,65 @@ function initAjaxOpenId(box, openid_logo_url, dotnetopenid_logo_url, spinner_url /// <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); - + box.dnoi_internal.discoverySuccess = function(discoveryResult) { // 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(); + if (discoveryResult.userSuppliedIdentifier === box.lastDiscoveredIdentifier) { + // 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); + } + } + discoveryResult.loginBackground( + box.dnoi_internal.authenticationIFrames, + box.dnoi_internal.onAuthSuccess, + box.dnoi_internal.onAuthFailed, + box.dnoi_internal.lastAuthenticationFailed, + box.timeout); } } - /// <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); + box.dnoi_internal.lastAuthenticationFailed = function() { + trace('No asynchronous authentication attempt is in progress. Display setup view.'); + // visual cue that auth failed + box.dnoi_internal.setVisualCue('setup'); + }; - // 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; + box.dnoi_internal.onAuthSuccess = function(discoveryResult, respondingEndpoint) { + // visual cue that auth was successful + var parsedPositiveAssertion = new window.dnoa_internal.PositiveAssertion(discoveryResult.successAuthData); + box.dnoi_internal.claimedIdentifier = parsedPositiveAssertion.claimedIdentifier; + box.dnoi_internal.setVisualCue('authenticated', parsedPositiveAssertion.endpoint, parsedPositiveAssertion.claimedIdentifier); + if (box.dnoi_internal.onauthenticated) { + box.dnoi_internal.onauthenticated(box); } - 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); - } - // visual cue that auth was successful - box.dnoi_internal.claimedIdentifier = discoveryInfo.claimedIdentifier; - box.dnoi_internal.setVisualCue('authenticated', tracker.endpoint, discoveryInfo.claimedIdentifier); - if (box.dnoi_internal.onauthenticated) { - box.dnoi_internal.onauthenticated(box); - } - 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) { - box.parentForm.submit(); - } else { - box.dnoi_internal.submitPending.click(); - } + 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) { + box.parentForm.submit(); + } else { + box.dnoi_internal.submitPending.click(); } - } else { - tracker.authFailed(); - } - - box.dnoi_internal.submitPending = null; - }; - 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"); + box.dnoi_internal.submitPending = null; } }; - function isOpenID2Response(resultUri) { - return resultUri.containsQueryArg("openid.ns"); + box.dnoi_internal.onAuthFailed = function() { + box.dnoi_internal.submitPending = null; }; - + box.onblur = function(event) { - var discoveryInfo = box.dnoi_internal.authenticationRequests[box.value]; - if (discoveryInfo == null) { - if (box.value.length > 0) { - box.dnoi_internal.performDiscovery(box.value); - } else { - box.dnoi_internal.setVisualCue(); - } + if (box.value.length > 0) { + box.dnoi_internal.performDiscovery(box.value); } else { - if ((priorSuccess = discoveryInfo.findSuccessfulRequest())) { - box.dnoi_internal.setVisualCue('authenticated', priorSuccess.endpoint, discoveryInfo.claimedIdentifier); - } else { - discoveryInfo.tryImmediate(); - } + box.dnoi_internal.setVisualCue(); } return true; }; @@ -677,101 +390,5 @@ function initAjaxOpenId(box, openid_logo_url, dotnetopenid_logo_url, spinner_url 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()); - } + window.dnoa_internal.deserializePreviousAuthentication(findOrCreateHiddenField().value, box.dnoi_internal.onAuthSuccess); } - -function Uri(url) { - this.originalUri = url; - - this.toString = function() { - return this.originalUri; - }; - - 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; - 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('#'); - if (hashmark >= 0) { - return new Uri(this.originalUri.substr(0, hashmark)); - } - return this; - }; - - 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; - } - }; - - function KeyValuePair(key, value) { - this.key = key; - this.value = value; - }; - - this.Pairs = new Array(); - - var queryBeginsAt = this.originalUri.indexOf('?'); - if (queryBeginsAt >= 0) { - this.queryString = url.substr(queryBeginsAt + 1); - var queryStringPairs = this.queryString.split('&'); - - 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))); - } - }; - - this.getQueryArgValue = function(key) { - for (var i = 0; i < this.Pairs.length; i++) { - if (this.Pairs[i].key == key) { - return this.Pairs[i].value; - } - } - }; - - this.containsQueryArg = function(key) { - return this.getQueryArgValue(key); - }; - - this.indexOf = function(args) { - return this.originalUri.indexOf(args); - }; - - return this; -}; diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdLogin.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdLogin.cs index fe1ce67..b0dce61 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 != LoginPersistence.Session; } + set { this.UsePersistentCookie = value ? LoginPersistence.PersistentAuthentication : LoginPersistence.Session; } } /// <summary> @@ -468,7 +568,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { set { unchecked { - this.WrappedTextBox.TabIndex = (short)(value + TextBoxTabIndexOffset); + 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 +585,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 +604,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 +634,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// cookie should persist across user sessions. /// </summary> [Browsable(false), Bindable(false)] - public override bool UsePersistentCookie { + public override LoginPersistence UsePersistentCookie { get { return base.UsePersistentCookie; } @@ -535,8 +644,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 != LoginPersistence.Session; + if (this.rememberMeCheckBox.Checked != rememberMe) { + this.rememberMeCheckBox.Checked = rememberMe; } } } @@ -544,27 +654,27 @@ 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); - - if (ShouldBeFocused) { - WrappedTextBox.Focus(); - } + base.Controls.Add(this.panel); } /// <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 +693,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 +721,7 @@ 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.ControlToValidate = this.ID; this.requiredValidator.ValidationGroup = ValidationGroupDefault; cell.Controls.Add(this.requiredValidator); this.identifierFormatValidator = new CustomValidator(); @@ -620,7 +730,7 @@ 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.ControlToValidate = this.ID; this.identifierFormatValidator.ValidationGroup = ValidationGroupDefault; cell.Controls.Add(this.identifierFormatValidator); this.errorLabel = new Label(); @@ -680,26 +790,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 { @@ -737,30 +838,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 +876,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 a917e24..a56f257 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdMobileTextBox.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdMobileTextBox.cs @@ -610,7 +610,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.SetCallbackArgument(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; 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/OpenIdRelyingPartyAjaxControlBase.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs index da2a9ae..480b08c 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs @@ -12,24 +12,18 @@ 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> @@ -46,7 +40,47 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { protected const string CallbackJsFunctionAsync = "window.dnoa_internal.callbackAsync"; /// <summary> - /// Stores the result of a AJAX callback discovery. + /// 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 the 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> + /// 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,10 +95,26 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> protected OpenIdRelyingPartyAjaxControlBase() { // The AJAX login style always uses popups (or invisible iframes). - this.Popup = PopupBehavior.Always; + base.Popup = PopupBehavior.Always; + + // The expected use case for the AJAX login box is for comments... not logging in. + this.LoginMode = LoginSiteNotification.None; } /// <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> @@ -74,6 +124,90 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { set { ErrorUtilities.VerifySupported(value == base.Popup, OpenIdStrings.PropertyValueNotSupported); } } + /// <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. + /// </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 { + 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); + } + #region ICallbackEventHandler Members /// <summary> @@ -118,7 +252,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { 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; @@ -139,6 +272,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { discoveryResultBuilder.Append("requests: new Array(),"); discoveryResultBuilder.AppendFormat("error: {0}", MessagingUtilities.GetSafeJavascriptValue(ex.Message)); } + discoveryResultBuilder.Append("}"); this.discoveryResult = discoveryResultBuilder.ToString(); } @@ -146,6 +280,39 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { #endregion /// <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.GetCallbackArgument(ReturnToReceivingControlId); + if (receiver == null || receiver == this.ClientID) { + this.ProcessResponse(this.AuthenticationResponse); + this.AuthenticationProcessedAlready = true; + } + } + } + } + + /// <summary> /// Creates the authentication requests for a given user-supplied Identifier. /// </summary> /// <returns>A sequence of authentication requests, any one of which may be @@ -176,6 +343,62 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <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); + List<string> assignments = new List<string>(); + + var authResponse = RelyingPartyNonVerifying.GetResponse(); + if (authResponse.Status == AuthenticationStatus.Authenticated) { + this.OnUnconfirmedPositiveAssertion(); // event handler will fill the clientScriptExtensions collection. + 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); + } + } + + 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); + } + this.CallbackUserAgentMethod("dnoa_internal.processAuthorizationResult(" + payload + ")", assignments.ToArray()); + } + + /// <summary> /// Creates the authentication requests for a given user-supplied Identifier. /// </summary> /// <param name="requests">The authentication requests to prepare.</param> @@ -189,25 +412,20 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { // 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.SetCallbackArgument("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])) { + req.SetCallbackArgument(AuthenticationRequest.UserSuppliedIdentifierParameterName, this.Identifier.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.SetCallbackArgument(OPEndpointParameterName, req.Provider.Uri.AbsoluteUri); + req.SetCallbackArgument(ClaimedIdParameterName, (string)req.ClaimedIdentifier ?? string.Empty); + + // Inform ourselves in return_to that we're in a popup or iframe. + req.SetCallbackArgument(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,33 +465,6 @@ 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> @@ -294,7 +485,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { 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) { @@ -306,11 +497,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { // 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(); diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.js b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.js index 65b1b99..c6f6a75 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.js +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.js @@ -1,5 +1,5 @@ //----------------------------------------------------------------------- -// <copyright file="OpenIdRelyingPartyControlBase.js" company="Andrew Arnott"> +// <copyright file="OpenIdRelyingPartyAjaxControlBase.js" company="Andrew Arnott"> // Copyright (c) Andrew Arnott. All rights reserved. // </copyright> //----------------------------------------------------------------------- @@ -8,139 +8,170 @@ if (window.dnoa_internal === undefined) { window.dnoa_internal = new Object(); }; +/// <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; + } +}; + window.dnoa_internal.discoveryResults = new Array(); // user supplied identifiers and discovery results -/// <summary>Instantiates an object that represents an OpenID Identifier.</summary> -window.OpenId = function(identifier) { - /// <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; - } +// The possible authentication results +window.dnoa_internal.authSuccess = new Object(); +window.dnoa_internal.authRefused = new Object(); +window.dnoa_internal.timedOut = new Object(); - 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) { - // 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; - - // 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.processAuthorizationResult = null; - } - } - }, 250); - }; - }; - - 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)")); - - if (discoveryInfo) { - 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; - } - }; +/// <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 = 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 the iframe to. + /// 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"); + } + iframe.setAttribute("src", job(iframe, p1)); + iframe.dnoa_internal = window.dnoa_internal; + 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; + }; +}; + +/// <summary>Instantiates an object that represents an OpenID Identifier.</summary> +window.OpenIdIdentifier = function(identifier) { + /// <summary>Performs discovery on the identifier.</summary> + /// <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 successCallback(discoveryResult, identifier) { + 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 DiscoveryResult(identifier, discoveryResult); + discoveryResult = new window.dnoa_internal.DiscoveryResult(identifier, discoveryResult); window.dnoa_internal.discoveryResults[identifier] = discoveryResult; - if (onCompleted) { - onCompleted(discoveryResult); + if (onDiscoverSuccess) { + onDiscoverSuccess(discoveryResult); } }; /// <summary>Receives the discovery failure notification.</summary> - failureCallback = function(message, userSuppliedIdentifier) { + function discoverFailureCallback(message, userSuppliedIdentifier) { trace('Discovery failed for: ' + identifier); - if (onCompleted) { - onCompleted(); + if (onDiscoverFailure) { + onDiscoverFailure(); } }; 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]); + if (onDiscoverSuccess) { + onDiscoverSuccess(window.dnoa_internal.discoveryResults[identifier]); + } + return; } trace('starting discovery on ' + identifier); - window.dnoa_internal.callbackAsync(identifier, successCallback, failureCallback); + window.dnoa_internal.callbackAsync(identifier, discoverSuccessCallback, discoverFailureCallback); }; /// <summary>Performs discovery and immediately begins checkid_setup to authenticate the user using a given identifier.</summary> - this.login = function(onSuccess, onFailure) { + 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) { 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); + discoveryResult.loginBackground(frameManager, onLoginSuccess, onLoginFailure, onLoginFailure, timeout); } else { trace("This doesn't look like an OpenID Identifier. Aborting login."); - if (onFailure) { - onFailure(); + if (onLoginFailure) { + onLoginFailure(); } } } @@ -148,3 +179,314 @@ window.OpenId = function(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) { + //trace('processAuthorizationResult ' + resultUrl); + var resultUri = new window.dnoa_internal.Uri(resultUrl); + + // Find the tracking object responsible for this request. + var userSuppliedIdentifier = resultUri.getQueryArgValue('dnoa.userSuppliedIdentifier'); + if (!userSuppliedIdentifier) { + trace('processAuthorizationResult called but no userSuppliedIdentifier parameter was found. Exiting function.'); + return; + } + var discoveryResult = window.dnoa_internal.discoveryResults[userSuppliedIdentifier]; + if (discoveryResult == null) { + trace('processAuthorizationResult called but no discovery result matching user supplied identifier ' + userSuppliedIdentifier + ' was found. Exiting function.'); + return; + } + + 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); + + if (window.dnoa_internal.isAuthSuccessful(resultUri)) { + discoveryResult.successAuthData = resultUrl; + respondingEndpoint.onAuthSuccess(resultUri); + + 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 + thisDiscoveryResult.onAuthSuccess = onAuthSuccess; + thisDiscoveryResult.onAuthFailed = onAuthFailed; + var width = 800; + 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 = 450; + 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 - height) / 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 && 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(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 (thisDiscoveryResult.onAuthFailed) { + thisDiscoveryResult.onAuthFailed(thisDiscoveryResult, thisServiceEndpoint); + } + window.dnoa_internal.processAuthorizationResult = null; + } + } + }, 250); + }; + + this.loginBackgroundJob = function(iframe, timeout) { + thisServiceEndpoint.abort(); // ensure no concurrent attempts + if (timeout) { + thisServiceEndpoint.timeout = setTimeout(function() { thisServiceEndpoint.onAuthenticationTimedOut(); }, timeout); + } + trace('iframe hosting ' + thisServiceEndpoint.endpoint + ' now OPENING (timeout ' + timeout + ').'); + //trace('initiating auth attempt with: ' + thisServiceEndpoint.immediate); + thisServiceEndpoint.iframe = iframe; + return thisServiceEndpoint.immediate.toString(); + }; + + this.busy = function() { + return thisServiceEndpoint.iframe != null || thisServiceEndpoint.popup != null; + }; + + this.completeAttempt = function(successful) { + if (!thisServiceEndpoint.busy()) return false; + window.clearInterval(thisServiceEndpoint.timeout); + 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() == null) { + if (thisDiscoveryResult.onLastAttemptFailed) { + thisDiscoveryResult.onLastAttemptFailed(); + } + } + + return true; + }; + + this.onAuthenticationTimedOut = function() { + if (thisServiceEndpoint.completeAttempt()) { + trace(thisServiceEndpoint.host + " timed out"); + thisServiceEndpoint.result = window.dnoa_internal.timedOut; + } + }; + + this.onAuthSuccess = function(authUri) { + if (thisServiceEndpoint.completeAttempt(true)) { + trace(thisServiceEndpoint.host + " authenticated!"); + thisServiceEndpoint.result = window.dnoa_internal.authSuccess; + thisServiceEndpoint.claimedIdentifier = authUri////////////////////////////////// + thisServiceEndpoint.response = authUri; + thisDiscoveryResult.abortAll(); + if (thisDiscoveryResult.onAuthSuccess) { + thisDiscoveryResult.onAuthSuccess(thisDiscoveryResult, thisServiceEndpoint); + } + } + }; + + this.onAuthFailed = function() { + if (thisServiceEndpoint.completeAttempt()) { + trace(thisServiceEndpoint.host + " failed authentication"); + thisServiceEndpoint.result = window.dnoa_internal.authRefused; + if (thisDiscoveryResult.onAuthFailed) { + thisDiscoveryResult.onAuthFailed(thisDiscoveryResult, thisServiceEndpoint); + } + } + }; + + this.abort = function() { + if (thisServiceEndpoint.completeAttempt()) { + trace(thisServiceEndpoint.host + " aborted"); + // leave the result as whatever it was before. + } + }; + + }; + + this.userSuppliedIdentifier = identifier; + + 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; + } + + 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. + 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."; + } + if (thisDiscoveryResult.findSuccessfulRequest() != null) { + onAuthSuccess(thisDiscoveryResult, thisDiscoveryResult.findSuccessfulRequest()); + } else { + thisDiscoveryResult.frameManager = frameManager; + thisDiscoveryResult.onAuthSuccess = onAuthSuccess; + thisDiscoveryResult.onAuthFailed = onAuthFailed; + thisDiscoveryResult.onLastAttemptFailed = onLastAuthFailed; + if (thisDiscoveryResult.length > 0) { + for (var i = 0; i < thisDiscoveryResult.length; i++) { + thisDiscoveryResult.frameManager.enqueueWork(thisDiscoveryResult[i].loginBackgroundJob, timeout); + } + } + } + }; +}; + +/// <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> +/// <param name="onAuthSuccess">Fired if the positive assertion is successfully processed, as if it had just come in.</param> +window.dnoa_internal.deserializePreviousAuthentication = function(positiveAssertion, onAuthSuccess) { + if (!positiveAssertion || positiveAssertion.length === 0) { + return; + } + + trace('Revitalizing an old positive assertion from a prior postback.'); + var oldAuthResult = new window.dnoa_internal.Uri(positiveAssertion); + + // 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 }] + }; + + window.dnoa_internal.discoveryResults[box.value] = discoveryResult = new window.dnoa_internal.DiscoveryResult(parsedPositiveAssertion.userSuppliedIdentifier, discoveryInfo); + discoveryResult[0].result = window.dnoa_internal.authSuccess; + discoveryResult.successAuthData = positiveAssertion; + + // restore old state from before postback + if (onAuthSuccess) { + onAuthSuccess(discoveryResult, discoveryResult[0]); + } +}; + +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(); }; +}; diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs index 8451dbd..71df3d0 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs @@ -30,12 +30,22 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// A common base class for OpenID Relying Party controls. /// </summary> [DefaultProperty("Identifier"), ValidationProperty("Identifier")] - public abstract class OpenIdRelyingPartyControlBase : Control { + public abstract class OpenIdRelyingPartyControlBase : Control, 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 +65,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 +105,12 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// Default value of <see cref="UsePersistentCookie"/>. /// </summary> - private const bool UsePersistentCookieDefault = false; + private const LoginPersistence UsePersistentCookieDefault = LoginPersistence.Session; + + /// <summary> + /// Default value of <see cref="LoginMode"/>. + /// </summary> + private const LoginSiteNotification LoginModeDefault = LoginSiteNotification.FormsAuthentication; /// <summary> /// The default value for the <see cref="RealmUrl"/> property. @@ -102,6 +142,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { private const string UsePersistentCookieViewStateKey = "UsePersistentCookie"; /// <summary> + /// The viewstate key to use for the <see cref="LoginMode"/> property. + /// </summary> + private const string LoginModeViewStateKey = "LoginMode"; + + /// <summary> /// The viewstate key to use for the <see cref="RealmUrl"/> property. /// </summary> private const string RealmUrlViewStateKey = "RealmUrl"; @@ -128,40 +173,21 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { #endregion - #region Callback parameter names - /// <summary> - /// The callback parameter for use with persisting the <see cref="UsePersistentCookie"/> property. + /// The lifetime of the cookie used to persist the Identifier the user logged in with. /// </summary> - private const string UsePersistentCookieCallbackKey = OpenIdUtilities.CustomParameterPrefix + "UsePersistentCookie"; + private static readonly TimeSpan PersistentIdentifierTimeToLiveDefault = TimeSpan.FromDays(14); /// <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 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 +198,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> @@ -200,6 +225,49 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { #endregion /// <summary> + /// Methods of indicating to the rest of the web site that the user has logged in. + /// </summary> + public enum LoginSiteNotification { + /// <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 LoginPersistence { + /// <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> /// 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> @@ -215,12 +283,18 @@ 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; } } @@ -306,12 +380,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 LoginPersistence UsePersistentCookie { + get { return (LoginPersistence)(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(LoginModeDefault), Category(BehaviorCategory)] + [Description("The way a completed login is communicated to the rest of the web site.")] + public virtual LoginSiteNotification LoginMode { + get { return (LoginSiteNotification)(this.ViewState[LoginModeViewStateKey] ?? LoginModeDefault); } + set { this.ViewState[LoginModeViewStateKey] = 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> @@ -351,11 +435,31 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { internal AssociationPreference AssociationPreference { get; set; } /// <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); + ErrorUtilities.VerifyArgumentNotNull(request, "request"); + if (this.IsPopupAppropriate(request)) { this.ScriptPopupWindow(request); } else { @@ -364,6 +468,29 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <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> + /// 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; + } + } + } + + /// <summary> /// Creates the authentication requests for a given user-supplied Identifier. /// </summary> /// <returns>A sequence of authentication requests, any one of which may be @@ -404,31 +531,31 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { // 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()); - // Inform ourselves in return_to that we're in a popup. - req.AddCallbackArguments(UIPopupCallbackKey, "1"); + req.SetCallbackArgument(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.AddCallbackArguments(PopupUISupportedJsHint, "1"); + req.SetCallbackArgument(PopupUISupportedJsHint, "1"); } } // 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); + req.SetCallbackArgument(UsePersistentCookieCallbackKey, this.UsePersistentCookie.ToString()); + req.SetCallbackArgument(ReturnToReceivingControlId, this.ClientID); } ((AuthenticationRequest)req).AssociationPreference = this.AssociationPreference; - this.OnLoggingIn(req); - - yield return req; + if (this.OnLoggingIn(req)) { + yield return req; + } } } @@ -444,12 +571,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 +589,41 @@ 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.GetCallbackArgument(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> + /// Processes the response. + /// </summary> + /// <param name="response">The response.</param> + protected virtual void ProcessResponse(IAuthenticationResponse response) { + if (response == null) { + return; + } + string persistentString = response.GetCallbackArgument(UsePersistentCookieCallbackKey); + if (persistentString != null) { + this.UsePersistentCookie = (LoginPersistence)Enum.Parse(typeof(LoginPersistence), 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); } } @@ -515,7 +654,18 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } if (!args.Cancel) { - FormsAuthentication.RedirectFromLoginPage(response.ClaimedIdentifier, this.UsePersistentCookie); + if (this.UsePersistentCookie == LoginPersistence.SessionAndPersistentIdentifier) { + Page.Response.SetCookie(CreateIdentifierPersistingCookie(response)); + } + + switch (this.LoginMode) { + case LoginSiteNotification.FormsAuthentication: + FormsAuthentication.RedirectFromLoginPage(response.ClaimedIdentifier, this.UsePersistentCookie == LoginPersistence.PersistentAuthentication); + break; + case LoginSiteNotification.None: + default: + break; + } } } @@ -577,8 +727,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. @@ -639,6 +801,57 @@ 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> /// 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> @@ -666,27 +879,30 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { startupScript.AppendLine("window.dnoa_internal = new Object();"); 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..4496445 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.js +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.js @@ -25,11 +25,6 @@ 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(); - /// <summary>Instantiates an object that provides string manipulation services for URIs.</summary> window.dnoa_internal.Uri = function(url) { this.originalUri = url.toString(); diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs index b7c879e..be95ed6 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,48 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { private const string ShowLogoViewStateKey = "ShowLogo"; /// <summary> - /// The viewstate key to use for the <see cref="UsePersistentCookie"/> property. - /// </summary> - private const string UsePersistentCookieViewStateKey = "UsePersistentCookie"; - - /// <summary> /// The viewstate key to use for the <see cref="RequestGender"/> property. /// </summary> private const string RequestGenderViewStateKey = "RequestGender"; /// <summary> - /// The viewstate key to use for the <see cref="ReturnToUrl"/> property. + /// The viewstate key to use for the <see cref="RequestBirthDate"/> property. /// </summary> - private const string ReturnToUrlViewStateKey = "ReturnToUrl"; + private const string RequestBirthDateViewStateKey = "RequestBirthDate"; /// <summary> - /// The viewstate key to use for the <see cref="Stateless"/> property. + /// The viewstate key to use for the <see cref="CssClass"/> property. /// </summary> - private const string StatelessViewStateKey = "Stateless"; + private const string CssClassViewStateKey = "CssClass"; /// <summary> - /// The viewstate key to use for the <see cref="RequestBirthDate"/> property. + /// The viewstate key to use for the <see cref="MaxLength"/> property. /// </summary> - private const string RequestBirthDateViewStateKey = "RequestBirthDate"; + private const string MaxLengthViewStateKey = "MaxLength"; /// <summary> - /// The viewstate key to use for the <see cref="RealmUrl"/> property. + /// The viewstate key to use for the <see cref="Columns"/> property. /// </summary> - private const string RealmUrlViewStateKey = "RealmUrl"; + private const string ColumnsViewStateKey = "Columns"; - #endregion + /// <summary> + /// The viewstate key to use for the <see cref="TabIndex"/> property. + /// </summary> + private const string TabIndexViewStateKey = "TabIndex"; - #region Property defaults + /// <summary> + /// The viewstate key to use for the <see cref="Enabled"/> property. + /// </summary> + private const string EnabledViewStateKey = "Enabled"; /// <summary> - /// The default value for the <see cref="Popup"/> property. + /// The viewstate key to use for the <see cref="Name"/> property. /// </summary> - private const PopupBehavior PopupDefault = PopupBehavior.Never; + private const string NameViewStateKey = "Name"; + + #endregion + + #region Property defaults /// <summary> /// The default value for the <see cref="Columns"/> property. @@ -193,19 +173,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 +203,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 +255,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 @@ -374,102 +282,18 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { [Bindable(true), DefaultValue(""), Category(AppearanceCategory)] [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); - } - - 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. - } 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; - } - } - - /// <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(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; } + get { return this.Identifier != null ? this.Identifier.OriginalString : string.Empty; } + set { this.Identifier = value; } } /// <summary> - /// Gets or sets a value indicating whether stateless mode is used. + /// Gets or sets the form name to use for this input field. /// </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 +301,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 +327,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 +342,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 +358,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 +497,42 @@ 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(); } - } - - /// <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; } - } + #region IPostBackDataHandler Members /// <summary> - /// Gets or sets the height 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 height of the control. The default is <see cref="F:System.Web.UI.WebControls.Unit.Empty"/>. + /// true if the server control's state changes as a result of the postback; otherwise, false. /// </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(); } + bool IPostBackDataHandler.LoadPostData(string postDataKey, NameValueCollection postCollection) { + return this.LoadPostData(postDataKey, postCollection); } /// <summary> - /// Gets or sets the width 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> - /// 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(); } + void IPostBackDataHandler.RaisePostDataChangedEvent() { + this.RaisePostDataChangedEvent(); } - /// <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> /// <returns> - /// The name of the skin to apply to the control. The default is <see cref="F:System.String.Empty"/>. + /// 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> - /// <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. - /// </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; } - - /// <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; + protected override IEnumerable<IAuthenticationRequest> CreateRequests() { + // 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> @@ -1018,156 +540,110 @@ 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.GetCallbackArgument(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. + /// 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="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param> - protected override void OnPreRender(EventArgs e) { - base.OnPreRender(e); - + /// <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) { + Identifier identifier = postCollection[this.Name].Length == 0 ? null : postCollection[this.Name]; + if (identifier != this.Identifier) { + this.Identifier = identifier; + 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)); - } + 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); - // Why are we firing Failed when we're OnSetupRequired? Backward compatibility. - var setupRequired = this.SetupRequired; - if (setupRequired != null) { - setupRequired(this, new OpenIdEventArgs(response)); + foreach (var request in requests) { + if (this.EnableRequestProfile) { + this.AddProfileArgs(request); + } + + yield return request; } } - #endregion - /// <summary> /// Adds extensions to a given authentication request to ask the Provider /// for user profile data. @@ -1176,7 +652,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { private void AddProfileArgs(IAuthenticationRequest request) { ErrorUtilities.VerifyArgumentNotNull(request, "request"); - request.AddExtension(new ClaimsRequest() { + var sreg = new ClaimsRequest() { Nickname = this.RequestNickname, Email = this.RequestEmail, FullName = this.RequestFullName, @@ -1188,75 +664,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); - - return this.Popup == PopupBehavior.Always || this.Request.Provider.IsExtensionSupported<UIRequest>(); - } - - /// <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/PositiveAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs index 69a6eaa..c6ab095 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs @@ -19,17 +19,6 @@ 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> @@ -39,7 +28,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { Contract.Requires(relyingParty != null); ErrorUtilities.VerifyArgumentNotNull(relyingParty, "relyingParty"); - 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 3fd7d20..04a403c 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> //----------------------------------------------------------------------- @@ -16,17 +16,17 @@ 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> private IDictionary<string, string> callbackArguments; /// <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) { + internal PositiveAuthenticationResponseSnapshot(IAuthenticationResponse copyFrom) { ErrorUtilities.VerifyArgumentNotNull(copyFrom, "copyFrom"); this.ClaimedIdentifier = copyFrom.ClaimedIdentifier; diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/ServiceEndpoint.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/ServiceEndpoint.cs index 88264f5..fc9a24a 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/ServiceEndpoint.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/ServiceEndpoint.cs @@ -427,7 +427,7 @@ 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> diff --git a/src/DotNetOpenAuth/OpenId/UriIdentifier.cs b/src/DotNetOpenAuth/OpenId/UriIdentifier.cs index 512200d..d196516 100644 --- a/src/DotNetOpenAuth/OpenId/UriIdentifier.cs +++ b/src/DotNetOpenAuth/OpenId/UriIdentifier.cs @@ -43,7 +43,7 @@ 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) { + : base(uri, requireSslDiscovery) { ErrorUtilities.VerifyNonZeroLength(uri, "uri"); Uri canonicalUri; bool schemePrepended; @@ -70,7 +70,7 @@ 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) { + : base(uri != null ? uri.OriginalString : null, requireSslDiscovery) { ErrorUtilities.VerifyArgumentNotNull(uri, "uri"); if (!TryCanonicalize(new UriBuilder(uri), out uri)) { throw new UriFormatException(); diff --git a/src/DotNetOpenAuth/OpenId/XriIdentifier.cs b/src/DotNetOpenAuth/OpenId/XriIdentifier.cs index c659982..fb49817 100644 --- a/src/DotNetOpenAuth/OpenId/XriIdentifier.cs +++ b/src/DotNetOpenAuth/OpenId/XriIdentifier.cs @@ -74,7 +74,7 @@ namespace DotNetOpenAuth.OpenId { /// only succeed if it can be done entirely using SSL. /// </param> internal XriIdentifier(string xri, bool requireSsl) - : base(requireSsl) { + : base(xri, requireSsl) { Contract.Requires((xri != null && xri.Length > 0) || !string.IsNullOrEmpty(xri)); ErrorUtilities.VerifyFormat(IsValidXri(xri), OpenIdStrings.InvalidXri, xri); Contract.Assume(xri != null); // Proven by IsValidXri 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/Util.cs b/src/DotNetOpenAuth/Util.cs index 1a67966..aeeba88 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,15 @@ 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); + ////Contract.Requires(l != null); // CC: anonymous method can't handle it + ErrorUtilities.VerifyArgumentNotNull(l, "l"); string newLine = Environment.NewLine; - Contract.Assume(newLine != null && newLine.Length > 0); + ////Contract.Assume(newLine != null && newLine.Length > 0); // CC: anonymous method can't handle it StringBuilder sb = new StringBuilder(); if (multiLineElements) { sb.AppendLine("[{"); 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/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 e4604e3..15a2799 100644 --- a/src/version.txt +++ b/src/version.txt @@ -1 +1 @@ -3.2.1 +3.3.0 |