//-----------------------------------------------------------------------
//
// Copyright (c) Outercurve Foundation. All rights reserved.
//
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.OAuth {
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Security.Principal;
using System.ServiceModel.Channels;
using System.Web;
using DotNetOpenAuth.Configuration;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.Messaging.Bindings;
using DotNetOpenAuth.OAuth.ChannelElements;
using DotNetOpenAuth.OAuth.Messages;
///
/// A web application that allows access via OAuth.
///
///
/// The Service Provider’s documentation should include:
///
/// - The URLs (Request URLs) the Consumer will use when making OAuth requests, and the HTTP methods (i.e. GET, POST, etc.) used in the Request Token URL and Access Token URL.
/// - Signature methods supported by the Service Provider.
/// - Any additional request parameters that the Service Provider requires in order to obtain a Token. Service Provider specific parameters MUST NOT begin with oauth_.
///
///
public class ServiceProvider : IDisposable {
///
/// The name of the key to use in the HttpApplication cache to store the
/// instance of to use.
///
private const string ApplicationStoreKey = "DotNetOpenAuth.OAuth.ServiceProvider.HttpApplicationStore";
///
/// The length of the verifier code (in raw bytes before base64 encoding) to generate.
///
private const int VerifierCodeLength = 5;
///
/// The field behind the property.
///
private OAuthChannel channel;
///
/// Initializes a new instance of the class.
///
/// The endpoints and behavior on the Service Provider.
/// The host's method of storing and recalling tokens and secrets.
public ServiceProvider(ServiceProviderDescription serviceDescription, IServiceProviderTokenManager tokenManager)
: this(serviceDescription, tokenManager, new OAuthServiceProviderMessageFactory(tokenManager)) {
}
///
/// Initializes a new instance of the class.
///
/// The endpoints and behavior on the Service Provider.
/// The host's method of storing and recalling tokens and secrets.
/// An object that can figure out what type of message is being received for deserialization.
public ServiceProvider(ServiceProviderDescription serviceDescription, IServiceProviderTokenManager tokenManager, OAuthServiceProviderMessageFactory messageTypeProvider)
: this(serviceDescription, tokenManager, OAuthElement.Configuration.ServiceProvider.ApplicationStore.CreateInstance(HttpApplicationStore), messageTypeProvider) {
Requires.NotNull(serviceDescription, "serviceDescription");
Requires.NotNull(tokenManager, "tokenManager");
Requires.NotNull(messageTypeProvider, "messageTypeProvider");
}
///
/// Initializes a new instance of the class.
///
/// The endpoints and behavior on the Service Provider.
/// The host's method of storing and recalling tokens and secrets.
/// The nonce store.
public ServiceProvider(ServiceProviderDescription serviceDescription, IServiceProviderTokenManager tokenManager, INonceStore nonceStore)
: this(serviceDescription, tokenManager, nonceStore, new OAuthServiceProviderMessageFactory(tokenManager)) {
}
///
/// Initializes a new instance of the class.
///
/// The endpoints and behavior on the Service Provider.
/// The host's method of storing and recalling tokens and secrets.
/// The nonce store.
/// An object that can figure out what type of message is being received for deserialization.
public ServiceProvider(ServiceProviderDescription serviceDescription, IServiceProviderTokenManager tokenManager, INonceStore nonceStore, OAuthServiceProviderMessageFactory messageTypeProvider) {
Requires.NotNull(serviceDescription, "serviceDescription");
Requires.NotNull(tokenManager, "tokenManager");
Requires.NotNull(nonceStore, "nonceStore");
Requires.NotNull(messageTypeProvider, "messageTypeProvider");
var signingElement = serviceDescription.CreateTamperProtectionElement();
this.ServiceDescription = serviceDescription;
this.SecuritySettings = OAuthElement.Configuration.ServiceProvider.SecuritySettings.CreateSecuritySettings();
this.OAuthChannel = new OAuthServiceProviderChannel(signingElement, nonceStore, tokenManager, this.SecuritySettings, messageTypeProvider);
this.TokenGenerator = new StandardTokenGenerator();
OAuthReporting.RecordFeatureAndDependencyUse(this, serviceDescription, tokenManager, nonceStore);
}
///
/// Gets the standard state storage mechanism that uses ASP.NET's
/// HttpApplication state dictionary to store associations and nonces.
///
[EditorBrowsable(EditorBrowsableState.Advanced)]
public static INonceStore HttpApplicationStore {
get {
Contract.Ensures(Contract.Result() != null);
HttpContext context = HttpContext.Current;
ErrorUtilities.VerifyOperation(context != null, Strings.StoreRequiredWhenNoHttpContextAvailable, typeof(INonceStore).Name);
var store = (INonceStore)context.Application[ApplicationStoreKey];
if (store == null) {
context.Application.Lock();
try {
if ((store = (INonceStore)context.Application[ApplicationStoreKey]) == null) {
context.Application[ApplicationStoreKey] = store = new NonceMemoryStore(StandardExpirationBindingElement.MaximumMessageAge);
}
} finally {
context.Application.UnLock();
}
}
return store;
}
}
///
/// Gets the description of this Service Provider.
///
public ServiceProviderDescription ServiceDescription { get; private set; }
///
/// Gets or sets the generator responsible for generating new tokens and secrets.
///
public ITokenGenerator TokenGenerator { get; set; }
///
/// Gets the persistence store for tokens and secrets.
///
public IServiceProviderTokenManager TokenManager {
get { return (IServiceProviderTokenManager)this.OAuthChannel.TokenManager; }
}
///
/// Gets the channel to use for sending/receiving messages.
///
public Channel Channel {
get { return this.OAuthChannel; }
}
///
/// Gets the security settings for this service provider.
///
public ServiceProviderSecuritySettings SecuritySettings { get; private set; }
///
/// Gets or sets the channel to use for sending/receiving messages.
///
internal OAuthChannel OAuthChannel {
get {
return this.channel;
}
set {
Requires.NotNull(value, "value");
this.channel = value;
}
}
///
/// Creates a cryptographically strong random verification code.
///
/// The desired format of the verification code.
/// The length of the code.
/// When is ,
/// this is the length of the original byte array before base64 encoding rather than the actual
/// length of the final string.
/// The verification code.
public static string CreateVerificationCode(VerificationCodeFormat format, int length) {
Requires.InRange(length >= 0, "length");
switch (format) {
case VerificationCodeFormat.IncludedInCallback:
return MessagingUtilities.GetCryptoRandomDataAsBase64(length);
case VerificationCodeFormat.AlphaNumericNoLookAlikes:
return MessagingUtilities.GetRandomString(length, MessagingUtilities.AlphaNumericNoLookAlikes);
case VerificationCodeFormat.AlphaUpper:
return MessagingUtilities.GetRandomString(length, MessagingUtilities.UppercaseLetters);
case VerificationCodeFormat.AlphaLower:
return MessagingUtilities.GetRandomString(length, MessagingUtilities.LowercaseLetters);
case VerificationCodeFormat.Numeric:
return MessagingUtilities.GetRandomString(length, MessagingUtilities.Digits);
default:
throw new ArgumentOutOfRangeException("format");
}
}
///
/// Reads any incoming OAuth message.
///
/// The deserialized message.
///
/// Requires HttpContext.Current.
///
public IDirectedProtocolMessage ReadRequest() {
return this.Channel.ReadFromRequest();
}
///
/// Reads any incoming OAuth message.
///
/// The HTTP request to read the message from.
/// The deserialized message.
public IDirectedProtocolMessage ReadRequest(HttpRequestInfo request) {
return this.Channel.ReadFromRequest(request);
}
///
/// Gets the incoming request for an unauthorized token, if any.
///
/// The incoming request, or null if no OAuth message was attached.
/// Thrown if an unexpected OAuth message is attached to the incoming request.
///
/// Requires HttpContext.Current.
///
public UnauthorizedTokenRequest ReadTokenRequest() {
return this.ReadTokenRequest(this.Channel.GetRequestFromContext());
}
///
/// Reads a request for an unauthorized token from the incoming HTTP request.
///
/// The HTTP request to read from.
/// The incoming request, or null if no OAuth message was attached.
/// Thrown if an unexpected OAuth message is attached to the incoming request.
public UnauthorizedTokenRequest ReadTokenRequest(HttpRequestInfo request) {
UnauthorizedTokenRequest message;
if (this.Channel.TryReadFromRequest(request, out message)) {
ErrorUtilities.VerifyProtocol(message.Version >= Protocol.Lookup(this.SecuritySettings.MinimumRequiredOAuthVersion).Version, OAuthStrings.MinimumConsumerVersionRequirementNotMet, this.SecuritySettings.MinimumRequiredOAuthVersion, message.Version);
}
return message;
}
///
/// Prepares a message containing an unauthorized token for the Consumer to use in a
/// user agent redirect for subsequent authorization.
///
/// The token request message the Consumer sent that the Service Provider is now responding to.
/// The response message to send using the , after optionally adding extra data to it.
public UnauthorizedTokenResponse PrepareUnauthorizedTokenMessage(UnauthorizedTokenRequest request) {
Requires.NotNull(request, "request");
string token = this.TokenGenerator.GenerateRequestToken(request.ConsumerKey);
string secret = this.TokenGenerator.GenerateSecret();
UnauthorizedTokenResponse response = new UnauthorizedTokenResponse(request, token, secret);
return response;
}
///
/// Gets the incoming request for the Service Provider to authorize a Consumer's
/// access to some protected resources.
///
/// The incoming request, or null if no OAuth message was attached.
/// Thrown if an unexpected OAuth message is attached to the incoming request.
///
/// Requires HttpContext.Current.
///
public UserAuthorizationRequest ReadAuthorizationRequest() {
return this.ReadAuthorizationRequest(this.Channel.GetRequestFromContext());
}
///
/// Reads in a Consumer's request for the Service Provider to obtain permission from
/// the user to authorize the Consumer's access of some protected resource(s).
///
/// The HTTP request to read from.
/// The incoming request, or null if no OAuth message was attached.
/// Thrown if an unexpected OAuth message is attached to the incoming request.
public UserAuthorizationRequest ReadAuthorizationRequest(HttpRequestInfo request) {
UserAuthorizationRequest message;
this.Channel.TryReadFromRequest(request, out message);
return message;
}
///
/// Prepares the message to send back to the consumer following proper authorization of
/// a token by an interactive user at the Service Provider's web site.
///
/// The Consumer's original authorization request.
///
/// The message to send to the Consumer using if one is necessary.
/// Null if the Consumer did not request a callback as part of the authorization request.
///
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Consistent user experience with instance.")]
public UserAuthorizationResponse PrepareAuthorizationResponse(UserAuthorizationRequest request) {
Requires.NotNull(request, "request");
// It is very important for us to ignore the oauth_callback argument in the
// UserAuthorizationRequest if the Consumer is a 1.0a consumer or else we
// open up a security exploit.
IServiceProviderRequestToken token = this.TokenManager.GetRequestToken(request.RequestToken);
Uri callback;
if (request.Version >= Protocol.V10a.Version) {
// In OAuth 1.0a, we'll prefer the token-specific callback to the pre-registered one.
if (token.Callback != null) {
callback = token.Callback;
} else {
IConsumerDescription consumer = this.TokenManager.GetConsumer(token.ConsumerKey);
callback = consumer.Callback;
}
} else {
// In OAuth 1.0, we'll prefer the pre-registered callback over the token-specific one
// since 1.0 has a security weakness for user-modified callback URIs.
IConsumerDescription consumer = this.TokenManager.GetConsumer(token.ConsumerKey);
callback = consumer.Callback ?? request.Callback;
}
return callback != null ? this.PrepareAuthorizationResponse(request, callback) : null;
}
///
/// Prepares the message to send back to the consumer following proper authorization of
/// a token by an interactive user at the Service Provider's web site.
///
/// The Consumer's original authorization request.
/// The callback URI the consumer has previously registered
/// with this service provider or that came in the .
///
/// The message to send to the Consumer using .
///
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Consistent user experience with instance.")]
public UserAuthorizationResponse PrepareAuthorizationResponse(UserAuthorizationRequest request, Uri callback) {
Requires.NotNull(request, "request");
Requires.NotNull(callback, "callback");
var authorization = new UserAuthorizationResponse(callback, request.Version) {
RequestToken = request.RequestToken,
};
if (authorization.Version >= Protocol.V10a.Version) {
authorization.VerificationCode = CreateVerificationCode(VerificationCodeFormat.IncludedInCallback, VerifierCodeLength);
}
return authorization;
}
///
/// Gets the incoming request to exchange an authorized token for an access token.
///
/// The incoming request, or null if no OAuth message was attached.
/// Thrown if an unexpected OAuth message is attached to the incoming request.
///
/// Requires HttpContext.Current.
///
public AuthorizedTokenRequest ReadAccessTokenRequest() {
return this.ReadAccessTokenRequest(this.Channel.GetRequestFromContext());
}
///
/// Reads in a Consumer's request to exchange an authorized request token for an access token.
///
/// The HTTP request to read from.
/// The incoming request, or null if no OAuth message was attached.
/// Thrown if an unexpected OAuth message is attached to the incoming request.
public AuthorizedTokenRequest ReadAccessTokenRequest(HttpRequestInfo request) {
AuthorizedTokenRequest message;
this.Channel.TryReadFromRequest(request, out message);
return message;
}
///
/// Prepares and sends an access token to a Consumer, and invalidates the request token.
///
/// The Consumer's message requesting an access token.
/// The HTTP response to actually send to the Consumer.
public AuthorizedTokenResponse PrepareAccessTokenMessage(AuthorizedTokenRequest request) {
Requires.NotNull(request, "request");
ErrorUtilities.VerifyProtocol(this.TokenManager.IsRequestTokenAuthorized(request.RequestToken), OAuthStrings.AccessTokenNotAuthorized, request.RequestToken);
string accessToken = this.TokenGenerator.GenerateAccessToken(request.ConsumerKey);
string tokenSecret = this.TokenGenerator.GenerateSecret();
this.TokenManager.ExpireRequestTokenAndStoreNewAccessToken(request.ConsumerKey, request.RequestToken, accessToken, tokenSecret);
var grantAccess = new AuthorizedTokenResponse(request) {
AccessToken = accessToken,
TokenSecret = tokenSecret,
};
return grantAccess;
}
///
/// Gets the authorization (access token) for accessing some protected resource.
///
/// The authorization message sent by the Consumer, or null if no authorization message is attached.
///
/// This method verifies that the access token and token secret are valid.
/// It falls on the caller to verify that the access token is actually authorized
/// to access the resources being requested.
///
/// Thrown if an unexpected message is attached to the request.
public AccessProtectedResourceRequest ReadProtectedResourceAuthorization() {
return this.ReadProtectedResourceAuthorization(this.Channel.GetRequestFromContext());
}
///
/// Gets the authorization (access token) for accessing some protected resource.
///
/// HTTP details from an incoming WCF message.
/// The URI of the WCF service endpoint.
/// The authorization message sent by the Consumer, or null if no authorization message is attached.
///
/// This method verifies that the access token and token secret are valid.
/// It falls on the caller to verify that the access token is actually authorized
/// to access the resources being requested.
///
/// Thrown if an unexpected message is attached to the request.
public AccessProtectedResourceRequest ReadProtectedResourceAuthorization(HttpRequestMessageProperty request, Uri requestUri) {
return this.ReadProtectedResourceAuthorization(new HttpRequestInfo(request, requestUri));
}
///
/// Gets the authorization (access token) for accessing some protected resource.
///
/// The incoming HTTP request.
/// The authorization message sent by the Consumer, or null if no authorization message is attached.
///
/// This method verifies that the access token and token secret are valid.
/// It falls on the caller to verify that the access token is actually authorized
/// to access the resources being requested.
///
/// Thrown if an unexpected message is attached to the request.
public AccessProtectedResourceRequest ReadProtectedResourceAuthorization(HttpRequestInfo request) {
Requires.NotNull(request, "request");
AccessProtectedResourceRequest accessMessage;
if (this.Channel.TryReadFromRequest(request, out accessMessage)) {
if (this.TokenManager.GetTokenType(accessMessage.AccessToken) != TokenType.AccessToken) {
throw new ProtocolException(
string.Format(
CultureInfo.CurrentCulture,
OAuthStrings.BadAccessTokenInProtectedResourceRequest,
accessMessage.AccessToken));
}
}
return accessMessage;
}
///
/// Creates a security principal that may be used.
///
/// The request.
/// The instance that can be used for access control of resources.
public OAuthPrincipal CreatePrincipal(AccessProtectedResourceRequest request) {
Requires.NotNull(request, "request");
IServiceProviderAccessToken accessToken = this.TokenManager.GetAccessToken(request.AccessToken);
return new OAuth1Principal(accessToken);
}
#region IDisposable Members
///
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
///
public void Dispose() {
this.Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Releases unmanaged and - optionally - managed resources
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected virtual void Dispose(bool disposing) {
if (disposing) {
this.Channel.Dispose();
}
}
#endregion
}
}