diff options
Diffstat (limited to 'samples')
56 files changed, 2415 insertions, 45 deletions
diff --git a/samples/DotNetOpenAuth.ApplicationBlock/CustomExtensions/AcmeRequest.cs b/samples/DotNetOpenAuth.ApplicationBlock/CustomExtensions/AcmeRequest.cs index 84fdc36..8859c10 100644 --- a/samples/DotNetOpenAuth.ApplicationBlock/CustomExtensions/AcmeRequest.cs +++ b/samples/DotNetOpenAuth.ApplicationBlock/CustomExtensions/AcmeRequest.cs @@ -27,6 +27,8 @@ namespace DotNetOpenAuth.ApplicationBlock.CustomExtensions { get { return Enumerable.Empty<string>(); } } + public bool IsSignedByRemoteParty { get; set; } + #endregion #region IMessage Members diff --git a/samples/DotNetOpenAuth.ApplicationBlock/CustomExtensions/AcmeResponse.cs b/samples/DotNetOpenAuth.ApplicationBlock/CustomExtensions/AcmeResponse.cs index 3fae7d8..1e6748c 100644 --- a/samples/DotNetOpenAuth.ApplicationBlock/CustomExtensions/AcmeResponse.cs +++ b/samples/DotNetOpenAuth.ApplicationBlock/CustomExtensions/AcmeResponse.cs @@ -46,6 +46,8 @@ namespace DotNetOpenAuth.ApplicationBlock.CustomExtensions { get { return Enumerable.Empty<string>(); } } + public bool IsSignedByRemoteParty { get; set; } + #endregion #region IMessage Members diff --git a/samples/DotNetOpenAuth.ApplicationBlock/DotNetOpenAuth.ApplicationBlock.csproj b/samples/DotNetOpenAuth.ApplicationBlock/DotNetOpenAuth.ApplicationBlock.csproj index 976a325..570d91f 100644 --- a/samples/DotNetOpenAuth.ApplicationBlock/DotNetOpenAuth.ApplicationBlock.csproj +++ b/samples/DotNetOpenAuth.ApplicationBlock/DotNetOpenAuth.ApplicationBlock.csproj @@ -59,7 +59,11 @@ <Compile Include="CustomExtensions\AcmeRequest.cs" /> <Compile Include="CustomExtensions\AcmeResponse.cs" /> <Compile Include="GoogleConsumer.cs" /> + <Compile Include="OAuthIdentity.cs" /> + <Compile Include="OAuthPrincipal.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="Provider\AnonymousIdentifierProviderBase.cs" /> + <Compile Include="Provider\AuthenticationRequestExtensions.cs" /> <Compile Include="TwitterConsumer.cs" /> <Compile Include="Util.cs" /> </ItemGroup> diff --git a/samples/DotNetOpenAuth.ApplicationBlock/GoogleConsumer.cs b/samples/DotNetOpenAuth.ApplicationBlock/GoogleConsumer.cs index bcdb477..4d3ce13 100644 --- a/samples/DotNetOpenAuth.ApplicationBlock/GoogleConsumer.cs +++ b/samples/DotNetOpenAuth.ApplicationBlock/GoogleConsumer.cs @@ -268,7 +268,7 @@ namespace DotNetOpenAuth.ApplicationBlock { /// </summary> /// <param name="scope">The scope, which may include one or several Google applications.</param> /// <returns>A space-delimited list of URIs for the requested Google applications.</returns> - private static string GetScopeUri(Applications scope) { + public static string GetScopeUri(Applications scope) { return string.Join(" ", Util.GetIndividualFlags(scope).Select(app => DataScopeUris[(Applications)app]).ToArray()); } } diff --git a/samples/DotNetOpenAuth.ApplicationBlock/OAuthIdentity.cs b/samples/DotNetOpenAuth.ApplicationBlock/OAuthIdentity.cs new file mode 100644 index 0000000..ea9ec0b --- /dev/null +++ b/samples/DotNetOpenAuth.ApplicationBlock/OAuthIdentity.cs @@ -0,0 +1,63 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuthIdentity.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.ApplicationBlock { + using System; + using System.Runtime.InteropServices; + using System.Security.Principal; + + /// <summary> + /// Represents an OAuth consumer that is impersonating a known user on the system. + /// </summary> + [Serializable] + [ComVisible(true)] + internal class OAuthIdentity : IIdentity { + /// <summary> + /// Initializes a new instance of the <see cref="OAuthIdentity"/> class. + /// </summary> + /// <param name="username">The username.</param> + internal OAuthIdentity(string username) { + if (String.IsNullOrEmpty(username)) { + throw new ArgumentNullException("username"); + } + + this.Name = username; + } + + #region IIdentity Members + + /// <summary> + /// Gets the type of authentication used. + /// </summary> + /// <value>The constant "OAuth"</value> + /// <returns> + /// The type of authentication used to identify the user. + /// </returns> + public string AuthenticationType { + get { return "OAuth"; } + } + + /// <summary> + /// Gets a value indicating whether the user has been authenticated. + /// </summary> + /// <value>The value <c>true</c></value> + /// <returns>true if the user was authenticated; otherwise, false. + /// </returns> + public bool IsAuthenticated { + get { return true; } + } + + /// <summary> + /// Gets the name of the user who authorized the OAuth token the consumer is using for authorization. + /// </summary> + /// <returns> + /// The name of the user on whose behalf the code is running. + /// </returns> + public string Name { get; private set; } + + #endregion + } +} diff --git a/samples/DotNetOpenAuth.ApplicationBlock/OAuthPrincipal.cs b/samples/DotNetOpenAuth.ApplicationBlock/OAuthPrincipal.cs new file mode 100644 index 0000000..88f3b83 --- /dev/null +++ b/samples/DotNetOpenAuth.ApplicationBlock/OAuthPrincipal.cs @@ -0,0 +1,67 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuthPrincipal.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.ApplicationBlock { + using System; + using System.Linq; + using System.Runtime.InteropServices; + using System.Security.Principal; + + /// <summary> + /// Represents an OAuth consumer that is impersonating a known user on the system. + /// </summary> + [Serializable] + [ComVisible(true)] + internal class OAuthPrincipal : IPrincipal { + /// <summary> + /// The roles this user belongs to. + /// </summary> + private string[] roles; + + /// <summary> + /// Initializes a new instance of the <see cref="OAuthPrincipal"/> class. + /// </summary> + /// <param name="identity">The identity.</param> + /// <param name="roles">The roles this user belongs to.</param> + internal OAuthPrincipal(OAuthIdentity identity, string[] roles) { + this.Identity = identity; + this.roles = roles; + } + + /// <summary> + /// Initializes a new instance of the <see cref="OAuthPrincipal"/> class. + /// </summary> + /// <param name="username">The username.</param> + /// <param name="roles">The roles this user belongs to.</param> + internal OAuthPrincipal(string username, string[] roles) + : this(new OAuthIdentity(username), roles) { + } + + #region IPrincipal Members + + /// <summary> + /// Gets the identity of the current principal. + /// </summary> + /// <value></value> + /// <returns> + /// The <see cref="T:System.Security.Principal.IIdentity"/> object associated with the current principal. + /// </returns> + public IIdentity Identity { get; private set; } + + /// <summary> + /// Determines whether the current principal belongs to the specified role. + /// </summary> + /// <param name="role">The name of the role for which to check membership.</param> + /// <returns> + /// true if the current principal is a member of the specified role; otherwise, false. + /// </returns> + public bool IsInRole(string role) { + return this.roles.Contains(role); + } + + #endregion + } +} diff --git a/samples/DotNetOpenAuth.ApplicationBlock/Provider/AnonymousIdentifierProviderBase.cs b/samples/DotNetOpenAuth.ApplicationBlock/Provider/AnonymousIdentifierProviderBase.cs new file mode 100644 index 0000000..1df7267 --- /dev/null +++ b/samples/DotNetOpenAuth.ApplicationBlock/Provider/AnonymousIdentifierProviderBase.cs @@ -0,0 +1,122 @@ +//----------------------------------------------------------------------- +// <copyright file="AnonymousIdentifierProviderBase.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.ApplicationBlock.Provider { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using System.Security.Cryptography; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId; + + public abstract class AnonymousIdentifierProviderBase { + private int newSaltLength = 20; + + /// <summary> + /// Initializes a new instance of the <see cref="AnonymousIdentifierProviderBase"/> class. + /// </summary> + /// <param name="baseIdentifier">The base URI on which to append the anonymous part.</param> + public AnonymousIdentifierProviderBase(Uri baseIdentifier) { + if (baseIdentifier == null) { + throw new ArgumentNullException("baseIdentifier"); + } + + this.Hasher = HashAlgorithm.Create("SHA256"); + this.Encoder = Encoding.UTF8; + this.BaseIdentifier = baseIdentifier; + } + + public Uri BaseIdentifier { get; private set; } + + protected HashAlgorithm Hasher { get; private set; } + + protected Encoding Encoder { get; private set; } + + protected int NewSaltLength { + get { + return this.newSaltLength; + } + + set { + if (value <= 0) { + throw new ArgumentOutOfRangeException("value"); + } + + this.newSaltLength = value; + } + } + + #region IAnonymousIdentifierProvider Members + + public Uri GetAnonymousIdentifier(Identifier localIdentifier, Realm relyingPartyRealm) { + byte[] salt = this.GetHashSaltForLocalIdentifier(localIdentifier); + string valueToHash = localIdentifier + "#" + (relyingPartyRealm ?? string.Empty); + byte[] valueAsBytes = this.Encoder.GetBytes(valueToHash); + byte[] bytesToHash = new byte[valueAsBytes.Length + salt.Length]; + valueAsBytes.CopyTo(bytesToHash, 0); + salt.CopyTo(bytesToHash, valueAsBytes.Length); + byte[] hash = this.Hasher.ComputeHash(bytesToHash); + string base64Hash = Convert.ToBase64String(hash); + Uri anonymousIdentifier = this.AppendIdentifiers(this.BaseIdentifier, base64Hash); + return anonymousIdentifier; + } + + #endregion + + protected virtual byte[] GetNewSalt() { + // We COULD use a crypto random function, but for a salt it seems overkill. + return Util.GetNonCryptoRandomData(this.NewSaltLength); + } + + protected Uri AppendIdentifiers(Uri baseIdentifier, string uriHash) { + if (baseIdentifier == null) { + throw new ArgumentNullException("baseIdentifier"); + } + if (String.IsNullOrEmpty(uriHash)) { + throw new ArgumentNullException("uriHash"); + } + + if (string.IsNullOrEmpty(baseIdentifier.Query)) { + // The uriHash will appear on the path itself. + string pathEncoded = Uri.EscapeUriString(uriHash.Replace('/', '_')); + return new Uri(baseIdentifier, pathEncoded); + } else { + // The uriHash will appear on the query string. + string dataEncoded = Uri.EscapeDataString(uriHash); + return new Uri(baseIdentifier + dataEncoded); + } + } + + /// <summary> + /// Gets the salt to use for generating an anonymous identifier for a given OP local identifier. + /// </summary> + /// <param name="localIdentifier">The OP local identifier.</param> + /// <returns>The salt to use in the hash.</returns> + /// <remarks> + /// It is important that this method always return the same value for a given + /// <paramref name="localIdentifier"/>. + /// New salts can be generated for local identifiers without previously assigned salt + /// values by calling <see cref="GetNewSalt"/> or by a custom method. + /// </remarks> + protected abstract byte[] GetHashSaltForLocalIdentifier(Identifier localIdentifier); + +#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.Hasher != null); + Contract.Invariant(this.Encoder != null); + Contract.Invariant(this.BaseIdentifier != null); + Contract.Invariant(this.NewHashLength > 0); + } +#endif + } +} diff --git a/samples/DotNetOpenAuth.ApplicationBlock/Provider/AuthenticationRequestExtensions.cs b/samples/DotNetOpenAuth.ApplicationBlock/Provider/AuthenticationRequestExtensions.cs new file mode 100644 index 0000000..a737d30 --- /dev/null +++ b/samples/DotNetOpenAuth.ApplicationBlock/Provider/AuthenticationRequestExtensions.cs @@ -0,0 +1,38 @@ +namespace DotNetOpenAuth.ApplicationBlock.Provider { + using System; + using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.OpenId.Provider; + + public static class AuthenticationRequestExtensions { + /// <summary> + /// Removes all personally identifiable information from the positive assertion. + /// </summary> + /// <param name="request">The incoming authentication request.</param> + /// <param name="localIdentifier">The OP local identifier, before the anonymous hash is applied to it.</param> + /// <param name="anonymousIdentifierProvider">The anonymous identifier provider.</param> + /// <param name="pairwiseUnique">if set to <c>true</c> the anonymous identifier will be unique to the requesting relying party's realm.</param> + /// <remarks> + /// The openid.claimed_id and openid.identity values are hashed. + /// </remarks> + public static void ScrubPersonallyIdentifiableInformation(this IAuthenticationRequest request, Identifier localIdentifier, AnonymousIdentifierProviderBase anonymousIdentifierProvider, bool pairwiseUnique) { + if (request == null) { + throw new ArgumentNullException("request"); + } + if (!request.IsDirectedIdentity) { + throw new InvalidOperationException("This operation is supported only under identifier select (directed identity) scenarios."); + } + if (anonymousIdentifierProvider == null) { + throw new ArgumentNullException("anonymousIdentifierProvider"); + } + if (localIdentifier == null) { + throw new ArgumentNullException("localIdentifier"); + } + + // When generating the anonymous identifiers, the openid.identity and openid.claimed_id + // will always end up with matching values. + var anonymousIdentifier = anonymousIdentifierProvider.GetAnonymousIdentifier(localIdentifier, pairwiseUnique ? request.Realm : null); + request.ClaimedIdentifier = anonymousIdentifier; + request.LocalIdentifier = anonymousIdentifier; + } + } +} diff --git a/samples/DotNetOpenAuth.ApplicationBlock/Util.cs b/samples/DotNetOpenAuth.ApplicationBlock/Util.cs index ea7da97..8a188ac 100644 --- a/samples/DotNetOpenAuth.ApplicationBlock/Util.cs +++ b/samples/DotNetOpenAuth.ApplicationBlock/Util.cs @@ -5,6 +5,8 @@ using DotNetOpenAuth.Messaging; internal static class Util { + internal static readonly Random NonCryptoRandomDataGenerator = new Random(); + /// <summary> /// Enumerates through the individual set bits in a flag enum. /// </summary> @@ -28,6 +30,17 @@ } /// <summary> + /// Gets a buffer of random data (not cryptographically strong). + /// </summary> + /// <param name="length">The length of the sequence to generate.</param> + /// <returns>The generated values, which may contain zeros.</returns> + internal static byte[] GetNonCryptoRandomData(int length) { + byte[] buffer = new byte[length]; + NonCryptoRandomDataGenerator.NextBytes(buffer); + return buffer; + } + + /// <summary> /// Copies the contents of one stream to another. /// </summary> /// <param name="copyFrom">The stream to copy from, at the position where copying should begin.</param> diff --git a/samples/OpenIdOfflineProvider/App.config b/samples/OpenIdOfflineProvider/App.config new file mode 100644 index 0000000..cd04b13 --- /dev/null +++ b/samples/OpenIdOfflineProvider/App.config @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8" ?> +<configuration> + <configSections> + <section name="dotNetOpenAuth" type="DotNetOpenAuth.Configuration.DotNetOpenAuthSection, DotNetOpenAuth" requirePermission="false" allowLocation="true"/> + <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" requirePermission="false"/> + </configSections> + <dotNetOpenAuth> + <messaging> + <untrustedWebRequest> + <whitelistHosts> + <!-- since this is a tool designed for local use and will often be used with localhost --> + <add name="localhost" /> + </whitelistHosts> + </untrustedWebRequest> + </messaging> + </dotNetOpenAuth> + <log4net> + <appender name="TextBoxAppender" type="log4net.Appender.TextWriterAppender"> + <immediateFlush value="true" /> + <layout type="log4net.Layout.PatternLayout"> + <conversionPattern value="%-5level %message%newline" /> + </layout> + </appender> + <!-- Setup the root category, add the appenders and set the default level --> + <root> + <level value="Info" /> + <appender-ref ref="TextBoxAppender" /> + </root> + <!-- Specify the level for some specific categories --> + <logger name="DotNetOpenAuth"> + <level value="INFO" /> + </logger> + <logger name="DotNetOpenAuth.OpenId.ChannelElements.SigningBindingElement"> + <level value="WARN" /> + </logger> + </log4net> +</configuration>
\ No newline at end of file diff --git a/samples/OpenIdOfflineProvider/App.xaml b/samples/OpenIdOfflineProvider/App.xaml new file mode 100644 index 0000000..a23f243 --- /dev/null +++ b/samples/OpenIdOfflineProvider/App.xaml @@ -0,0 +1,8 @@ +<Application x:Class="DotNetOpenAuth.OpenIdOfflineProvider.App" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + StartupUri="MainWindow.xaml"> + <Application.Resources> + + </Application.Resources> +</Application> diff --git a/samples/OpenIdOfflineProvider/App.xaml.cs b/samples/OpenIdOfflineProvider/App.xaml.cs new file mode 100644 index 0000000..ed0f25a --- /dev/null +++ b/samples/OpenIdOfflineProvider/App.xaml.cs @@ -0,0 +1,33 @@ +//----------------------------------------------------------------------- +// <copyright file="App.xaml.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenIdOfflineProvider { + using System; + using System.Collections.Generic; + using System.Configuration; + using System.Data; + using System.Linq; + using System.Windows; + using log4net; + using log4net.Core; + + /// <summary> + /// Interaction logic for App.xaml + /// </summary> + public partial class App : Application { + /// <summary> + /// Message logger. + /// </summary> + internal static ILog Logger = log4net.LogManager.GetLogger(typeof(App)); + + /// <summary> + /// Initializes a new instance of the <see cref="App"/> class. + /// </summary> + public App() { + log4net.Config.XmlConfigurator.Configure(); + } + } +} diff --git a/samples/OpenIdOfflineProvider/CheckIdWindow.xaml b/samples/OpenIdOfflineProvider/CheckIdWindow.xaml new file mode 100644 index 0000000..29e5126 --- /dev/null +++ b/samples/OpenIdOfflineProvider/CheckIdWindow.xaml @@ -0,0 +1,84 @@ +<Window x:Class="DotNetOpenAuth.OpenIdOfflineProvider.CheckIdWindow" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + Title="Authentication request" Height="345" Width="379" ShowInTaskbar="False"> + <DockPanel Margin="12"> + <TextBlock DockPanel.Dock="Top" TextWrapping="Wrap">An authentication request has been received. How do you want to proceed?</TextBlock> + <Expander DockPanel.Dock="Top" Header="View request details" IsExpanded="True"> + <Grid> + <Grid.RowDefinitions> + <RowDefinition Height="auto" /> + <RowDefinition Height="auto" /> + <RowDefinition Height="auto" /> + <RowDefinition Height="auto" /> + <RowDefinition Height="auto" /> + </Grid.RowDefinitions> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="auto" /> + <ColumnDefinition /> + </Grid.ColumnDefinitions> + <Label>Immediate?</Label> + <Label Grid.Column="1" Name="immediateModeLabel" Content="Yes" /> + <Label Grid.Column="1" Name="setupModeLabel" Content="No" /> + <Label Grid.Row="1">Realm</Label> + <Label Grid.Row="1" Grid.Column="1" Name="realmLabel" /> + <!--<Label Grid.Row="2">Callback</Label> + <Label Grid.Row="2" Grid.Column="1" Name="callbackLabel" />--> + <Label Grid.Row="3">Discoverable</Label> + <Label Grid.Row="3" Grid.Column="1" Name="discoverableYesLabel" Content="Yes" /> + <Label Grid.Row="3" Grid.Column="1" Name="discoverableNoLabel" Content="No" /> + <!--<Label Grid.Row="4">Shared association?</Label> + <Label Grid.Row="4" Grid.Column="1" Name="sharedAssociationLabel" Content="Yes" /> + <Label Grid.Row="4" Grid.Column="1" Name="privateAssociationLabel" Content="No" />--> + </Grid> + </Expander> + <StackPanel DockPanel.Dock="Bottom" HorizontalAlignment="Right" Orientation="Horizontal" Margin="0,12,0,0"> + <Button Name="sendResponseButton" IsDefault="True" Margin="0,0,4,0" Click="sendResponseButton_Click">Send response</Button> + <Button Name="cancelButton" IsCancel="True">Cancel</Button> + </StackPanel> + + <TabControl Name="tabControl1"> + <TabItem Header="Positive assertion" Name="positiveTab"> + <StackPanel> + <TextBlock TextWrapping="Wrap">You may customize the positive assertion if you wish.</TextBlock> + <Grid> + <Grid.RowDefinitions> + <RowDefinition Height="auto" /> + <RowDefinition Height="auto" /> + <RowDefinition Height="auto" /> + <RowDefinition Height="auto" /> + </Grid.RowDefinitions> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="auto" /> + <ColumnDefinition Width="*" /> + </Grid.ColumnDefinitions> + <Label>Claimed identifier</Label> + <TextBox Grid.Column="1" Name="claimedIdentifierBox" /> + <Label Grid.Row="1">OP Local identifier</Label> + <TextBox Grid.Column="1" Grid.Row="1" Name="localIdentifierBox" /> + <!--<Label Grid.Row="2">Association</Label> + <WrapPanel Grid.Row="2" Grid.Column="1" VerticalAlignment="Center"> + <RadioButton Margin="0,0,12,0" GroupName="AssociationType">Shared</RadioButton> + <RadioButton GroupName="AssociationType">Private</RadioButton> + </WrapPanel>--> + </Grid> + </StackPanel> + </TabItem> + <TabItem Header="Negative assertion" Name="negativeTab"> + <TextBlock TextWrapping="Wrap">There is nothing to customize in a negative assertion.</TextBlock> + </TabItem> + <!--<TabItem Header="Error" Name="errorTab" > + <StackPanel> + <TextBlock TextWrapping="Wrap">What message do you want to send describing the simulated error?</TextBlock> + <TextBox TextWrapping="Wrap" /> + </StackPanel> + </TabItem> + <TabItem Header="Invalid" Name="invalidTab"> + <StackPanel> + <TextBlock TextWrapping="Wrap">This tab is useful for testing a relying party's resiliance to invalid responses.</TextBlock> + <TextBlock TextWrapping="Wrap">But it's not implemented yet. :)</TextBlock> + </StackPanel> + </TabItem>--> + </TabControl> + </DockPanel> +</Window> diff --git a/samples/OpenIdOfflineProvider/CheckIdWindow.xaml.cs b/samples/OpenIdOfflineProvider/CheckIdWindow.xaml.cs new file mode 100644 index 0000000..597f72f --- /dev/null +++ b/samples/OpenIdOfflineProvider/CheckIdWindow.xaml.cs @@ -0,0 +1,91 @@ +//----------------------------------------------------------------------- +// <copyright file="CheckIdWindow.xaml.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenIdOfflineProvider { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using System.Windows; + using System.Windows.Controls; + using System.Windows.Data; + using System.Windows.Documents; + using System.Windows.Input; + using System.Windows.Media; + using System.Windows.Media.Imaging; + using System.Windows.Shapes; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Provider; + + /// <summary> + /// Interaction logic for CheckIdWindow.xaml + /// </summary> + public partial class CheckIdWindow : Window { + /// <summary> + /// Initializes a new instance of the <see cref="CheckIdWindow"/> class. + /// </summary> + /// <param name="provider">The OpenID Provider host.</param> + /// <param name="request">The incoming authentication request.</param> + private CheckIdWindow(HostedProvider provider, IAuthenticationRequest request) { + Contract.Requires(request != null); + + InitializeComponent(); + + // Initialize the window with appropriate values. + this.realmLabel.Content = request.Realm; + this.immediateModeLabel.Visibility = request.Immediate ? Visibility.Visible : Visibility.Collapsed; + this.setupModeLabel.Visibility = request.Immediate ? Visibility.Collapsed : Visibility.Visible; + + bool isRPDiscoverable = request.IsReturnUrlDiscoverable(provider.Provider.Channel.WebRequestHandler); + this.discoverableYesLabel.Visibility = isRPDiscoverable ? Visibility.Visible : Visibility.Collapsed; + this.discoverableNoLabel.Visibility = isRPDiscoverable ? Visibility.Collapsed : Visibility.Visible; + + if (request.IsDirectedIdentity) { + this.claimedIdentifierBox.Text = provider.UserIdentityPageBase.AbsoluteUri; + this.localIdentifierBox.Text = provider.UserIdentityPageBase.AbsoluteUri; + } else { + this.claimedIdentifierBox.Text = request.ClaimedIdentifier; + this.localIdentifierBox.Text = request.LocalIdentifier; + } + } + + /// <summary> + /// Processes an authentication request by a popup window. + /// </summary> + /// <param name="provider">The OpenID Provider host.</param> + /// <param name="request">The incoming authentication request.</param> + internal static void ProcessAuthentication(HostedProvider provider, IAuthenticationRequest request) { + Contract.Requires(provider != null); + Contract.Requires(request != null); + + var window = new CheckIdWindow(provider, request); + bool? result = window.ShowDialog(); + + // If the user pressed Esc or cancel, just send a negative assertion. + if (!result.HasValue || !result.Value) { + request.IsAuthenticated = false; + return; + } + + request.IsAuthenticated = window.tabControl1.SelectedItem == window.positiveTab; + if (request.IsAuthenticated.Value) { + request.ClaimedIdentifier = window.claimedIdentifierBox.Text; + request.LocalIdentifier = window.localIdentifierBox.Text; + } + } + + /// <summary> + /// Handles the Click event of the sendResponseButton control. + /// </summary> + /// <param name="sender">The source of the event.</param> + /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param> + private void sendResponseButton_Click(object sender, RoutedEventArgs e) { + this.DialogResult = true; + Close(); + } + } +} diff --git a/samples/OpenIdOfflineProvider/HostedProvider.cs b/samples/OpenIdOfflineProvider/HostedProvider.cs new file mode 100644 index 0000000..3d50dd9 --- /dev/null +++ b/samples/OpenIdOfflineProvider/HostedProvider.cs @@ -0,0 +1,253 @@ +//----------------------------------------------------------------------- +// <copyright file="HostedProvider.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenIdOfflineProvider { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.IO; + using System.Linq; + using System.Net; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Provider; + using log4net; + + /// <summary> + /// The OpenID Provider host. + /// </summary> + internal class HostedProvider : IDisposable { + /// <summary> + /// The path to the Provider Endpoint. + /// </summary> + private const string ProviderPath = "/provider"; + + /// <summary> + /// The path to the OP Identifier. + /// </summary> + private const string OPIdentifierPath = "/"; + + /// <summary> + /// The URL path with which all user identities must start. + /// </summary> + private const string UserIdentifierPath = "/user/"; + + /// <summary> + /// The <see cref="OpenIdProvider"/> instance that processes incoming requests. + /// </summary> + private OpenIdProvider provider = new OpenIdProvider(new StandardProviderApplicationStore()); + + /// <summary> + /// The HTTP listener that acts as the OpenID Provider socket. + /// </summary> + private HttpHost httpHost; + + /// <summary> + /// Initializes a new instance of the <see cref="HostedProvider"/> class. + /// </summary> + internal HostedProvider() { + } + + /// <summary> + /// Gets a value indicating whether this instance is running. + /// </summary> + /// <value> + /// <c>true</c> if this instance is running; otherwise, <c>false</c>. + /// </value> + internal bool IsRunning { + get { return this.httpHost != null; } + } + + /// <summary> + /// Gets the <see cref="OpenIdProvider"/> instance that processes incoming requests. + /// </summary> + internal OpenIdProvider Provider { + get { return this.provider; } + } + + /// <summary> + /// Gets or sets the delegate that handles authentication requests. + /// </summary> + internal Action<HttpRequestInfo, HttpListenerResponse> ProcessRequest { get; set; } + + /// <summary> + /// Gets the provider endpoint. + /// </summary> + internal Uri ProviderEndpoint { + get { + Contract.Requires(this.IsRunning); + return new Uri(this.httpHost.BaseUri, ProviderPath); + } + } + + /// <summary> + /// Gets the base URI that all user identities must start with. + /// </summary> + internal Uri UserIdentityPageBase { + get { + Contract.Requires(this.IsRunning); + return new Uri(this.httpHost.BaseUri, UserIdentifierPath); + } + } + + /// <summary> + /// Gets the OP identifier. + /// </summary> + internal Uri OPIdentifier { + get { + Contract.Requires(this.IsRunning); + return new Uri(this.httpHost.BaseUri, OPIdentifierPath); + } + } + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() { + this.Dispose(true); + } + + /// <summary> + /// Starts the provider. + /// </summary> + internal void StartProvider() { + Contract.Ensures(this.IsRunning); + this.httpHost = HttpHost.CreateHost(this.RequestHandler); + } + + /// <summary> + /// Stops the provider. + /// </summary> + internal void StopProvider() { + Contract.Ensures(!this.IsRunning); + if (this.httpHost != null) { + this.httpHost.Dispose(); + this.httpHost = null; + } + } + + #region IDisposable Members + + /// <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) { + var host = this.httpHost as IDisposable; + if (host != null) { + host.Dispose(); + } + + this.httpHost = null; + } + } + + #endregion + + /// <summary> + /// Generates HTML for an identity page. + /// </summary> + /// <param name="providerEndpoint">The provider endpoint.</param> + /// <param name="localId">The local id.</param> + /// <returns>The HTML document to return to the RP.</returns> + private static string GenerateHtmlDiscoveryDocument(Uri providerEndpoint, string localId) { + Contract.Requires(providerEndpoint != null); + + const string DelegatedHtmlDiscoveryFormat = @"<html><head> + <link rel=""openid.server"" href=""{0}"" /> + <link rel=""openid.delegate"" href=""{1}"" /> + <link rel=""openid2.provider"" href=""{0}"" /> + <link rel=""openid2.local_id"" href=""{1}"" /> + </head><body></body></html>"; + + const string NonDelegatedHtmlDiscoveryFormat = @"<html><head> + <link rel=""openid.server"" href=""{0}"" /> + <link rel=""openid2.provider"" href=""{0}"" /> + </head><body></body></html>"; + + return string.Format( + localId != null ? DelegatedHtmlDiscoveryFormat : NonDelegatedHtmlDiscoveryFormat, + providerEndpoint.AbsoluteUri, + localId); + } + + /// <summary> + /// Generates the OP Identifier XRDS document. + /// </summary> + /// <param name="providerEndpoint">The provider endpoint.</param> + /// <param name="supportedExtensions">The supported extensions.</param> + /// <returns>The content of the XRDS document.</returns> + private static string GenerateXrdsOPIdentifierDocument(Uri providerEndpoint, IEnumerable<string> supportedExtensions) { + Contract.Requires(providerEndpoint != null); + Contract.Requires(supportedExtensions != null); + + const string OPIdentifierDiscoveryFormat = @"<xrds:XRDS + xmlns:xrds='xri://$xrds' + xmlns:openid='http://openid.net/xmlns/1.0' + xmlns='xri://$xrd*($v*2.0)'> + <XRD> + <Service priority='10'> + <Type>http://specs.openid.net/auth/2.0/server</Type> + {1} + <URI>{0}</URI> + </Service> + </XRD> +</xrds:XRDS>"; + + string extensions = string.Join( + "\n\t\t\t", + supportedExtensions.Select(ext => "<Type>" + ext + "</Type>").ToArray()); + return string.Format( + OPIdentifierDiscoveryFormat, + providerEndpoint.AbsoluteUri, + extensions); + } + + /// <summary> + /// Handles incoming HTTP requests. + /// </summary> + /// <param name="context">The HttpListener context.</param> + private void RequestHandler(HttpListenerContext context) { + Contract.Requires(context != null); + Contract.Requires(context.Response.OutputStream != null); + Contract.Requires(this.ProcessRequest != null); + Stream outputStream = context.Response.OutputStream; + Contract.Assume(outputStream != null); // CC static verification shortcoming. + + UriBuilder providerEndpointBuilder = new UriBuilder(); + providerEndpointBuilder.Scheme = Uri.UriSchemeHttp; + providerEndpointBuilder.Host = "localhost"; + providerEndpointBuilder.Port = context.Request.Url.Port; + providerEndpointBuilder.Path = ProviderPath; + Uri providerEndpoint = providerEndpointBuilder.Uri; + + if (context.Request.Url.AbsolutePath == ProviderPath) { + HttpRequestInfo requestInfo = new HttpRequestInfo(context.Request); + this.ProcessRequest(requestInfo, context.Response); + } else if (context.Request.Url.AbsolutePath.StartsWith(UserIdentifierPath, StringComparison.Ordinal)) { + using (StreamWriter sw = new StreamWriter(outputStream)) { + context.Response.StatusCode = (int)HttpStatusCode.OK; + + string localId = null; // string.Format("http://localhost:{0}/user", context.Request.Url.Port); + string html = GenerateHtmlDiscoveryDocument(providerEndpoint, localId); + sw.WriteLine(html); + } + context.Response.OutputStream.Close(); + } else if (context.Request.Url == this.OPIdentifier) { + context.Response.ContentType = "application/xrds+xml"; + context.Response.StatusCode = (int)HttpStatusCode.OK; + App.Logger.Info("Discovery on OP Identifier detected."); + using (StreamWriter sw = new StreamWriter(outputStream)) { + sw.Write(GenerateXrdsOPIdentifierDocument(providerEndpoint, Enumerable.Empty<string>())); + } + context.Response.OutputStream.Close(); + } else { + context.Response.StatusCode = (int)HttpStatusCode.NotFound; + context.Response.OutputStream.Close(); + } + } + } +} diff --git a/samples/OpenIdOfflineProvider/HttpHost.cs b/samples/OpenIdOfflineProvider/HttpHost.cs new file mode 100644 index 0000000..390275a --- /dev/null +++ b/samples/OpenIdOfflineProvider/HttpHost.cs @@ -0,0 +1,135 @@ +//----------------------------------------------------------------------- +// <copyright file="HttpHost.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenIdOfflineProvider { + using System; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.IO; + using System.Net; + using System.Threading; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Provider; + + /// <summary> + /// An HTTP Listener that dispatches incoming requests for handling. + /// </summary> + internal class HttpHost : IDisposable { + /// <summary> + /// The HttpListener that waits for incoming requests. + /// </summary> + private readonly HttpListener listener; + + /// <summary> + /// The thread that listens for incoming HTTP requests and dispatches them + /// to the <see cref="handler"/>. + /// </summary> + private Thread listenerThread; + + /// <summary> + /// The handler for incoming HTTP requests. + /// </summary> + private RequestHandler handler; + + /// <summary> + /// Initializes a new instance of the <see cref="HttpHost"/> class. + /// </summary> + /// <param name="handler">The handler for incoming HTTP requests.</param> + private HttpHost(RequestHandler handler) { + Contract.Requires(handler != null); + + this.Port = 45235; + this.handler = handler; + Random r = new Random(); + tryAgain: + try { + this.listener = new HttpListener(); + this.listener.Prefixes.Add(string.Format(CultureInfo.InvariantCulture, "http://localhost:{0}/", this.Port)); + this.listener.Start(); + } catch (HttpListenerException ex) { + if (ex.Message.Contains("conflicts")) { + this.Port += r.Next(1, 20); + goto tryAgain; + } + throw; + } + + this.listenerThread = new Thread(this.ProcessRequests); + this.listenerThread.Start(); + } + + /// <summary> + /// The request handler delegate. + /// </summary> + /// <param name="context">Information on the incoming HTTP request.</param> + internal delegate void RequestHandler(HttpListenerContext context); + + /// <summary> + /// Gets the port that HTTP requests are being listened for on. + /// </summary> + public int Port { get; private set; } + + /// <summary> + /// Gets the base URI for all incoming web requests that will be received. + /// </summary> + public Uri BaseUri { + get { return new Uri("http://localhost:" + this.Port.ToString() + "/"); } + } + + /// <summary> + /// Creates the HTTP host. + /// </summary> + /// <param name="handler">The handler for incoming HTTP requests.</param> + /// <returns>The instantiated host.</returns> + public static HttpHost CreateHost(RequestHandler handler) { + Contract.Requires(handler != null); + Contract.Ensures(Contract.Result<HttpHost>() != null); + + return new HttpHost(handler); + } + + #region IDisposable Members + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool disposing) { + if (disposing) { + this.listener.Close(); + this.listenerThread.Join(1000); + this.listenerThread.Abort(); + } + } + + #endregion + + /// <summary> + /// The HTTP listener thread body. + /// </summary> + private void ProcessRequests() { + Contract.Requires(this.listener != null); + + try { + while (true) { + HttpListenerContext context = this.listener.GetContext(); + this.handler(context); + } + } catch (HttpListenerException ex) { + // the listener is probably being shut down + App.Logger.Warn("HTTP listener is closing down.", ex); + } + } + } +} diff --git a/samples/OpenIdOfflineProvider/MainWindow.xaml b/samples/OpenIdOfflineProvider/MainWindow.xaml new file mode 100644 index 0000000..de215ba --- /dev/null +++ b/samples/OpenIdOfflineProvider/MainWindow.xaml @@ -0,0 +1,25 @@ +<Window x:Class="DotNetOpenAuth.OpenIdOfflineProvider.MainWindow" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + Title="DotNetOpenAuth Offline OpenID Provider" Height="289" Width="493"> + <Grid Margin="4"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="*"/> + </Grid.RowDefinitions> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="auto" /> + <ColumnDefinition /> + </Grid.ColumnDefinitions> + <Label Grid.Row="0">OP Identifier</Label> + <Label Grid.Column="1" Grid.Row="0" Name="opIdentifierLabel" ToolTip="Click to copy URI to clipboard" MouseDown="opIdentifierLabel_MouseDown" /> + <Label Grid.Row="1">checkid requests</Label> + <ComboBox Grid.Column="1" Grid.Row="1" Name="checkidRequestList" SelectedIndex="0"> + <ComboBoxItem>Auto respond: Yes</ComboBoxItem> + <ComboBoxItem>Auto respond: No</ComboBoxItem> + <ComboBoxItem>Intercept</ComboBoxItem> + </ComboBox> + <TextBox Height="auto" Margin="0,8,0,0" Grid.Row="2" Grid.ColumnSpan="2" Name="logBox" IsReadOnly="True" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Auto" /> + </Grid> +</Window> diff --git a/samples/OpenIdOfflineProvider/MainWindow.xaml.cs b/samples/OpenIdOfflineProvider/MainWindow.xaml.cs new file mode 100644 index 0000000..f4f88ca --- /dev/null +++ b/samples/OpenIdOfflineProvider/MainWindow.xaml.cs @@ -0,0 +1,168 @@ +//----------------------------------------------------------------------- +// <copyright file="MainWindow.xaml.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenIdOfflineProvider { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Net; + using System.Text; + using System.Windows; + using System.Windows.Controls; + using System.Windows.Data; + using System.Windows.Documents; + using System.Windows.Input; + using System.Windows.Media; + using System.Windows.Media.Imaging; + using System.Windows.Navigation; + using System.Windows.Shapes; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Provider; + using log4net; + using log4net.Appender; + using log4net.Core; + + /// <summary> + /// Interaction logic for MainWindow.xaml + /// </summary> + public partial class MainWindow : Window, IDisposable { + /// <summary> + /// The OpenID Provider host object. + /// </summary> + private HostedProvider hostedProvider = new HostedProvider(); + + /// <summary> + /// The logger the application may use. + /// </summary> + private ILog logger = log4net.LogManager.GetLogger(typeof(MainWindow)); + + /// <summary> + /// Initializes a new instance of the <see cref="MainWindow"/> class. + /// </summary> + public MainWindow() { + this.InitializeComponent(); + this.hostedProvider.ProcessRequest = this.ProcessRequest; + TextWriterAppender boxLogger = log4net.LogManager.GetRepository().GetAppenders().OfType<TextWriterAppender>().FirstOrDefault(a => a.Name == "TextBoxAppender"); + if (boxLogger != null) { + boxLogger.Writer = new TextBoxTextWriter(logBox); + } + + this.startProvider(); + } + + #region IDisposable Members + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() { + this.Dispose(true); + } + + /// <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) { + var host = this.hostedProvider as IDisposable; + if (host != null) { + host.Dispose(); + } + + this.hostedProvider = null; + } + } + + #endregion + + /// <summary> + /// Raises the <see cref="E:Closing"/> event. + /// </summary> + /// <param name="e">The <see cref="System.ComponentModel.CancelEventArgs"/> instance containing the event data.</param> + protected override void OnClosing(System.ComponentModel.CancelEventArgs e) { + this.stopProvider(); + base.OnClosing(e); + } + + /// <summary> + /// Processes an incoming request at the OpenID Provider endpoint. + /// </summary> + /// <param name="requestInfo">The request info.</param> + /// <param name="response">The response.</param> + private void ProcessRequest(HttpRequestInfo requestInfo, HttpListenerResponse response) { + IRequest request = this.hostedProvider.Provider.GetRequest(requestInfo); + if (request == null) { + App.Logger.Error("A request came in that did not carry an OpenID message."); + response.ContentType = "text/html"; + response.StatusCode = (int)HttpStatusCode.BadRequest; + using (StreamWriter sw = new StreamWriter(response.OutputStream)) { + sw.WriteLine("<html><body>This is an OpenID Provider endpoint.</body></html>"); + } + return; + } + + this.Dispatcher.Invoke((Action)delegate { + if (!request.IsResponseReady) { + var authRequest = request as IAuthenticationRequest; + if (authRequest != null) { + switch (checkidRequestList.SelectedIndex) { + case 0: + if (authRequest.IsDirectedIdentity) { + authRequest.ClaimedIdentifier = new Uri(this.hostedProvider.UserIdentityPageBase, "directedidentity"); + authRequest.LocalIdentifier = authRequest.ClaimedIdentifier; + } + authRequest.IsAuthenticated = true; + break; + case 1: + authRequest.IsAuthenticated = false; + break; + case 2: + IntPtr oldForegroundWindow = NativeMethods.GetForegroundWindow(); + bool stoleFocus = NativeMethods.SetForegroundWindow(this); + CheckIdWindow.ProcessAuthentication(this.hostedProvider, authRequest); + if (stoleFocus) { + NativeMethods.SetForegroundWindow(oldForegroundWindow); + } + break; + } + } + } + }); + + this.hostedProvider.Provider.PrepareResponse(request).Send(response); + } + + /// <summary> + /// Starts the provider. + /// </summary> + private void startProvider() { + this.hostedProvider.StartProvider(); + this.opIdentifierLabel.Content = this.hostedProvider.OPIdentifier; + } + + /// <summary> + /// Stops the provider. + /// </summary> + private void stopProvider() { + this.hostedProvider.StopProvider(); + this.opIdentifierLabel.Content = string.Empty; + } + + /// <summary> + /// Handles the MouseDown event of the opIdentifierLabel control. + /// </summary> + /// <param name="sender">The source of the event.</param> + /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param> + private void opIdentifierLabel_MouseDown(object sender, MouseButtonEventArgs e) { + Clipboard.SetText(opIdentifierLabel.Content.ToString()); + } + } +} diff --git a/samples/OpenIdOfflineProvider/NativeMethods.cs b/samples/OpenIdOfflineProvider/NativeMethods.cs new file mode 100644 index 0000000..b512d70 --- /dev/null +++ b/samples/OpenIdOfflineProvider/NativeMethods.cs @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------- +// <copyright file="NativeMethods.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenIdOfflineProvider { + using System; + using System.Runtime.InteropServices; + using System.Windows; + using System.Windows.Interop; + + /// <summary> + /// P/Invoke methods and wrappers. + /// </summary> + internal static class NativeMethods { + /// <summary> + /// Gets the HWND of the current foreground window on the system. + /// </summary> + /// <returns>A handle to the foreground window.</returns> + [DllImport("user32.dll")] + internal static extern IntPtr GetForegroundWindow(); + + /// <summary> + /// Sets the foreground window of the system. + /// </summary> + /// <param name="hWnd">The HWND of the window to set as active.</param> + /// <returns>A value indicating whether the foreground window was actually changed.</returns> + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool SetForegroundWindow(IntPtr hWnd); + + /// <summary> + /// Sets the foreground window of the system. + /// </summary> + /// <param name="window">The window to bring to the foreground.</param> + /// <returns> + /// A value indicating whether the foreground window was actually changed. + /// </returns> + internal static bool SetForegroundWindow(Window window) { + return SetForegroundWindow(new WindowInteropHelper(window).Handle); + } + + /// <summary> + /// Sets the active window of the process. + /// </summary> + /// <param name="window">The window to bring to the foreground.</param> + internal static void SetActiveWindow(Window window) { + SetActiveWindow(new WindowInteropHelper(window).Handle); + } + + /// <summary> + /// Sets the active window of the process. + /// </summary> + /// <param name="hWnd">The HWND of the window to set as active.</param> + /// <returns>The window that was previously active?</returns> + [DllImport("user32.dll")] + private static extern IntPtr SetActiveWindow(IntPtr hWnd); + } +} diff --git a/samples/OpenIdOfflineProvider/OpenIdOfflineProvider.csproj b/samples/OpenIdOfflineProvider/OpenIdOfflineProvider.csproj new file mode 100644 index 0000000..1bb2367 --- /dev/null +++ b/samples/OpenIdOfflineProvider/OpenIdOfflineProvider.csproj @@ -0,0 +1,171 @@ +<?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>{5C65603B-235F-47E6-B536-06385C60DE7F}</ProjectGuid> + <OutputType>WinExe</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>DotNetOpenAuth.OpenIdOfflineProvider</RootNamespace> + <AssemblyName>OpenIdOfflineProvider</AssemblyName> + <TargetFrameworkVersion>v3.5</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> + <WarningLevel>4</WarningLevel> + <UICulture>en-US</UICulture> + <ApplicationIcon>openid.ico</ApplicationIcon> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <CodeContractsEnableRuntimeChecking>False</CodeContractsEnableRuntimeChecking> + <CodeContractsCustomRewriterAssembly> + </CodeContractsCustomRewriterAssembly> + <CodeContractsCustomRewriterClass> + </CodeContractsCustomRewriterClass> + <CodeContractsRuntimeCheckingLevel>Full</CodeContractsRuntimeCheckingLevel> + <CodeContractsRunCodeAnalysis>False</CodeContractsRunCodeAnalysis> + <CodeContractsBuildReferenceAssembly>False</CodeContractsBuildReferenceAssembly> + <CodeContractsNonNullObligations>False</CodeContractsNonNullObligations> + <CodeContractsBoundsObligations>False</CodeContractsBoundsObligations> + <CodeContractsArithmeticObligations>False</CodeContractsArithmeticObligations> + <CodeContractsLibPaths> + </CodeContractsLibPaths> + <CodeContractsPlatformPath> + </CodeContractsPlatformPath> + <CodeContractsExtraAnalysisOptions> + </CodeContractsExtraAnalysisOptions> + <CodeContractsBaseLineFile> + </CodeContractsBaseLineFile> + <CodeContractsUseBaseLine>False</CodeContractsUseBaseLine> + <CodeContractsRunInBackground>True</CodeContractsRunInBackground> + <CodeContractsShowSquigglies>True</CodeContractsShowSquigglies> + <CodeContractsRuntimeOnlyPublicSurface>False</CodeContractsRuntimeOnlyPublicSurface> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Sign)' == 'true' "> + <SignAssembly>true</SignAssembly> + <AssemblyOriginatorKeyFile>..\..\src\official-build-key.pfx</AssemblyOriginatorKeyFile> + <DefineConstants>$(DefineConstants);StrongNameSigned</DefineConstants> + </PropertyGroup> + <ItemGroup> + <Reference Include="log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=1b44e1d426115821, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\..\lib\log4net.dll</HintPath> + </Reference> + <Reference Include="Microsoft.Contracts, Version=1.0.0.0, Culture=neutral, PublicKeyToken=736440c9b414ea16, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\..\lib\Microsoft.Contracts.dll</HintPath> + </Reference> + <Reference Include="System" /> + <Reference Include="System.Core"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> + <Reference Include="System.Web" /> + <Reference Include="System.Xml.Linq"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> + <Reference Include="System.Data.DataSetExtensions"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> + <Reference Include="System.Data" /> + <Reference Include="System.Xml" /> + <Reference Include="UIAutomationProvider"> + <RequiredTargetFramework>3.0</RequiredTargetFramework> + </Reference> + <Reference Include="WindowsBase"> + <RequiredTargetFramework>3.0</RequiredTargetFramework> + </Reference> + <Reference Include="PresentationCore"> + <RequiredTargetFramework>3.0</RequiredTargetFramework> + </Reference> + <Reference Include="PresentationFramework"> + <RequiredTargetFramework>3.0</RequiredTargetFramework> + </Reference> + </ItemGroup> + <ItemGroup> + <ApplicationDefinition Include="App.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </ApplicationDefinition> + <Page Include="CheckIdWindow.xaml"> + <SubType>Designer</SubType> + <Generator>MSBuild:Compile</Generator> + </Page> + <Page Include="MainWindow.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Compile Include="App.xaml.cs"> + <DependentUpon>App.xaml</DependentUpon> + <SubType>Code</SubType> + </Compile> + <Compile Include="MainWindow.xaml.cs"> + <DependentUpon>MainWindow.xaml</DependentUpon> + <SubType>Code</SubType> + </Compile> + </ItemGroup> + <ItemGroup> + <Compile Include="CheckIdWindow.xaml.cs"> + <DependentUpon>CheckIdWindow.xaml</DependentUpon> + </Compile> + <Compile Include="HostedProvider.cs" /> + <Compile Include="HttpHost.cs" /> + <Compile Include="NativeMethods.cs" /> + <Compile Include="Properties\AssemblyInfo.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="Properties\Resources.Designer.cs"> + <AutoGen>True</AutoGen> + <DesignTime>True</DesignTime> + <DependentUpon>Resources.resx</DependentUpon> + </Compile> + <Compile Include="Properties\Settings.Designer.cs"> + <AutoGen>True</AutoGen> + <DependentUpon>Settings.settings</DependentUpon> + <DesignTimeSharedInput>True</DesignTimeSharedInput> + </Compile> + <Compile Include="TextBoxTextWriter.cs" /> + <EmbeddedResource Include="Properties\Resources.resx"> + <Generator>ResXFileCodeGenerator</Generator> + <LastGenOutput>Resources.Designer.cs</LastGenOutput> + </EmbeddedResource> + <None Include="App.config" /> + <None Include="Properties\Settings.settings"> + <Generator>SettingsSingleFileGenerator</Generator> + <LastGenOutput>Settings.Designer.cs</LastGenOutput> + </None> + <AppDesigner Include="Properties\" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\src\DotNetOpenAuth\DotNetOpenAuth.csproj"> + <Project>{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}</Project> + <Name>DotNetOpenAuth</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <Resource Include="openid.ico" /> + </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/samples/OpenIdOfflineProvider/Properties/AssemblyInfo.cs b/samples/OpenIdOfflineProvider/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..adaded3 --- /dev/null +++ b/samples/OpenIdOfflineProvider/Properties/AssemblyInfo.cs @@ -0,0 +1,51 @@ +//----------------------------------------------------------------------- +// <copyright file="AssemblyInfo.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// 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("OpenIdOfflineProvider")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Andrew Arnott")] +[assembly: AssemblyProduct("DotNetOpenAuth Offline OpenID Provider")] +[assembly: AssemblyCopyright("Copyright © Andrew Arnott 2009")] +[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)] + +[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, // where theme specific resource dictionaries are located + // (used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly)] // where the generic resource dictionary is located + // (used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) + +// 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/samples/OpenIdOfflineProvider/Properties/Resources.Designer.cs b/samples/OpenIdOfflineProvider/Properties/Resources.Designer.cs new file mode 100644 index 0000000..ac28082 --- /dev/null +++ b/samples/OpenIdOfflineProvider/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:2.0.50727.4912 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace DotNetOpenAuth.OpenIdOfflineProvider.Properties { + 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 Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// <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.OpenIdOfflineProvider.Properties.Resources", typeof(Resources).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; + } + } + } +} diff --git a/samples/OpenIdOfflineProvider/Properties/Resources.resx b/samples/OpenIdOfflineProvider/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/samples/OpenIdOfflineProvider/Properties/Resources.resx @@ -0,0 +1,117 @@ +<?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.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: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" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + </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" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + </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> +</root>
\ No newline at end of file diff --git a/samples/OpenIdOfflineProvider/Properties/Settings.Designer.cs b/samples/OpenIdOfflineProvider/Properties/Settings.Designer.cs new file mode 100644 index 0000000..014a460 --- /dev/null +++ b/samples/OpenIdOfflineProvider/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:2.0.50727.4912 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace DotNetOpenAuth.OpenIdOfflineProvider.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "9.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/samples/OpenIdOfflineProvider/Properties/Settings.settings b/samples/OpenIdOfflineProvider/Properties/Settings.settings new file mode 100644 index 0000000..033d7a5 --- /dev/null +++ b/samples/OpenIdOfflineProvider/Properties/Settings.settings @@ -0,0 +1,7 @@ +<?xml version='1.0' encoding='utf-8'?> +<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)"> + <Profiles> + <Profile Name="(Default)" /> + </Profiles> + <Settings /> +</SettingsFile>
\ No newline at end of file diff --git a/samples/OpenIdOfflineProvider/Settings.StyleCop b/samples/OpenIdOfflineProvider/Settings.StyleCop new file mode 100644 index 0000000..0d69b34 --- /dev/null +++ b/samples/OpenIdOfflineProvider/Settings.StyleCop @@ -0,0 +1,19 @@ +<StyleCopSettings Version="4.3"> + <Analyzers> + <Analyzer AnalyzerId="Microsoft.StyleCop.CSharp.DocumentationRules"> + <Rules> + <Rule Name="FileMustHaveHeader"> + <RuleSettings> + <BooleanProperty Name="Enabled">True</BooleanProperty> + </RuleSettings> + </Rule> + <Rule Name="ElementsMustBeDocumented"> + <RuleSettings> + <BooleanProperty Name="Enabled">True</BooleanProperty> + </RuleSettings> + </Rule> + </Rules> + <AnalyzerSettings /> + </Analyzer> + </Analyzers> +</StyleCopSettings>
\ No newline at end of file diff --git a/samples/OpenIdOfflineProvider/TextBoxTextWriter.cs b/samples/OpenIdOfflineProvider/TextBoxTextWriter.cs new file mode 100644 index 0000000..8118986 --- /dev/null +++ b/samples/OpenIdOfflineProvider/TextBoxTextWriter.cs @@ -0,0 +1,87 @@ +//----------------------------------------------------------------------- +// <copyright file="TextBoxTextWriter.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenIdOfflineProvider { + using System; + using System.Diagnostics.Contracts; + using System.IO; + using System.Text; + using System.Windows.Controls; + + /// <summary> + /// A text writer that appends all write calls to a text box. + /// </summary> + internal class TextBoxTextWriter : TextWriter { + /// <summary> + /// Initializes a new instance of the <see cref="TextBoxTextWriter"/> class. + /// </summary> + /// <param name="box">The text box to append log messages to.</param> + internal TextBoxTextWriter(TextBox box) { + Contract.Requires(box != null); + this.Box = box; + } + + /// <summary> + /// Gets the <see cref="T:System.Text.Encoding"/> in which the output is written. + /// </summary> + /// <returns> + /// The Encoding in which the output is written. + /// </returns> + public override Encoding Encoding { + get { return Encoding.Unicode; } + } + + /// <summary> + /// Gets the box to append to. + /// </summary> + internal TextBox Box { get; private set; } + + /// <summary> + /// Writes a character to the text stream. + /// </summary> + /// <param name="value">The character to write to the text stream.</param> + /// <exception cref="T:System.ObjectDisposedException"> + /// The <see cref="T:System.IO.TextWriter"/> is closed. + /// </exception> + /// <exception cref="T:System.IO.IOException"> + /// An I/O error occurs. + /// </exception> + public override void Write(char value) { + this.Box.Dispatcher.BeginInvoke((Action<string>)this.AppendText, value.ToString()); + } + + /// <summary> + /// Writes a string to the text stream. + /// </summary> + /// <param name="value">The string to write.</param> + /// <exception cref="T:System.ObjectDisposedException"> + /// The <see cref="T:System.IO.TextWriter"/> is closed. + /// </exception> + /// <exception cref="T:System.IO.IOException"> + /// An I/O error occurs. + /// </exception> + public override void Write(string value) { + this.Box.Dispatcher.BeginInvoke((Action<string>)this.AppendText, value); + } + + /// <summary> + /// Verifies conditions that should be true for any valid state of this object. + /// </summary> + [ContractInvariantMethod] + protected void ObjectInvariant() { + Contract.Invariant(this.Box != null); + } + + /// <summary> + /// Appends text to the text box. + /// </summary> + /// <param name="value">The string to append.</param> + private void AppendText(string value) { + this.Box.AppendText(value); + this.Box.ScrollToEnd(); + } + } +} diff --git a/samples/OpenIdOfflineProvider/openid.ico b/samples/OpenIdOfflineProvider/openid.ico Binary files differnew file mode 100644 index 0000000..651aeba --- /dev/null +++ b/samples/OpenIdOfflineProvider/openid.ico diff --git a/samples/OpenIdProviderMvc/App_Data/Users.xml b/samples/OpenIdProviderMvc/App_Data/Users.xml index cffe009..7f70bc6 100644 --- a/samples/OpenIdProviderMvc/App_Data/Users.xml +++ b/samples/OpenIdProviderMvc/App_Data/Users.xml @@ -3,21 +3,26 @@ <User> <UserName>bob</UserName> <Password>test</Password> + <Salt>CDI=</Salt> </User> <User> <UserName>bob1</UserName> <Password>test</Password> + <Salt>CAI=</Salt> </User> <User> <UserName>bob2</UserName> <Password>test</Password> + <Salt>hYMC</Salt> </User> <User> <UserName>bob3</UserName> <Password>test</Password> + <Salt>hTKDAg==</Salt> </User> <User> <UserName>bob4</UserName> <Password>test</Password> + <Salt>hTkDAg==</Salt> </User> </Users> diff --git a/samples/OpenIdProviderMvc/Code/AnonymousIdentifierProvider.cs b/samples/OpenIdProviderMvc/Code/AnonymousIdentifierProvider.cs new file mode 100644 index 0000000..2b9e01c --- /dev/null +++ b/samples/OpenIdProviderMvc/Code/AnonymousIdentifierProvider.cs @@ -0,0 +1,23 @@ +namespace OpenIdProviderMvc.Code { + using System; + using System.Web.Security; + using DotNetOpenAuth.ApplicationBlock.Provider; + using DotNetOpenAuth.OpenId; + using OpenIdProviderMvc.Models; + + internal class AnonymousIdentifierProvider : AnonymousIdentifierProviderBase { + internal AnonymousIdentifierProvider() + : base(Util.GetAppPathRootedUri("anon?id=")) { + } + + protected override byte[] GetHashSaltForLocalIdentifier(Identifier localIdentifier) { + // This is just a sample with no database... a real web app MUST return + // a reasonable salt here and have that salt be persistent for each user. + var membership = (ReadOnlyXmlMembershipProvider)Membership.Provider; + string username = User.GetUserFromClaimedIdentifier(new Uri(localIdentifier)); + string salt = membership.GetSalt(username); + return Convert.FromBase64String(salt); + ////return AnonymousIdentifierProviderBase.GetNewSalt(5); + } + } +} diff --git a/samples/OpenIdProviderMvc/Code/ReadOnlyXmlMembershipProvider.cs b/samples/OpenIdProviderMvc/Code/ReadOnlyXmlMembershipProvider.cs index 3da0f8e..cc5a321 100644 --- a/samples/OpenIdProviderMvc/Code/ReadOnlyXmlMembershipProvider.cs +++ b/samples/OpenIdProviderMvc/Code/ReadOnlyXmlMembershipProvider.cs @@ -236,6 +236,11 @@ throw new NotSupportedException(); } + internal string GetSalt(string userName) { + this.ReadMembershipDataStore(); + return this.users[userName].Email; + } + // Helper method private void ReadMembershipDataStore() { lock (this) { @@ -246,11 +251,13 @@ XmlNodeList nodes = doc.GetElementsByTagName("User"); foreach (XmlNode node in nodes) { + // Yes, we're misusing some of these fields. A real app would + // have the right fields from a database to use. MembershipUser user = new MembershipUser( Name, // Provider name node["UserName"].InnerText, // Username null, // providerUserKey - null, // Email + node["Salt"].InnerText, // Email string.Empty, // passwordQuestion node["Password"].InnerText, // Comment true, // isApproved diff --git a/samples/OpenIdProviderMvc/Code/Util.cs b/samples/OpenIdProviderMvc/Code/Util.cs new file mode 100644 index 0000000..6623952 --- /dev/null +++ b/samples/OpenIdProviderMvc/Code/Util.cs @@ -0,0 +1,17 @@ +namespace OpenIdProviderMvc.Code { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Web; + + internal static class Util { + internal static Uri GetAppPathRootedUri(string value) { + string appPath = HttpContext.Current.Request.ApplicationPath.ToLowerInvariant(); + if (!appPath.EndsWith("/")) { + appPath += "/"; + } + + return new Uri(HttpContext.Current.Request.Url, appPath + value); + } + } +} diff --git a/samples/OpenIdProviderMvc/Controllers/HomeController.cs b/samples/OpenIdProviderMvc/Controllers/HomeController.cs index 5ba08b3..346e838 100644 --- a/samples/OpenIdProviderMvc/Controllers/HomeController.cs +++ b/samples/OpenIdProviderMvc/Controllers/HomeController.cs @@ -23,5 +23,9 @@ public ActionResult Xrds() { return View(); } + + public ActionResult PpidXrds() { + return View(); + } } } diff --git a/samples/OpenIdProviderMvc/Controllers/OpenIdController.cs b/samples/OpenIdProviderMvc/Controllers/OpenIdController.cs index f75377c..e353268 100644 --- a/samples/OpenIdProviderMvc/Controllers/OpenIdController.cs +++ b/samples/OpenIdProviderMvc/Controllers/OpenIdController.cs @@ -5,8 +5,11 @@ namespace OpenIdProviderMvc.Controllers { using System.Web; using System.Web.Mvc; using System.Web.Mvc.Ajax; + using DotNetOpenAuth.ApplicationBlock.Provider; using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId; using DotNetOpenAuth.OpenId.Provider; + using OpenIdProviderMvc.Code; public class OpenIdController : Controller { internal static OpenIdProvider OpenIdProvider = new OpenIdProvider(); @@ -16,16 +19,68 @@ namespace OpenIdProviderMvc.Controllers { set { ProviderEndpoint.PendingAuthenticationRequest = value; } } + [ValidateInput(false)] + public ActionResult PpidProvider() { + return this.DoProvider(true); + } + + [ValidateInput(false)] public ActionResult Provider() { + return this.DoProvider(false); + } + + [Authorize] + public ActionResult SendAssertion(bool pseudonymous) { + IAuthenticationRequest authReq = PendingAuthenticationRequest; + PendingAuthenticationRequest = null; + if (authReq == null) { + throw new InvalidOperationException(); + } + + Identifier localIdentifier = Models.User.GetClaimedIdentifierForUser(User.Identity.Name); + + if (pseudonymous) { + if (!authReq.IsDirectedIdentity) { + throw new InvalidOperationException("Directed identity is the only supported scenario for anonymous identifiers."); + } + + var anonProvider = new AnonymousIdentifierProvider(); + authReq.ScrubPersonallyIdentifiableInformation(localIdentifier, anonProvider, true); + authReq.IsAuthenticated = true; + } else { + if (authReq.IsDirectedIdentity) { + authReq.LocalIdentifier = localIdentifier; + authReq.ClaimedIdentifier = localIdentifier; + authReq.IsAuthenticated = true; + } else { + if (authReq.LocalIdentifier == localIdentifier) { + authReq.IsAuthenticated = true; + if (!authReq.IsDelegatedIdentifier) { + authReq.ClaimedIdentifier = authReq.LocalIdentifier; + } + } else { + authReq.IsAuthenticated = false; + } + } + + // TODO: Respond to AX/sreg extension requests here. + // We don't want to add these extension responses for anonymous identifiers + // because they could leak information about the user's identity. + } + + return OpenIdProvider.PrepareResponse(authReq).AsActionResult(); + } + + private ActionResult DoProvider(bool pseudonymous) { IRequest request = OpenIdProvider.GetRequest(); if (request != null) { var authRequest = request as IAuthenticationRequest; if (authRequest != null) { PendingAuthenticationRequest = authRequest; if (User.Identity.IsAuthenticated && (authRequest.IsDirectedIdentity || Models.User.GetClaimedIdentifierForUser(User.Identity.Name) == authRequest.LocalIdentifier)) { - return this.SendAssertion(); + return this.SendAssertion(pseudonymous); } else { - return RedirectToAction("LogOn", "Account", new { returnUrl = Url.Action("SendAssertion") }); + return RedirectToAction("LogOn", "Account", new { returnUrl = Url.Action("SendAssertion", new { pseudonymous = pseudonymous }) }); } } @@ -38,30 +93,5 @@ namespace OpenIdProviderMvc.Controllers { return View(); } } - - [Authorize] - public ActionResult SendAssertion() { - IAuthenticationRequest authReq = PendingAuthenticationRequest; - PendingAuthenticationRequest = null; - if (authReq == null) { - throw new InvalidOperationException(); - } - - if (authReq.IsDirectedIdentity) { - authReq.LocalIdentifier = Models.User.GetClaimedIdentifierForUser(User.Identity.Name); - authReq.ClaimedIdentifier = authReq.LocalIdentifier; - authReq.IsAuthenticated = true; - } else { - if (authReq.LocalIdentifier == Models.User.GetClaimedIdentifierForUser(User.Identity.Name)) { - authReq.IsAuthenticated = true; - if (!authReq.IsDelegatedIdentifier) { - authReq.ClaimedIdentifier = authReq.LocalIdentifier; - } - } else { - authReq.IsAuthenticated = false; - } - } - return OpenIdProvider.PrepareResponse(authReq).AsActionResult(); - } } } diff --git a/samples/OpenIdProviderMvc/Controllers/UserController.cs b/samples/OpenIdProviderMvc/Controllers/UserController.cs index 70bea04..c160fce 100644 --- a/samples/OpenIdProviderMvc/Controllers/UserController.cs +++ b/samples/OpenIdProviderMvc/Controllers/UserController.cs @@ -7,6 +7,14 @@ namespace OpenIdProviderMvc.Controllers { using System.Web.Mvc.Ajax; public class UserController : Controller { + public ActionResult PpidIdentity() { + if (Request.AcceptTypes.Contains("application/xrds+xml")) { + return View("PpidXrds"); + } + + return View(); + } + public ActionResult Identity(string id) { var redirect = this.RedirectIfNotNormalizedRequestUri(); if (redirect != null) { @@ -25,6 +33,10 @@ namespace OpenIdProviderMvc.Controllers { return View(); } + public ActionResult PpidXrds() { + return View(); + } + private ActionResult RedirectIfNotNormalizedRequestUri() { Uri normalized = Models.User.GetNormalizedClaimedIdentifier(Request.Url); if (Request.Url != normalized) { diff --git a/samples/OpenIdProviderMvc/Global.asax.cs b/samples/OpenIdProviderMvc/Global.asax.cs index b0d1b60..8c57961 100644 --- a/samples/OpenIdProviderMvc/Global.asax.cs +++ b/samples/OpenIdProviderMvc/Global.asax.cs @@ -22,6 +22,14 @@ "user/{id}/{action}", new { controller = "User", action = "Identity", id = string.Empty }); routes.MapRoute( + "PPID identifiers", + "anon", + new { controller = "User", action = "PpidIdentity", id = string.Empty }); + routes.MapRoute( + "PpidXrds", + "PpidXrds", + new { controller = "Home", action = "PpidXrds" }); // Parameter defaults + routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = string.Empty }); // Parameter defaults diff --git a/samples/OpenIdProviderMvc/Models/User.cs b/samples/OpenIdProviderMvc/Models/User.cs index 577aa05..443c004 100644 --- a/samples/OpenIdProviderMvc/Models/User.cs +++ b/samples/OpenIdProviderMvc/Models/User.cs @@ -5,17 +5,23 @@ using System.Text.RegularExpressions; using System.Web; using System.Web.Routing; + using OpenIdProviderMvc.Code; internal class User { + internal static Uri PpidClaimedIdentifierBaseUri { + get { return Util.GetAppPathRootedUri("anon?id="); } + } + + internal static Uri ClaimedIdentifierBaseUri { + get { return Util.GetAppPathRootedUri("user/"); } + } + internal static Uri GetClaimedIdentifierForUser(string username) { - string appPath = HttpContext.Current.Request.ApplicationPath; - if (!appPath.EndsWith("/")) { - appPath += "/"; + if (String.IsNullOrEmpty(username)) { + throw new ArgumentNullException("username"); } - Uri claimedIdentifier = new Uri( - HttpContext.Current.Request.Url, - appPath + "user/" + username); - return new Uri(claimedIdentifier.AbsoluteUri.ToLowerInvariant()); + + return new Uri(ClaimedIdentifierBaseUri, username.ToLowerInvariant()); } internal static string GetUserFromClaimedIdentifier(Uri claimedIdentifier) { diff --git a/samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj b/samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj index 80e8e64..5caf26d 100644 --- a/samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj +++ b/samples/OpenIdProviderMvc/OpenIdProviderMvc.csproj @@ -65,10 +65,12 @@ </ItemGroup> <ItemGroup> <Compile Include="Code\AccountMembershipService.cs" /> + <Compile Include="Code\AnonymousIdentifierProvider.cs" /> <Compile Include="Code\FormsAuthenticationService.cs" /> <Compile Include="Code\IFormsAuthentication.cs" /> <Compile Include="Code\IMembershipService.cs" /> <Compile Include="Code\ReadOnlyXmlMembershipProvider.cs" /> + <Compile Include="Code\Util.cs" /> <Compile Include="Controllers\AccountController.cs" /> <Compile Include="Controllers\HomeController.cs" /> <Compile Include="Controllers\OpenIdController.cs" /> @@ -90,8 +92,11 @@ <Content Include="Views\Account\ChangePassword.aspx" /> <Content Include="Views\Account\ChangePasswordSuccess.aspx" /> <Content Include="Views\Account\Register.aspx" /> + <Content Include="Views\Home\PpidXrds.aspx" /> <Content Include="Views\Home\Xrds.aspx" /> <Content Include="Views\OpenId\Provider.aspx" /> + <Content Include="Views\User\PpidXrds.aspx" /> + <Content Include="Views\User\PpidIdentity.aspx" /> <Content Include="Views\User\Identity.aspx" /> <Content Include="Views\User\Xrds.aspx" /> <Content Include="Web.config" /> @@ -117,6 +122,10 @@ <Project>{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}</Project> <Name>DotNetOpenAuth</Name> </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.ApplicationBlock\DotNetOpenAuth.ApplicationBlock.csproj"> + <Project>{AA78D112-D889-414B-A7D4-467B34C7B663}</Project> + <Name>DotNetOpenAuth.ApplicationBlock</Name> + </ProjectReference> </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v9.0\WebApplications\Microsoft.WebApplication.targets" /> diff --git a/samples/OpenIdProviderMvc/Views/Home/PpidXrds.aspx b/samples/OpenIdProviderMvc/Views/Home/PpidXrds.aspx new file mode 100644 index 0000000..990a3df --- /dev/null +++ b/samples/OpenIdProviderMvc/Views/Home/PpidXrds.aspx @@ -0,0 +1,18 @@ +<%@ Page Language="C#" AutoEventWireup="true" ContentType="application/xrds+xml" %><?xml version="1.0" encoding="UTF-8"?> +<%-- +This page is a required as part of the service discovery phase of the openid +protocol (step 1). It simply renders the xml for doing service discovery of +server.aspx using the xrds mechanism. +This XRDS doc is discovered via the user.aspx page. +--%> +<xrds:XRDS + xmlns:xrds="xri://$xrds" + xmlns:openid="http://openid.net/xmlns/1.0" + xmlns="xri://$xrd*($v*2.0)"> + <XRD> + <Service priority="10"> + <Type>http://specs.openid.net/auth/2.0/server</Type> + <URI><%=new Uri(Request.Url, Response.ApplyAppPathModifier("~/OpenId/PpidProvider"))%></URI> + </Service> + </XRD> +</xrds:XRDS> diff --git a/samples/OpenIdProviderMvc/Views/Shared/Site.Master b/samples/OpenIdProviderMvc/Views/Shared/Site.Master index 8df2d5f..073908e 100644 --- a/samples/OpenIdProviderMvc/Views/Shared/Site.Master +++ b/samples/OpenIdProviderMvc/Views/Shared/Site.Master @@ -13,7 +13,7 @@ <div class="page"> <div id="header"> <div id="title"> - <h1>My MVC Application</h1> + <h1>OpenID Provider MVC Application</h1> </div> <div id="logindisplay"> <% Html.RenderPartial("LogOnUserControl"); %> diff --git a/samples/OpenIdProviderMvc/Views/User/Identity.aspx b/samples/OpenIdProviderMvc/Views/User/Identity.aspx index 632df43..bb50899 100644 --- a/samples/OpenIdProviderMvc/Views/User/Identity.aspx +++ b/samples/OpenIdProviderMvc/Views/User/Identity.aspx @@ -3,7 +3,7 @@ <%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth.OpenId.Provider" TagPrefix="op" %> <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> - <%=ViewData["username"]%> + <%=Html.Encode(ViewData["username"])%> identity page </asp:Content> <asp:Content runat="server" ContentPlaceHolderID="HeadContent"> @@ -12,7 +12,7 @@ </asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> <h2>This is - <%=ViewData["username"]%>'s OpenID identity page </h2> + <%=Html.Encode(ViewData["username"])%>'s OpenID identity page </h2> <% if (string.Equals(User.Identity.Name, ViewData["username"])) { %> <p>This is <b>your</b> identity page. </p> diff --git a/samples/OpenIdProviderMvc/Views/User/PpidIdentity.aspx b/samples/OpenIdProviderMvc/Views/User/PpidIdentity.aspx new file mode 100644 index 0000000..f33a694 --- /dev/null +++ b/samples/OpenIdProviderMvc/Views/User/PpidIdentity.aspx @@ -0,0 +1,16 @@ +<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %> + +<%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth.OpenId.Provider" + TagPrefix="op" %> +<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> + Identity page +</asp:Content> +<asp:Content runat="server" ContentPlaceHolderID="HeadContent"> + <op:IdentityEndpoint ID="IdentityEndpoint11" runat="server" ProviderEndpointUrl="~/OpenId/PpidProvider" + ProviderVersion="V11" /> + <op:IdentityEndpoint ID="IdentityEndpoint20" runat="server" ProviderEndpointUrl="~/OpenId/PpidProvider" + XrdsUrl="~/User/all/ppidxrds" XrdsAutoAnswer="false" /> +</asp:Content> +<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> + <h2>OpenID identity page </h2> +</asp:Content> diff --git a/samples/OpenIdProviderMvc/Views/User/PpidXrds.aspx b/samples/OpenIdProviderMvc/Views/User/PpidXrds.aspx new file mode 100644 index 0000000..67256bd --- /dev/null +++ b/samples/OpenIdProviderMvc/Views/User/PpidXrds.aspx @@ -0,0 +1,13 @@ +<%@ Page Language="C#" AutoEventWireup="true" ContentType="application/xrds+xml" %><?xml version="1.0" encoding="UTF-8"?> +<XRDS xmlns="xri://$xrds" xmlns:openid="http://openid.net/xmlns/1.0"> + <XRD xmlns="xri://$xrd*($v*2.0)"> + <Service priority="10"> + <Type>http://specs.openid.net/auth/2.0/signon</Type> + <URI><%=new Uri(Request.Url, Response.ApplyAppPathModifier("~/OpenId/PpidProvider"))%></URI> + </Service> + <Service priority="20"> + <Type>http://openid.net/signon/1.0</Type> + <URI><%=new Uri(Request.Url, Response.ApplyAppPathModifier("~/OpenId/PpidProvider"))%></URI> + </Service> + </XRD> +</XRDS> diff --git a/samples/OpenIdRelyingPartyMvc/Controllers/UserController.cs b/samples/OpenIdRelyingPartyMvc/Controllers/UserController.cs index c0478b1..fd22389 100644 --- a/samples/OpenIdRelyingPartyMvc/Controllers/UserController.cs +++ b/samples/OpenIdRelyingPartyMvc/Controllers/UserController.cs @@ -34,6 +34,7 @@ return View("Login"); } + [ValidateInput(false)] public ActionResult Authenticate(string returnUrl) { var response = openid.GetResponse(); if (response == null) { diff --git a/samples/OpenIdRelyingPartyMvc/Views/User/LoginPopup.aspx b/samples/OpenIdRelyingPartyMvc/Views/User/LoginPopup.aspx index 67006fa..e7bc18a 100644 --- a/samples/OpenIdRelyingPartyMvc/Views/User/LoginPopup.aspx +++ b/samples/OpenIdRelyingPartyMvc/Views/User/LoginPopup.aspx @@ -28,7 +28,7 @@ if (box.style.display != 'none') { box.focus(); } - }, + } }); $('#loggedOut').dialog({ @@ -38,15 +38,15 @@ resizable: false, closeOnEscape: true, buttons: { - "Ok": function() { $(this).dialog('close'); }, - }, + "Ok": function() { $(this).dialog('close'); } + } }); $('#loginAction').click(function() { $('#openidlogin').dialog('open'); return false; }); - + $('#logoutAction').click(function() { // TODO: asynchronously log out. document.setClaimedIdentifier(); @@ -120,7 +120,7 @@ box.focus(); } this.lastIdentifierTemplate = identifierTemplate; - } + }; $('#loginButton').click(function() { completeLogin(); diff --git a/samples/OpenIdRelyingPartyWebForms/Code/InMemoryTokenManager.cs b/samples/OpenIdRelyingPartyWebForms/Code/InMemoryTokenManager.cs new file mode 100644 index 0000000..e665cb6 --- /dev/null +++ b/samples/OpenIdRelyingPartyWebForms/Code/InMemoryTokenManager.cs @@ -0,0 +1,87 @@ +//----------------------------------------------------------------------- +// <copyright file="InMemoryTokenManager.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace OpenIdRelyingPartyWebForms.Code { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using DotNetOpenAuth.OAuth.ChannelElements; + using DotNetOpenAuth.OAuth.Messages; + using DotNetOpenAuth.OpenId.Extensions.OAuth; + + public class InMemoryTokenManager : IConsumerTokenManager, IOpenIdOAuthTokenManager { + private Dictionary<string, string> tokensAndSecrets = new Dictionary<string, string>(); + + public InMemoryTokenManager(string consumerKey, string consumerSecret) { + if (String.IsNullOrEmpty(consumerKey)) { + throw new ArgumentNullException("consumerKey"); + } + + this.ConsumerKey = consumerKey; + this.ConsumerSecret = consumerSecret; + } + + public string ConsumerKey { get; private set; } + + public string ConsumerSecret { get; private set; } + + #region ITokenManager Members + + public string GetConsumerSecret(string consumerKey) { + if (consumerKey == this.ConsumerKey) { + return this.ConsumerSecret; + } else { + throw new ArgumentException("Unrecognized consumer key.", "consumerKey"); + } + } + + public string GetTokenSecret(string token) { + return this.tokensAndSecrets[token]; + } + + public void StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response) { + this.tokensAndSecrets[response.Token] = response.TokenSecret; + } + + /// <summary> + /// Checks whether a given request token has already been authorized + /// by some user for use by the Consumer that requested it. + /// </summary> + /// <param name="requestToken">The Consumer's request token.</param> + /// <returns> + /// True if the request token has already been fully authorized by the user + /// who owns the relevant protected resources. False if the token has not yet + /// been authorized, has expired or does not exist. + /// </returns> + public bool IsRequestTokenAuthorized(string requestToken) { + throw new NotImplementedException(); + } + + public void ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken, string accessTokenSecret) { + this.tokensAndSecrets.Remove(requestToken); + this.tokensAndSecrets[accessToken] = accessTokenSecret; + } + + /// <summary> + /// Classifies a token as a request token or an access token. + /// </summary> + /// <param name="token">The token to classify.</param> + /// <returns>Request or Access token, or invalid if the token is not recognized.</returns> + public TokenType GetTokenType(string token) { + throw new NotImplementedException(); + } + + #endregion + + #region IOpenIdOAuthTokenManager Members + + public void StoreOpenIdAuthorizedRequestToken(string consumerKey, AuthorizationApprovedResponse authorization) { + this.tokensAndSecrets[authorization.RequestToken] = string.Empty; + } + + #endregion + } +}
\ No newline at end of file diff --git a/samples/OpenIdRelyingPartyWebForms/Code/State.cs b/samples/OpenIdRelyingPartyWebForms/Code/State.cs index 4861a34..c8147e5 100644 --- a/samples/OpenIdRelyingPartyWebForms/Code/State.cs +++ b/samples/OpenIdRelyingPartyWebForms/Code/State.cs @@ -1,6 +1,6 @@ namespace OpenIdRelyingPartyWebForms { - using System.Collections.Generic; using System.Web; + using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; using DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy; using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; @@ -13,6 +13,11 @@ namespace OpenIdRelyingPartyWebForms { set { HttpContext.Current.Session["ProfileFields"] = value; } } + public static FetchResponse FetchResponse { + get { return HttpContext.Current.Session["FetchResponse"] as FetchResponse; } + set { HttpContext.Current.Session["FetchResponse"] = value; } + } + public static string FriendlyLoginName { get { return HttpContext.Current.Session["FriendlyUsername"] as string; } set { HttpContext.Current.Session["FriendlyUsername"] = value; } @@ -23,10 +28,17 @@ namespace OpenIdRelyingPartyWebForms { set { HttpContext.Current.Session["PapePolicies"] = value; } } + public static string GoogleAccessToken { + get { return HttpContext.Current.Session["GoogleAccessToken"] as string; } + set { HttpContext.Current.Session["GoogleAccessToken"] = value; } + } + public static void Clear() { ProfileFields = null; + FetchResponse = null; FriendlyLoginName = null; PapePolicies = null; + GoogleAccessToken = null; } } }
\ No newline at end of file diff --git a/samples/OpenIdRelyingPartyWebForms/Global.asax.cs b/samples/OpenIdRelyingPartyWebForms/Global.asax.cs index c7d1e8b..ac74853 100644 --- a/samples/OpenIdRelyingPartyWebForms/Global.asax.cs +++ b/samples/OpenIdRelyingPartyWebForms/Global.asax.cs @@ -1,15 +1,47 @@ namespace OpenIdRelyingPartyWebForms { using System; using System.Collections.Specialized; + using System.Configuration; using System.IO; using System.Text; using System.Web; + using DotNetOpenAuth.ApplicationBlock; + using DotNetOpenAuth.OAuth; + using OpenIdRelyingPartyWebForms.Code; public class Global : HttpApplication { public static log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(Global)); internal static StringBuilder LogMessages = new StringBuilder(); + internal static WebConsumer GoogleWebConsumer { + get { + var googleWebConsumer = (WebConsumer)HttpContext.Current.Application["GoogleWebConsumer"]; + if (googleWebConsumer == null) { + googleWebConsumer = new WebConsumer(GoogleConsumer.ServiceDescription, GoogleTokenManager); + HttpContext.Current.Application["GoogleWebConsumer"] = googleWebConsumer; + } + + return googleWebConsumer; + } + } + + internal static InMemoryTokenManager GoogleTokenManager { + get { + var tokenManager = (InMemoryTokenManager)HttpContext.Current.Application["GoogleTokenManager"]; + if (tokenManager == null) { + string consumerKey = ConfigurationManager.AppSettings["googleConsumerKey"]; + string consumerSecret = ConfigurationManager.AppSettings["googleConsumerSecret"]; + if (!string.IsNullOrEmpty(consumerKey)) { + tokenManager = new InMemoryTokenManager(consumerKey, consumerSecret); + HttpContext.Current.Application["GoogleTokenManager"] = tokenManager; + } + } + + return tokenManager; + } + } + public static string ToString(NameValueCollection collection) { using (StringWriter sw = new StringWriter()) { foreach (string key in collection.Keys) { diff --git a/samples/OpenIdRelyingPartyWebForms/MembersOnly/DisplayGoogleContacts.aspx b/samples/OpenIdRelyingPartyWebForms/MembersOnly/DisplayGoogleContacts.aspx new file mode 100644 index 0000000..7d5a54f --- /dev/null +++ b/samples/OpenIdRelyingPartyWebForms/MembersOnly/DisplayGoogleContacts.aspx @@ -0,0 +1,22 @@ +<%@ Page Language="C#" AutoEventWireup="true" MasterPageFile="~/Site.Master" CodeBehind="DisplayGoogleContacts.aspx.cs" + Inherits="OpenIdRelyingPartyWebForms.MembersOnly.DisplayGoogleContacts" %> + +<asp:Content ID="Content1" runat="server" ContentPlaceHolderID="Main"> + <asp:MultiView ID="MultiView1" runat="server" ActiveViewIndex="0"> + <asp:View ID="View1" runat="server"> + <p>Obtain an access token by <asp:HyperLink NavigateUrl="~/loginPlusOAuth.aspx" runat="server" + Text="logging in at our OpenID+OAuth hybrid login page" />. </p> + <p>If you've already done that, then you might have inadvertently clicked "Allow [this + site] to remember me", which causes Google to stop sending the access token that + this sample doesn't save. If you did check it, you can restore this sample's + functionality by <a href="https://www.google.com/accounts/IssuedAuthSubTokens">revoking + access</a> to this site from your Google Account. </p> + </asp:View> + <asp:View ID="View2" runat="server"> + <h2>Address book</h2> + <p>These are the contacts for Google Account: <asp:Label ID="emailLabel" runat="server" + Font-Bold="True" /> and OpenID <asp:Label ID="claimedIdLabel" runat="server" Font-Bold="True" /></p> + <asp:PlaceHolder ID="resultsPlaceholder" runat="server" /> + </asp:View> + </asp:MultiView> +</asp:Content> diff --git a/samples/OpenIdRelyingPartyWebForms/MembersOnly/DisplayGoogleContacts.aspx.cs b/samples/OpenIdRelyingPartyWebForms/MembersOnly/DisplayGoogleContacts.aspx.cs new file mode 100644 index 0000000..b14aba1 --- /dev/null +++ b/samples/OpenIdRelyingPartyWebForms/MembersOnly/DisplayGoogleContacts.aspx.cs @@ -0,0 +1,41 @@ +namespace OpenIdRelyingPartyWebForms.MembersOnly { + using System; + using System.Linq; + using System.Text; + using System.Web; + using System.Web.UI.WebControls; + using System.Xml.Linq; + using DotNetOpenAuth.ApplicationBlock; + using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; + + public partial class DisplayGoogleContacts : System.Web.UI.Page { + protected void Page_Load(object sender, EventArgs e) { + if (!string.IsNullOrEmpty(State.GoogleAccessToken)) { + this.MultiView1.ActiveViewIndex = 1; + if (State.FetchResponse != null && State.FetchResponse.Attributes.Contains(WellKnownAttributes.Contact.Email)) { + this.emailLabel.Text = State.FetchResponse.Attributes[WellKnownAttributes.Contact.Email].Values[0]; + } else { + this.emailLabel.Text = "unavailable"; + } + this.claimedIdLabel.Text = this.User.Identity.Name; + var contactsDocument = GoogleConsumer.GetContacts(Global.GoogleWebConsumer, State.GoogleAccessToken); + this.RenderContacts(contactsDocument); + } + } + + private void RenderContacts(XDocument contactsDocument) { + var contacts = from entry in contactsDocument.Root.Elements(XName.Get("entry", "http://www.w3.org/2005/Atom")) + select new { Name = entry.Element(XName.Get("title", "http://www.w3.org/2005/Atom")).Value, Email = entry.Element(XName.Get("email", "http://schemas.google.com/g/2005")).Attribute("address").Value }; + StringBuilder tableBuilder = new StringBuilder(); + tableBuilder.Append("<table><tr><td>Name</td><td>Email</td></tr>"); + foreach (var contact in contacts) { + tableBuilder.AppendFormat( + "<tr><td>{0}</td><td>{1}</td></tr>", + HttpUtility.HtmlEncode(contact.Name), + HttpUtility.HtmlEncode(contact.Email)); + } + tableBuilder.Append("</table>"); + this.resultsPlaceholder.Controls.Add(new Literal { Text = tableBuilder.ToString() }); + } + } +} diff --git a/samples/OpenIdRelyingPartyWebForms/MembersOnly/DisplayGoogleContacts.aspx.designer.cs b/samples/OpenIdRelyingPartyWebForms/MembersOnly/DisplayGoogleContacts.aspx.designer.cs new file mode 100644 index 0000000..5cc5894 --- /dev/null +++ b/samples/OpenIdRelyingPartyWebForms/MembersOnly/DisplayGoogleContacts.aspx.designer.cs @@ -0,0 +1,70 @@ +//------------------------------------------------------------------------------ +// <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 OpenIdRelyingPartyWebForms.MembersOnly { + + + public partial class DisplayGoogleContacts { + + /// <summary> + /// MultiView1 control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.MultiView MultiView1; + + /// <summary> + /// View1 control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.View View1; + + /// <summary> + /// View2 control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.View View2; + + /// <summary> + /// emailLabel control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.Label emailLabel; + + /// <summary> + /// claimedIdLabel control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.Label claimedIdLabel; + + /// <summary> + /// resultsPlaceholder control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.PlaceHolder resultsPlaceholder; + } +} diff --git a/samples/OpenIdRelyingPartyWebForms/OpenIdRelyingPartyWebForms.csproj b/samples/OpenIdRelyingPartyWebForms/OpenIdRelyingPartyWebForms.csproj index cf40440..c45f007 100644 --- a/samples/OpenIdRelyingPartyWebForms/OpenIdRelyingPartyWebForms.csproj +++ b/samples/OpenIdRelyingPartyWebForms/OpenIdRelyingPartyWebForms.csproj @@ -98,6 +98,7 @@ <DesignTime>True</DesignTime> <DependentUpon>CustomStoreDataSet.xsd</DependentUpon> </Compile> + <Compile Include="Code\InMemoryTokenManager.cs" /> <Compile Include="Code\State.cs" /> <Compile Include="Code\TracePageAppender.cs" /> <Compile Include="Global.asax.cs"> @@ -110,6 +111,13 @@ <Compile Include="login.aspx.designer.cs"> <DependentUpon>login.aspx</DependentUpon> </Compile> + <Compile Include="loginPlusOAuth.aspx.cs"> + <DependentUpon>loginPlusOAuth.aspx</DependentUpon> + <SubType>ASPXCodeBehind</SubType> + </Compile> + <Compile Include="loginPlusOAuth.aspx.designer.cs"> + <DependentUpon>loginPlusOAuth.aspx</DependentUpon> + </Compile> <Compile Include="loginProgrammatic.aspx.cs"> <DependentUpon>loginProgrammatic.aspx</DependentUpon> <SubType>ASPXCodeBehind</SubType> @@ -117,6 +125,13 @@ <Compile Include="loginProgrammatic.aspx.designer.cs"> <DependentUpon>loginProgrammatic.aspx</DependentUpon> </Compile> + <Compile Include="MembersOnly\DisplayGoogleContacts.aspx.cs"> + <DependentUpon>DisplayGoogleContacts.aspx</DependentUpon> + <SubType>ASPXCodeBehind</SubType> + </Compile> + <Compile Include="MembersOnly\DisplayGoogleContacts.aspx.designer.cs"> + <DependentUpon>DisplayGoogleContacts.aspx</DependentUpon> + </Compile> <Compile Include="m\Login.aspx.cs"> <DependentUpon>Login.aspx</DependentUpon> <SubType>ASPXCodeBehind</SubType> @@ -144,6 +159,8 @@ <Content Include="images\dotnetopenid_tiny.gif" /> <Content Include="images\openid_login.gif" /> <Content Include="images\yahoo.png" /> + <Content Include="loginPlusOAuth.aspx" /> + <Content Include="MembersOnly\DisplayGoogleContacts.aspx" /> <Content Include="MembersOnly\Web.config" /> <Content Include="m\Login.aspx" /> </ItemGroup> @@ -165,6 +182,10 @@ <Project>{3191B653-F76D-4C1A-9A5A-347BC3AAAAB7}</Project> <Name>DotNetOpenAuth</Name> </ProjectReference> + <ProjectReference Include="..\DotNetOpenAuth.ApplicationBlock\DotNetOpenAuth.ApplicationBlock.csproj"> + <Project>{AA78D112-D889-414B-A7D4-467B34C7B663}</Project> + <Name>DotNetOpenAuth.ApplicationBlock</Name> + </ProjectReference> </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v9.0\WebApplications\Microsoft.WebApplication.targets" /> diff --git a/samples/OpenIdRelyingPartyWebForms/Web.config b/samples/OpenIdRelyingPartyWebForms/Web.config index a4cb801..7983e21 100644 --- a/samples/OpenIdRelyingPartyWebForms/Web.config +++ b/samples/OpenIdRelyingPartyWebForms/Web.config @@ -43,6 +43,14 @@ </messaging> </dotNetOpenAuth> + <appSettings> + <!-- Fill in your various consumer keys and secrets here to make the sample work. --> + <!-- You must get these values by signing up with each individual service provider. --> + <!-- Google sign-up: https://www.google.com/accounts/ManageDomains --> + <add key="googleConsumerKey" value="demo.dotnetopenauth.net"/> + <add key="googleConsumerSecret" value="5Yv1TfKm1551QrXZ9GpqepeD"/> + </appSettings> + <system.web> <!--<sessionState cookieless="true" />--> <compilation debug="true"/> diff --git a/samples/OpenIdRelyingPartyWebForms/loginPlusOAuth.aspx b/samples/OpenIdRelyingPartyWebForms/loginPlusOAuth.aspx new file mode 100644 index 0000000..57bca52 --- /dev/null +++ b/samples/OpenIdRelyingPartyWebForms/loginPlusOAuth.aspx @@ -0,0 +1,30 @@ +<%@ Page Language="C#" AutoEventWireup="True" CodeBehind="loginPlusOAuth.aspx.cs" + Inherits="OpenIdRelyingPartyWebForms.loginPlusOAuth" ValidateRequest="false" + MasterPageFile="~/Site.Master" %> + +<%@ Register Assembly="DotNetOpenAuth" Namespace="DotNetOpenAuth.OpenId.RelyingParty" + TagPrefix="rp" %> +<asp:Content ID="Content1" runat="server" ContentPlaceHolderID="Main"> + <h2>Login Page </h2> + <asp:MultiView ID="MultiView1" runat="server" ActiveViewIndex='0'> + <asp:View ID="View1" runat="server"> + <p><b>Important note:</b> Do <b>not</b> check the "Allow [this site] to remember me" + check box while Google is asking for verification. Doing so will make this + sample only work once for your account. If you do check it, you can restore this + sample's functionality by <a href="https://www.google.com/accounts/IssuedAuthSubTokens"> + revoking access</a> to this site from your Google Account. </p> + <asp:Button ID="beginButton" runat="server" Text="Login and get Gmail Contacts" OnClick="beginButton_Click" /> + <p>Due to the way Google matches realms and consumer keys, this demo will only work + when it is run under http://demo.dotnetopenauth.net/. By registering your own consumer + key with Google and changing the configuration of this sample, you can run it on + your own public web site, but it can never work from a private (localhost or firewall-protected) + address. </p> + </asp:View> + <asp:View ID="AuthorizationDenied" runat="server"> + Authentication succeeded, but Gmail Contacts access was denied. + </asp:View> + <asp:View ID="AuthenticationFailed" runat="server"> + Authentication failed or was canceled. + </asp:View> + </asp:MultiView> +</asp:Content> diff --git a/samples/OpenIdRelyingPartyWebForms/loginPlusOAuth.aspx.cs b/samples/OpenIdRelyingPartyWebForms/loginPlusOAuth.aspx.cs new file mode 100644 index 0000000..d4e9885 --- /dev/null +++ b/samples/OpenIdRelyingPartyWebForms/loginPlusOAuth.aspx.cs @@ -0,0 +1,71 @@ +namespace OpenIdRelyingPartyWebForms { + using System; + using System.Web.Security; + using DotNetOpenAuth.ApplicationBlock; + using DotNetOpenAuth.OAuth.Messages; + using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; + using DotNetOpenAuth.OpenId.RelyingParty; + + public partial class loginPlusOAuth : System.Web.UI.Page { + private const string GoogleOPIdentifier = "https://www.google.com/accounts/o8/id"; + private static readonly OpenIdRelyingParty relyingParty = new OpenIdRelyingParty(); + + protected void Page_Load(object sender, EventArgs e) { + if (!IsPostBack && string.Equals(Request.Url.Host, "localhost", StringComparison.OrdinalIgnoreCase)) { + // Disable the button since the scenario won't work under localhost, + // and this will help encourage the user to read the the text above the button. + this.beginButton.Enabled = false; + } + + IAuthenticationResponse authResponse = relyingParty.GetResponse(); + if (authResponse != null) { + switch (authResponse.Status) { + case AuthenticationStatus.Authenticated: + State.FetchResponse = authResponse.GetExtension<FetchResponse>(); + AuthorizedTokenResponse accessToken = Global.GoogleWebConsumer.ProcessUserAuthorization(authResponse); + if (accessToken != null) { + State.GoogleAccessToken = accessToken.AccessToken; + FormsAuthentication.SetAuthCookie(authResponse.ClaimedIdentifier, false); + Response.Redirect("~/MembersOnly/DisplayGoogleContacts.aspx"); + } else { + MultiView1.SetActiveView(AuthorizationDenied); + } + break; + case AuthenticationStatus.Canceled: + case AuthenticationStatus.Failed: + default: + this.MultiView1.SetActiveView(this.AuthenticationFailed); + break; + } + } + } + + protected void beginButton_Click(object sender, EventArgs e) { + this.GetGoogleRequest().RedirectToProvider(); + } + + private IAuthenticationRequest GetGoogleRequest() { + // Google requires that the realm and consumer key be equal, + // so we constrain the realm to match the realm in the web.config file. + // This does mean that the return_to URL must also fall under the key, + // which means this sample will only work on a public web site + // that is properly registered with Google. + // We will customize the realm to use http or https based on what the + // return_to URL will be (which will be this page). + Realm realm = Request.Url.Scheme + Uri.SchemeDelimiter + Global.GoogleTokenManager.ConsumerKey + "/"; + IAuthenticationRequest authReq = relyingParty.CreateRequest(GoogleOPIdentifier, realm); + + // Prepare the OAuth extension + string scope = GoogleConsumer.GetScopeUri(GoogleConsumer.Applications.Contacts); + Global.GoogleWebConsumer.AttachAuthorizationRequest(authReq, scope); + + // We also want the user's email address + var fetch = new FetchRequest(); + fetch.Attributes.AddRequired(WellKnownAttributes.Contact.Email); + authReq.AddExtension(fetch); + + return authReq; + } + } +} diff --git a/samples/OpenIdRelyingPartyWebForms/loginPlusOAuth.aspx.designer.cs b/samples/OpenIdRelyingPartyWebForms/loginPlusOAuth.aspx.designer.cs new file mode 100644 index 0000000..b9c836d --- /dev/null +++ b/samples/OpenIdRelyingPartyWebForms/loginPlusOAuth.aspx.designer.cs @@ -0,0 +1,61 @@ +//------------------------------------------------------------------------------ +// <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 OpenIdRelyingPartyWebForms { + + + public partial class loginPlusOAuth { + + /// <summary> + /// MultiView1 control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.MultiView MultiView1; + + /// <summary> + /// View1 control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.View View1; + + /// <summary> + /// beginButton control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.Button beginButton; + + /// <summary> + /// AuthorizationDenied control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.View AuthorizationDenied; + + /// <summary> + /// AuthenticationFailed control. + /// </summary> + /// <remarks> + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// </remarks> + protected global::System.Web.UI.WebControls.View AuthenticationFailed; + } +} |