//----------------------------------------------------------------------- // // 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; } } }