using System;
using System.Collections.Generic;
using System.Text;
using DotNetOpenId;
using System.Collections.Specialized;
using System.Globalization;
using System.Web;
using System.Diagnostics;
using DotNetOpenId.Extensions;
namespace DotNetOpenId.RelyingParty {
///
/// Indicates the mode the Provider should use while authenticating the end user.
///
public enum AuthenticationRequestMode {
///
/// The Provider should use whatever credentials are immediately available
/// to determine whether the end user owns the Identifier. If sufficient
/// credentials (i.e. cookies) are not immediately available, the Provider
/// should fail rather than prompt the user.
///
Immediate,
///
/// The Provider should determine whether the end user owns the Identifier,
/// displaying a web page to the user to login etc., if necessary.
///
Setup
}
[DebuggerDisplay("ClaimedIdentifier: {ClaimedIdentifier}, Mode: {Mode}, OpenId: {protocol.Version}")]
class AuthenticationRequest : IAuthenticationRequest {
Association assoc;
ServiceEndpoint endpoint;
MessageEncoder encoder;
Protocol protocol { get { return endpoint.Protocol; } }
AuthenticationRequest(string token, Association assoc, ServiceEndpoint endpoint,
Realm realm, Uri returnToUrl, MessageEncoder encoder) {
if (endpoint == null) throw new ArgumentNullException("endpoint");
if (realm == null) throw new ArgumentNullException("realm");
if (returnToUrl == null) throw new ArgumentNullException("returnToUrl");
if (encoder == null) throw new ArgumentNullException("encoder");
this.assoc = assoc;
this.endpoint = endpoint;
this.encoder = encoder;
Realm = realm;
ReturnToUrl = returnToUrl;
Mode = AuthenticationRequestMode.Setup;
OutgoingExtensions = ExtensionArgumentsManager.CreateOutgoingExtensions(endpoint.Protocol);
ReturnToArgs = new Dictionary();
if (token != null)
AddCallbackArguments(DotNetOpenId.RelyingParty.Token.TokenKey, token);
}
internal static AuthenticationRequest Create(Identifier userSuppliedIdentifier,
Realm realm, Uri returnToUrl, IRelyingPartyApplicationStore store, MessageEncoder encoder) {
if (userSuppliedIdentifier == null) throw new ArgumentNullException("userSuppliedIdentifier");
if (realm == null) throw new ArgumentNullException("realm");
Logger.InfoFormat("Creating authentication request for user supplied Identifier: {0}",
userSuppliedIdentifier);
Logger.DebugFormat("Realm: {0}", realm);
Logger.DebugFormat("Return To: {0}", returnToUrl);
if (Logger.IsWarnEnabled && returnToUrl.Query != null) {
NameValueCollection returnToArgs = HttpUtility.ParseQueryString(returnToUrl.Query);
foreach (string key in returnToArgs) {
if (OpenIdRelyingParty.ShouldParameterBeStrippedFromReturnToUrl(key)) {
Logger.WarnFormat("OpenId argument \"{0}\" found in return_to URL. This can corrupt an OpenID response.", key);
break;
}
}
}
var endpoint = userSuppliedIdentifier.Discover();
if (endpoint == null)
throw new OpenIdException(Strings.OpenIdEndpointNotFound);
Logger.DebugFormat("Discovered provider endpoint: {0}", endpoint);
// Throw an exception now if the realm and the return_to URLs don't match
// as required by the provider. We could wait for the provider to test this and
// fail, but this will be faster and give us a better error message.
if (!realm.Contains(returnToUrl))
throw new OpenIdException(string.Format(CultureInfo.CurrentCulture,
Strings.ReturnToNotUnderRealm, returnToUrl, realm));
return new AuthenticationRequest(
new Token(endpoint).Serialize(store),
store != null ? getAssociation(endpoint, store) : null,
endpoint, realm, returnToUrl, encoder);
}
static Association getAssociation(ServiceEndpoint provider, IRelyingPartyApplicationStore store) {
if (provider == null) throw new ArgumentNullException("provider");
if (store == null) throw new ArgumentNullException("store");
Association assoc = store.GetAssociation(provider.ProviderEndpoint);
if (assoc == null || !assoc.HasUsefulLifeRemaining) {
var req = AssociateRequest.Create(provider);
if (req.Response != null) {
// try again if we failed the first time and have a worthy second-try.
if (req.Response.Association == null && req.Response.SecondAttempt != null) {
Logger.Warn("Initial association attempt failed, but will retry with Provider-suggested parameters.");
req = req.Response.SecondAttempt;
}
assoc = req.Response.Association;
if (assoc != null) {
Logger.InfoFormat("Association with {0} established.", provider.ProviderEndpoint);
store.StoreAssociation(provider.ProviderEndpoint, assoc);
} else {
Logger.ErrorFormat("Association attempt with {0} provider failed.", provider.ProviderEndpoint);
}
}
}
return assoc;
}
///
/// Extension arguments to pass to the Provider.
///
protected ExtensionArgumentsManager OutgoingExtensions { get; private set; }
///
/// Arguments to add to the return_to part of the query string, so that
/// these values come back to the consumer when the user agent returns.
///
protected IDictionary ReturnToArgs { get; private set; }
public AuthenticationRequestMode Mode { get; set; }
public Realm Realm { get; private set; }
public Uri ReturnToUrl { get; private set; }
public Identifier ClaimedIdentifier {
get { return IsDirectedIdentity ? null : endpoint.ClaimedIdentifier; }
}
public bool IsDirectedIdentity {
get { return endpoint.ClaimedIdentifier == endpoint.Protocol.ClaimedIdentifierForOPIdentifier; }
}
///
/// The detected version of OpenID implemented by the Provider.
///
public Version ProviderVersion { get { return protocol.Version; } }
///
/// Gets information about the OpenId Provider, as advertised by the
/// OpenId discovery documents found at the
/// location.
///
IProviderEndpoint IAuthenticationRequest.Provider { get { return endpoint; } }
///
/// Gets the response to send to the user agent to begin the
/// OpenID authentication process.
///
public IResponse RedirectingResponse {
get {
UriBuilder returnToBuilder = new UriBuilder(ReturnToUrl);
UriUtil.AppendQueryArgs(returnToBuilder, this.ReturnToArgs);
var qsArgs = new Dictionary();
qsArgs.Add(protocol.openid.mode, (Mode == AuthenticationRequestMode.Immediate) ?
protocol.Args.Mode.checkid_immediate : protocol.Args.Mode.checkid_setup);
qsArgs.Add(protocol.openid.identity, endpoint.ProviderLocalIdentifier);
if (endpoint.Protocol.QueryDeclaredNamespaceVersion != null)
qsArgs.Add(protocol.openid.ns, endpoint.Protocol.QueryDeclaredNamespaceVersion);
if (endpoint.Protocol.Version.Major >= 2) {
qsArgs.Add(protocol.openid.claimed_id, endpoint.ClaimedIdentifier);
}
qsArgs.Add(protocol.openid.Realm, Realm);
qsArgs.Add(protocol.openid.return_to, returnToBuilder.Uri.AbsoluteUri);
if (this.assoc != null)
qsArgs.Add(protocol.openid.assoc_handle, this.assoc.Handle);
// Add on extension arguments
foreach(var pair in OutgoingExtensions.GetArgumentsToSend(true))
qsArgs.Add(pair.Key, pair.Value);
var request = new IndirectMessageRequest(this.endpoint.ProviderEndpoint, qsArgs);
return this.encoder.Encode(request);
}
}
public void AddExtension(DotNetOpenId.Extensions.IExtensionRequest extension) {
if (extension == null) throw new ArgumentNullException("extension");
OutgoingExtensions.AddExtensionArguments(extension.TypeUri, extension.Serialize(this));
}
///
/// Adds given key/value pairs to the query that the provider will use in
/// the request to return to the consumer web site.
///
public void AddCallbackArguments(IDictionary arguments) {
if (arguments == null) throw new ArgumentNullException("arguments");
foreach (var pair in arguments) {
AddCallbackArguments(pair.Key, pair.Value);
}
}
///
/// Adds a given key/value pair to the query that the provider will use in
/// the request to return to the consumer web site.
///
public void AddCallbackArguments(string key, string value) {
if (string.IsNullOrEmpty(key)) throw new ArgumentNullException("key");
if (ReturnToArgs.ContainsKey(key)) throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
Strings.KeyAlreadyExists, key));
ReturnToArgs.Add(key, value ?? "");
}
///
/// Redirects the user agent to the provider for authentication.
/// Execution of the current page terminates after this call.
///
///
/// This method requires an ASP.NET HttpContext.
///
public void RedirectToProvider() {
if (HttpContext.Current == null || HttpContext.Current.Response == null)
throw new InvalidOperationException(Strings.CurrentHttpContextRequired);
RedirectingResponse.Send();
}
}
}