//-----------------------------------------------------------------------
//
// Copyright (c) Outercurve Foundation. All rights reserved.
//
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.OAuth2 {
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Security;
using DotNetOpenAuth.Configuration;
using DotNetOpenAuth.Logging;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OAuth2.Messages;
using Validation;
///
/// An OAuth 2.0 consumer designed for web applications.
///
public class WebServerClient : ClientBase {
///
/// The cookie name for XSRF mitigation during authorization code grant flows.
///
private const string XsrfCookieName = "DotNetOpenAuth.WebServerClient.XSRF-Session";
///
/// Initializes a new instance of the class.
///
/// The authorization server.
/// The client identifier.
/// The client secret.
/// The host factories.
public WebServerClient(AuthorizationServerDescription authorizationServer, string clientIdentifier = null, string clientSecret = null, IHostFactories hostFactories = null)
: this(authorizationServer, clientIdentifier, DefaultSecretApplicator(clientSecret), hostFactories) {
}
///
/// Initializes a new instance of the class.
///
/// The authorization server.
/// The client identifier.
/// The tool to use to apply client credentials to authenticated requests to the Authorization Server.
/// May be null for clients with no secret or other means of authentication.
/// The host factories.
public WebServerClient(AuthorizationServerDescription authorizationServer, string clientIdentifier, ClientCredentialApplicator clientCredentialApplicator, IHostFactories hostFactories = null)
: base(authorizationServer, clientIdentifier, clientCredentialApplicator, hostFactories) {
}
///
/// Gets or sets an optional component that gives you greater control to record and influence the authorization process.
///
/// The authorization tracker.
public IClientAuthorizationTracker AuthorizationTracker { get; set; }
///
/// Prepares a request for user authorization from an authorization server.
///
/// The scope of authorized access requested.
/// The URL the authorization server should redirect the browser (typically on this site) to when the authorization is completed. If null, the current request's URL will be used.
/// The cancellation token.
///
/// The authorization request.
///
public Task PrepareRequestUserAuthorizationAsync(IEnumerable scopes = null, Uri returnTo = null, CancellationToken cancellationToken = default(CancellationToken)) {
var authorizationState = new AuthorizationState(scopes) {
Callback = returnTo,
};
return this.PrepareRequestUserAuthorizationAsync(authorizationState, cancellationToken);
}
///
/// Prepares a request for user authorization from an authorization server.
///
/// The authorization state to associate with this particular request.
/// The cancellation token.
///
/// The authorization request.
///
public async Task PrepareRequestUserAuthorizationAsync(IAuthorizationState authorization, CancellationToken cancellationToken = default(CancellationToken)) {
Requires.NotNull(authorization, "authorization");
RequiresEx.ValidState(authorization.Callback != null || (HttpContext.Current != null && HttpContext.Current.Request != null), MessagingStrings.HttpContextRequired);
RequiresEx.ValidState(!string.IsNullOrEmpty(this.ClientIdentifier), Strings.RequiredPropertyNotYetPreset, "ClientIdentifier");
if (authorization.Callback == null) {
authorization.Callback = this.Channel.GetRequestFromContext().GetPublicFacingUrl()
.StripMessagePartsFromQueryString(this.Channel.MessageDescriptions.Get(typeof(EndUserAuthorizationSuccessResponseBase), Protocol.Default.Version))
.StripMessagePartsFromQueryString(this.Channel.MessageDescriptions.Get(typeof(EndUserAuthorizationFailedResponse), Protocol.Default.Version));
authorization.SaveChanges();
}
var request = new EndUserAuthorizationRequestC(this.AuthorizationServer) {
ClientIdentifier = this.ClientIdentifier,
Callback = authorization.Callback,
};
request.Scope.ResetContents(authorization.Scope);
// Mitigate XSRF attacks by including a state value that would be unpredictable between users, but
// verifiable for the same user/session.
// If the host is implementing the authorization tracker though, they're handling this protection themselves.
var cookies = new List();
if (this.AuthorizationTracker == null) {
string xsrfKey = MessagingUtilities.GetNonCryptoRandomDataAsBase64(16, useWeb64: true);
cookies.Add(new CookieHeaderValue(XsrfCookieName, xsrfKey) {
HttpOnly = true,
Secure = FormsAuthentication.RequireSSL,
});
request.ClientState = xsrfKey;
}
var response = await this.Channel.PrepareResponseAsync(request, cancellationToken);
response.Headers.AddCookies(cookies);
return response;
}
///
/// Processes the authorization response from an authorization server, if available.
///
/// The incoming HTTP request that may carry an authorization response.
/// The cancellation token.
/// The authorization state that contains the details of the authorization.
public Task ProcessUserAuthorizationAsync(
HttpRequestBase request = null, CancellationToken cancellationToken = default(CancellationToken)) {
request = request ?? this.Channel.GetRequestFromContext();
return this.ProcessUserAuthorizationAsync(request.AsHttpRequestMessage(), cancellationToken);
}
///
/// Processes the authorization response from an authorization server, if available.
///
/// The incoming HTTP request that may carry an authorization response.
/// The cancellation token.
/// The authorization state that contains the details of the authorization.
public async Task ProcessUserAuthorizationAsync(HttpRequestMessage request, CancellationToken cancellationToken = default(CancellationToken)) {
Requires.NotNull(request, "request");
RequiresEx.ValidState(!string.IsNullOrEmpty(this.ClientIdentifier), Strings.RequiredPropertyNotYetPreset, "ClientIdentifier");
RequiresEx.ValidState(this.ClientCredentialApplicator != null, Strings.RequiredPropertyNotYetPreset, "ClientCredentialApplicator");
var response = await this.Channel.TryReadFromRequestAsync(request, cancellationToken);
if (response != null) {
Uri callback = request.RequestUri.StripMessagePartsFromQueryString(this.Channel.MessageDescriptions.Get(response));
IAuthorizationState authorizationState;
if (this.AuthorizationTracker != null) {
authorizationState = this.AuthorizationTracker.GetAuthorizationState(callback, response.ClientState);
ErrorUtilities.VerifyProtocol(authorizationState != null, ClientStrings.AuthorizationResponseUnexpectedMismatch);
} else {
var xsrfCookieValue = (from cookieHeader in request.Headers.GetCookies()
from cookie in cookieHeader.Cookies
where cookie.Name == XsrfCookieName
select cookie.Value).FirstOrDefault();
ErrorUtilities.VerifyProtocol(xsrfCookieValue != null && string.Equals(response.ClientState, xsrfCookieValue, StringComparison.Ordinal), ClientStrings.AuthorizationResponseUnexpectedMismatch);
authorizationState = new AuthorizationState { Callback = callback };
}
var success = response as EndUserAuthorizationSuccessAuthCodeResponse;
var failure = response as EndUserAuthorizationFailedResponse;
ErrorUtilities.VerifyProtocol(success != null || failure != null, MessagingStrings.UnexpectedMessageReceivedOfMany);
if (success != null) {
await this.UpdateAuthorizationWithResponseAsync(authorizationState, success, cancellationToken);
} else { // failure
Logger.OAuth.Info("User refused to grant the requested authorization at the Authorization Server.");
authorizationState.Delete();
}
return authorizationState;
}
return null;
}
}
}