//-----------------------------------------------------------------------
//
// 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.Headers;
using System.Text;
using DotNetOpenAuth.Messaging;
using Validation;
using HttpRequestHeaders = DotNetOpenAuth.Messaging.HttpRequestHeaders;
///
/// Some common utility methods for OAuth 2.0.
///
public static class OAuthUtilities {
///
/// The instance to use when comparing scope equivalence.
///
public static readonly StringComparer ScopeStringComparer = StringComparer.Ordinal;
///
/// The string "Basic ".
///
private const string HttpBasicAuthScheme = "Basic";
///
/// The delimiter between scope elements.
///
private static readonly char[] scopeDelimiter = new char[] { ' ' };
///
/// A colon, in a 1-length character array.
///
private static readonly char[] ColonSeparator = new char[] { ':' };
///
/// The encoding to use when preparing credentials for transit in HTTP Basic base64 encoding form.
///
private static readonly Encoding HttpBasicEncoding = Encoding.UTF8;
///
/// The characters that may appear in an access token that is included in an HTTP Authorization header.
///
///
/// This is defined in OAuth 2.0 DRAFT 10, section 5.1.1. (http://tools.ietf.org/id/draft-ietf-oauth-v2-10.html#authz-header)
///
private static string accessTokenAuthorizationHeaderAllowedCharacters = MessagingUtilities.UppercaseLetters +
MessagingUtilities.LowercaseLetters +
MessagingUtilities.Digits +
@"!#$%&'()*+-./:<=>?@[]^_`{|}~\,;";
///
/// Identifies individual scope elements
///
/// The space-delimited list of scopes.
/// A set of individual scopes, with any duplicates removed.
public static HashSet SplitScopes(string scope) {
if (string.IsNullOrEmpty(scope)) {
return new HashSet();
}
var set = new HashSet(scope.Split(scopeDelimiter, StringSplitOptions.RemoveEmptyEntries), ScopeStringComparer);
VerifyValidScopeTokens(set);
return set;
}
///
/// Serializes a set of scopes as a space-delimited list.
///
/// The scopes to serialize.
/// A space-delimited list.
public static string JoinScopes(HashSet scopes) {
Requires.NotNull(scopes, "scopes");
VerifyValidScopeTokens(scopes);
return string.Join(" ", scopes.ToArray());
}
///
/// Parses a space-delimited list of scopes into a set.
///
/// The space-delimited string.
/// A set.
internal static HashSet ParseScopeSet(string scopes) {
Requires.NotNull(scopes, "scopes");
return ParseScopeSet(scopes.Split(scopeDelimiter, StringSplitOptions.RemoveEmptyEntries));
}
///
/// Creates a set out of an array of strings.
///
/// The array of strings.
/// A set.
internal static HashSet ParseScopeSet(string[] scopes) {
Requires.NotNull(scopes, "scopes");
return new HashSet(scopes, StringComparer.Ordinal);
}
///
/// Verifies that a sequence of scope tokens are all valid.
///
/// The scopes.
internal static void VerifyValidScopeTokens(IEnumerable scopes) {
Requires.NotNull(scopes, "scopes");
foreach (string scope in scopes) {
VerifyValidScopeToken(scope);
}
}
///
/// Verifies that a given scope token (not a space-delimited set, but a single token) is valid.
///
/// The scope token.
internal static void VerifyValidScopeToken(string scopeToken) {
ErrorUtilities.VerifyProtocol(!string.IsNullOrEmpty(scopeToken), OAuthStrings.InvalidScopeToken, scopeToken);
for (int i = 0; i < scopeToken.Length; i++) {
// The allowed set of characters comes from OAuth 2.0 section 3.3 (draft 23)
char ch = scopeToken[i];
if (!(ch == '\x21' || (ch >= '\x23' && ch <= '\x5B') || (ch >= '\x5D' && ch <= '\x7E'))) {
ErrorUtilities.ThrowProtocol(OAuthStrings.InvalidScopeToken, scopeToken);
}
}
}
///
/// Authorizes an HTTP request using an OAuth 2.0 access token in an HTTP Authorization header.
///
/// The request to authorize.
/// The access token previously obtained from the Authorization Server.
internal static void AuthorizeWithBearerToken(this HttpWebRequest request, string accessToken) {
Requires.NotNull(request, "request");
Requires.NotNullOrEmpty(accessToken, "accessToken");
ErrorUtilities.VerifyProtocol(accessToken.All(ch => accessTokenAuthorizationHeaderAllowedCharacters.IndexOf(ch) >= 0), OAuthStrings.AccessTokenInvalidForHttpAuthorizationHeader);
AuthorizeWithBearerToken(request.Headers, accessToken);
}
///
/// Authorizes an HTTP request using an OAuth 2.0 access token in an HTTP Authorization header.
///
/// The headers on the request for protected resources from the service provider.
/// The access token previously obtained from the Authorization Server.
internal static void AuthorizeWithBearerToken(WebHeaderCollection requestHeaders, string accessToken) {
Requires.NotNull(requestHeaders, "requestHeaders");
Requires.NotNullOrEmpty(accessToken, "accessToken");
ErrorUtilities.VerifyProtocol(accessToken.All(ch => accessTokenAuthorizationHeaderAllowedCharacters.IndexOf(ch) >= 0), OAuthStrings.AccessTokenInvalidForHttpAuthorizationHeader);
requestHeaders[HttpRequestHeader.Authorization] = string.Format(
CultureInfo.InvariantCulture,
Protocol.BearerHttpAuthorizationHeaderFormat,
accessToken);
}
///
/// Applies the HTTP Authorization header for HTTP Basic authentication.
///
/// The headers collection to set the authorization header to.
/// The username. Cannot be empty.
/// The password. Cannot be null.
internal static void ApplyHttpBasicAuth(System.Net.Http.Headers.HttpRequestHeaders headers, string userName, string password) {
Requires.NotNull(headers, "headers");
Requires.NotNullOrEmpty(userName, "userName");
Requires.NotNull(password, "password");
string concat = userName + ":" + password;
byte[] bits = HttpBasicEncoding.GetBytes(concat);
string base64 = Convert.ToBase64String(bits);
headers.Authorization = new AuthenticationHeaderValue(HttpBasicAuthScheme, base64);
}
///
/// Extracts the username and password from an HTTP Basic authorized web header.
///
/// The incoming web headers.
/// The network credentials; or null if none could be discovered in the request.
internal static NetworkCredential ParseHttpBasicAuth(System.Net.Http.Headers.HttpRequestHeaders headers) {
Requires.NotNull(headers, "headers");
var authorizationHeader = headers.Authorization;
if (authorizationHeader != null && string.Equals(authorizationHeader.Scheme, HttpBasicAuthScheme, StringComparison.Ordinal)) {
string base64 = authorizationHeader.Parameter;
byte[] bits = Convert.FromBase64String(base64);
string usernameColonPassword = HttpBasicEncoding.GetString(bits);
string[] usernameAndPassword = usernameColonPassword.Split(ColonSeparator, 2);
if (usernameAndPassword.Length == 2) {
return new NetworkCredential(usernameAndPassword[0], usernameAndPassword[1]);
}
}
return null;
}
}
}