/// <summary> /// Collects and removes all query string parameters beginning with "oauth_" from the specified request, /// and returns them as a collection. /// </summary> /// <param name="request">The request whose query string should be searched for "oauth_" parameters.</param> /// <returns>The collection of parameters that were removed from the query string.</returns> private static NameValueCollection ExtractOAuthParametersFromQueryString(HttpRequestMessage request) { Requires.NotNull(request, "request"); var extracted = new NameValueCollection(); if (!string.IsNullOrEmpty(request.RequestUri.Query)) { var queryString = PortableUtilities.ParseQueryString(request.RequestUri); foreach (var pair in queryString.AsKeyValuePairs()) { if (pair.Key.StartsWith(ParameterPrefix, StringComparison.Ordinal)) { extracted.Add(pair.Key, pair.Value); } } if (extracted.Count > 0) { foreach (string key in extracted) { queryString.Remove(key); } var modifiedRequestUri = new UriBuilder(request.RequestUri); modifiedRequestUri.Query = PortableUtilities.CreateQueryString(queryString.AsKeyValuePairs()); request.RequestUri = modifiedRequestUri.Uri; } } return(extracted); }
/// <summary> /// Finalizes authorization after the user has completed the authorization steps /// at the user agent. /// </summary> /// <param name="callbackUri">The final URL that the service provider redirected back to to signal that authorization is complete.</param> /// <param name="cancellationToken">A token that may be canceled to abort.</param> /// <returns> /// A task that completes when the authorization has been finalized. /// The access token and token secret obtained from the authorization are then /// available in the <see cref="AccessToken"/> and <see cref="AccessTokenSecret"/> properties. /// </returns> public async Task CompleteAuthorizationAsync(Uri callbackUri, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNull(callbackUri, "callbackUri"); Verify.Operation(!string.IsNullOrEmpty(this.TemporaryToken), "TemporaryToken and TemporaryTokenSecret properties must be initialized first."); var redirectArgs = PortableUtilities.ParseQueryString(callbackUri); ProtocolException.ThrowIfNot(string.Equals(this.TemporaryToken, redirectArgs["oauth_token"], StringComparison.Ordinal), "oauth_token was not the expected value of \"{0}\".", this.TemporaryToken); string verifier = redirectArgs["oauth_verifier"]; var handler = this.CreateOAuthMessageHandler(); handler.AccessToken = this.TemporaryToken; handler.AccessTokenSecret = this.TemporarySecret; using (var httpClient = new HttpClient(handler)) { var accessTokenEndpointBuilder = new UriBuilder(this.AccessTokenEndpoint); accessTokenEndpointBuilder.AppendQueryArgument("oauth_verifier", verifier); var response = await httpClient.PostAsync(accessTokenEndpointBuilder.Uri, new ByteArrayContent(new byte[0]), cancellationToken); ProtocolException.ThrowIf(response.Content == null, "Missing response entity from access token endpoint."); var urlEncodedArgsResponse = await response.Content.ReadAsStringAsync(); var argsResponse = PortableUtilities.ParseUrlEncodedString(urlEncodedArgsResponse); response.EnsureSuccessStatusCode(); this.AccessToken = argsResponse["oauth_token"]; this.AccessTokenSecret = argsResponse["oauth_token_secret"]; this.TemporaryToken = null; this.TemporarySecret = null; } }
/// <summary> /// Obtains temporary credentials and a start URL to direct the user to /// in order to authorize the consumer to access the user's resources. /// </summary> /// <param name="callbackUri"> /// The URL the service provider should redirect the user agent to after authorization. This becomes the value of the oauth_callback parameter. /// May be "oob" to indicate an out-of-band configuration. /// </param> /// <param name="cancellationToken">A token that may be canceled to abort.</param> /// <returns>A task whose result will be the authorization URL.</returns> /// <remarks> /// This method sets the <see cref="TemporaryToken"/> and <see cref="TemporarySecret"/> /// properties. These values should be preserved (or restored) till the /// <see cref="CompleteAuthorizationAsync(string, CancellationToken)"/> method is called. /// </remarks> public async Task <Uri> StartAuthorizationAsync(string callbackUri, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNullOrEmpty(callbackUri, "callbackUri"); Verify.Operation(this.TemporaryCredentialsEndpoint != null, "TemporaryCredentialsEndpoint must be set first."); Verify.Operation(this.ConsumerKey != null, "ConsumerKey must be initialized first."); Verify.Operation(this.ConsumerSecret != null, "ConsumerSecret must be initialized first."); var authorizingHandler = this.CreateOAuthMessageHandler(); authorizingHandler.AccessToken = string.Empty; authorizingHandler.AccessTokenSecret = string.Empty; using (var httpClient = new HttpClient(authorizingHandler)) { var requestUri = new UriBuilder(this.TemporaryCredentialsEndpoint); requestUri.AppendQueryArgument("oauth_callback", callbackUri); var response = await httpClient.PostAsync(requestUri.Uri, new ByteArrayContent(new byte[0]), cancellationToken); ProtocolException.ThrowIf(response.Content == null, "Response missing the expected body."); var urlEncodedArgsResponse = await response.Content.ReadAsStringAsync(); var argsResponse = PortableUtilities.ParseUrlEncodedString(urlEncodedArgsResponse); response.EnsureSuccessStatusCode(); ProtocolException.ThrowIfNot("true" == argsResponse["oauth_callback_confirmed"], "oauth_callback_confirmed parameter not provided or does not match the expected \"true\" value in response from service provider."); this.TemporaryToken = argsResponse["oauth_token"]; ProtocolException.ThrowIf(string.IsNullOrEmpty(this.TemporaryToken), "Unexpected empty or missing oauth_token parameter in response from service provider."); this.TemporarySecret = argsResponse["oauth_token_secret"]; var authorizationBuilder = new UriBuilder(this.AuthorizationEndpoint); authorizationBuilder.AppendQueryArgument("oauth_token", this.TemporaryToken); return(authorizationBuilder.Uri); } }
/// <summary> /// Gets a normalized string of the query string parameters included in the request and the additional OAuth parameters. /// </summary> /// <param name="request">The HTTP request.</param> /// <param name="oauthParameters">The oauth parameters that will be added to the request.</param> /// <returns>The normalized string of parameters to included in the signature base string.</returns> private string GetNormalizedParameters(HttpRequestMessage request, NameValueCollection oauthParameters) { Requires.NotNull(request, "request"); Requires.NotNull(oauthParameters, "oauthParameters"); NameValueCollection nvc; if (request.RequestUri.Query != null) { // NameValueCollection does support non-unique keys, as long as you use it carefully. nvc = PortableUtilities.ParseQueryString(request.RequestUri); } else { nvc = new NameValueCollection(8); } // Add OAuth parameters. nvc.Add(oauthParameters); // Now convert the NameValueCollection into an ordered list, and properly escape all keys and value while we're at it. var list = new List <KeyValuePair <string, string> >(nvc.Count); foreach (var pair in nvc.AsKeyValuePairs()) { string escapedKey = UrlEscape(pair.Key); string escapedValue = UrlEscape(pair.Value ?? string.Empty); // value can be null if no "=" appears in the query string for this key. list.Add(new KeyValuePair <string, string>(escapedKey, escapedValue)); } // Sort the parameters list.Sort((kv1, kv2) => { int compare = string.Compare(kv1.Key, kv2.Key, StringComparison.Ordinal); if (compare != 0) { return(compare); } return(string.Compare(kv1.Value, kv2.Value, StringComparison.Ordinal)); }); // Convert this sorted list into a single concatenated string. var normalizedParameterString = new StringBuilder(); foreach (var pair in list) { if (normalizedParameterString.Length > 0) { normalizedParameterString.Append("&"); } normalizedParameterString.Append(pair.Key); normalizedParameterString.Append("="); normalizedParameterString.Append(pair.Value); } return(normalizedParameterString.ToString()); }
/// <summary> /// Generates a string of random characters for use as a nonce. /// </summary> /// <returns>The nonce string.</returns> private string GenerateUniqueFragment() { return(PortableUtilities.GetRandomString(this.NonceLength, AllowedCharacters)); }
/// <summary> /// Escapes a value for transport in a URI, per RFC 3986. /// </summary> /// <param name="value">The value to escape. Null and empty strings are OK.</param> /// <returns>The escaped value. Never null.</returns> protected static string UrlEscape(string value) { return(PortableUtilities.EscapeUriDataStringRfc3986(value ?? string.Empty)); }
/// <summary> /// Applies OAuth authorization to the specified request. /// This method is applied automatically to outbound requests that use this message handler instance. /// However this method may be useful for obtaining the OAuth 1.0 signature without actually sending the request. /// </summary> /// <param name="request">The request.</param> public void ApplyAuthorization(HttpRequestMessage request) { Requires.NotNull(request, "request"); Verify.Operation(this.ConsumerKey != null, "Not yet initialized."); Verify.Operation(this.ConsumerSecret != null, "Not yet initialized."); var oauthParameters = this.GetOAuthParameters(); string signature = this.GetSignature(request, oauthParameters); oauthParameters.Add("oauth_signature", signature); // Add parameters and signature to request. switch (this.ParametersLocation) { case OAuthParametersLocation.AuthorizationHttpHeader: // Some oauth parameters may have been put in the query string of the original message. // We want to move any that we find into the authorization header. oauthParameters.Add(ExtractOAuthParametersFromQueryString(request)); request.Headers.Authorization = new AuthenticationHeaderValue(AuthorizationHeaderScheme, PortableUtilities.AssembleAuthorizationHeader(oauthParameters.AsKeyValuePairs())); break; case OAuthParametersLocation.QueryString: var uriBuilder = new UriBuilder(request.RequestUri); uriBuilder.AppendQueryArgs(oauthParameters.AsKeyValuePairs()); request.RequestUri = uriBuilder.Uri; break; } }