From 18454d805f976dd8b1f8f78bd06ad725b027c6a3 Mon Sep 17 00:00:00 2001 From: Kendall Bennett Date: Tue, 8 Nov 2022 12:07:58 -0500 Subject: [PATCH 1/2] Flattened all changes --- src/RestSharp/Request/RestRequest.cs | 36 +++++++++-- .../Request/RestRequestExtensions.cs | 10 ++++ src/RestSharp/RestClient.Async.cs | 6 +- src/RestSharp/RestClient.cs | 13 ++-- src/RestSharp/RestClientExtensions.Config.cs | 4 -- src/RestSharp/RestClientExtensions.Json.cs | 55 +++++++++++++---- src/RestSharp/RestClientExtensions.cs | 13 ++-- src/RestSharp/RestClientOptions.cs | 60 +++++++++++++++---- .../Sync/RestClientExtensions.Sync.Json.cs | 51 ++++++++++------ .../AuthenticationTests.cs | 37 ++++++------ .../TwitterClient.cs | 32 ++++++---- .../Authentication/AuthenticationTests.cs | 4 +- .../Authentication/OAuth2Tests.cs | 4 +- .../RestSharp.Tests.Integrated/OAuth1Tests.cs | 52 ++++++++-------- .../RequestTests.cs | 6 +- test/RestSharp.Tests/JwtAuthTests.cs | 54 ++++++++++++----- test/RestSharp.Tests/RestClientTests.cs | 21 +++++++ 17 files changed, 307 insertions(+), 151 deletions(-) diff --git a/src/RestSharp/Request/RestRequest.cs b/src/RestSharp/Request/RestRequest.cs index 4d81ac9ba..4bdab7fb6 100644 --- a/src/RestSharp/Request/RestRequest.cs +++ b/src/RestSharp/Request/RestRequest.cs @@ -13,6 +13,8 @@ // limitations under the License. using System.Net; +using System.Net.Http.Headers; +using RestSharp.Authenticators; using RestSharp.Extensions; // ReSharper disable UnusedAutoPropertyAccessor.Global @@ -22,8 +24,8 @@ namespace RestSharp; /// Container for data used to make requests /// public class RestRequest { - readonly Func? _advancedResponseHandler; - readonly Func? _responseWriter; + Func? _advancedResponseHandler; + Func? _responseWriter; /// /// Default constructor @@ -111,7 +113,11 @@ public RestRequest(Uri resource, Method method = Method.Get) public Method Method { get; set; } /// - /// Custom request timeout + /// Sets the timeout in milliseconds for this requests using this client. Note that there is also a timeout + /// set on the base client, and the the shorter of the two values is what will end up being used. So if you need long + /// timeouts at the request level, you will want to set the value on the client to to a larger value than the maximum + /// you need per request, or set the client to infinite. If this value is 0, an infinite timeout is used (basically + /// it then times out using whatever was configured at the client level). /// public int Timeout { get; set; } @@ -170,12 +176,32 @@ public RestRequest(Uri resource, Method method = Method.Get) /// public HttpCompletionOption CompletionOption { get; set; } = HttpCompletionOption.ResponseContentRead; + /// + /// Explicit Host header value to use in requests independent from the request URI. + /// + public string? BaseHost { get; set; } + + /// + /// Sets the user agent string to be used for this requests. Defaults to a the client default if not provided. + /// + public string? UserAgent { get; set; } + + /// + /// Sets the cache policy to use for this request + /// + public CacheControlHeaderValue? CachePolicy { get; set; } + + /// + /// Authenticator that will be used to populate request with necessary authentication data + /// + public IAuthenticator? Authenticator { get; set; } + /// /// Set this to write response to Stream rather than reading into memory. /// public Func? ResponseWriter { get => _responseWriter; - init { + set { if (AdvancedResponseWriter != null) throw new ArgumentException( "AdvancedResponseWriter is not null. Only one response writer can be used." @@ -190,7 +216,7 @@ public RestRequest(Uri resource, Method method = Method.Get) /// public Func? AdvancedResponseWriter { get => _advancedResponseHandler; - init { + set { if (ResponseWriter != null) throw new ArgumentException("ResponseWriter is not null. Only one response writer can be used."); diff --git a/src/RestSharp/Request/RestRequestExtensions.cs b/src/RestSharp/Request/RestRequestExtensions.cs index a35da5621..1f9413281 100644 --- a/src/RestSharp/Request/RestRequestExtensions.cs +++ b/src/RestSharp/Request/RestRequestExtensions.cs @@ -14,6 +14,7 @@ using System.Net; using System.Text.RegularExpressions; +using RestSharp.Authenticators; using RestSharp.Extensions; using RestSharp.Serializers; @@ -461,6 +462,15 @@ public static RestRequest AddCookie(this RestRequest request, string name, strin return request; } + /// + /// Enable request authentication for this request using the passed in authenticator + /// + /// Request to attach the authenticator to + /// Authenticator to use + /// + public static RestRequest UseAuthenticator(this RestRequest request, IAuthenticator authenticator) + => request.With(x => x.Authenticator = authenticator); + static void CheckAndThrowsForInvalidHost(string name, string value) { static bool InvalidHost(string host) => Uri.CheckHostName(PortSplitRegex.Split(host)[0]) == UriHostNameType.Unknown; diff --git a/src/RestSharp/RestClient.Async.cs b/src/RestSharp/RestClient.Async.cs index f317ed354..820c618f9 100644 --- a/src/RestSharp/RestClient.Async.cs +++ b/src/RestSharp/RestClient.Async.cs @@ -51,13 +51,13 @@ async Task ExecuteInternal(RestRequest request, CancellationTo using var requestContent = new RequestContent(this, request); - if (Authenticator != null) await Authenticator.Authenticate(this, request).ConfigureAwait(false); + if (request.Authenticator != null) await request.Authenticator.Authenticate(this, request).ConfigureAwait(false); var httpMethod = AsHttpMethod(request.Method); var url = BuildUri(request); var message = new HttpRequestMessage(httpMethod, url) { Content = requestContent.BuildContent() }; - message.Headers.Host = Options.BaseHost; - message.Headers.CacheControl = Options.CachePolicy; + message.Headers.Host = request.BaseHost; + message.Headers.CacheControl = request.CachePolicy; using var timeoutCts = new CancellationTokenSource(request.Timeout > 0 ? request.Timeout : int.MaxValue); using var cts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken); diff --git a/src/RestSharp/RestClient.cs b/src/RestSharp/RestClient.cs index 5abdba0d1..0f5623ff7 100644 --- a/src/RestSharp/RestClient.cs +++ b/src/RestSharp/RestClient.cs @@ -15,7 +15,6 @@ using System.Net; using System.Net.Http.Headers; using System.Text; -using RestSharp.Authenticators; using RestSharp.Extensions; // ReSharper disable VirtualMemberCallInConstructor @@ -125,6 +124,11 @@ public RestClient(HttpClient httpClient, RestClientOptions options, bool dispose /// Dispose the handler when disposing RestClient, true by default public RestClient(HttpMessageHandler handler, bool disposeHandler = true) : this(new HttpClient(handler, disposeHandler), true) { } + /// + /// Returns the currently configured BaseUrl for this RestClient instance + /// + public Uri? BaseUrl => Options.BaseUrl; + void ConfigureHttpClient(HttpClient httpClient) { if (Options.MaxTimeout > 0) httpClient.Timeout = TimeSpan.FromMilliseconds(Options.MaxTimeout); @@ -161,11 +165,6 @@ void ConfigureHttpMessageHandler(HttpClientHandler handler) { internal Func EncodeQuery { get; set; } = (s, encoding) => s.UrlEncode(encoding)!; - /// - /// Authenticator that will be used to populate request with necessary authentication data - /// - public IAuthenticator? Authenticator { get; set; } - public ParametersCollection DefaultParameters { get; } = new(); /// @@ -239,4 +238,4 @@ public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } -} +} \ No newline at end of file diff --git a/src/RestSharp/RestClientExtensions.Config.cs b/src/RestSharp/RestClientExtensions.Config.cs index 8868f7736..8f3eb408a 100644 --- a/src/RestSharp/RestClientExtensions.Config.cs +++ b/src/RestSharp/RestClientExtensions.Config.cs @@ -14,7 +14,6 @@ // using System.Text; -using RestSharp.Authenticators; using RestSharp.Extensions; namespace RestSharp; @@ -41,7 +40,4 @@ public static partial class RestClientExtensions { /// public static RestClient UseQueryEncoder(this RestClient client, Func queryEncoder) => client.With(x => x.EncodeQuery = queryEncoder); - - public static RestClient UseAuthenticator(this RestClient client, IAuthenticator authenticator) - => client.With(x => x.Authenticator = authenticator); } \ No newline at end of file diff --git a/src/RestSharp/RestClientExtensions.Json.cs b/src/RestSharp/RestClientExtensions.Json.cs index 1e4fa28f7..ad94b2945 100644 --- a/src/RestSharp/RestClientExtensions.Json.cs +++ b/src/RestSharp/RestClientExtensions.Json.cs @@ -14,6 +14,7 @@ // using System.Net; +using RestSharp.Authenticators; using RestSharp.Extensions; namespace RestSharp; @@ -25,10 +26,18 @@ public static partial class RestClientExtensions { /// RestClient instance /// Resource URL /// Cancellation token + /// Optional authenticator to use for the request /// Response object type - /// - public static Task GetJsonAsync(this RestClient client, string resource, CancellationToken cancellationToken = default) { - var request = new RestRequest(resource); + /// Deserialized response object + public static Task GetJsonAsync( + this RestClient client, + string resource, + CancellationToken cancellationToken = default, + IAuthenticator? authenticator = null + ) { + var request = new RestRequest(resource) { + Authenticator = authenticator + }; return client.GetAsync(request, cancellationToken); } @@ -39,16 +48,20 @@ public static partial class RestClientExtensions { /// Resource URL /// Parameters to pass to the request /// Cancellation token + /// Optional authenticator to use for the request /// Response object type /// Deserialized response object public static Task GetJsonAsync( this RestClient client, string resource, object parameters, - CancellationToken cancellationToken = default + CancellationToken cancellationToken = default, + IAuthenticator? authenticator = null ) { var props = parameters.GetProperties(); - var request = new RestRequest(resource); + var request = new RestRequest(resource) { + Authenticator = authenticator + }; foreach (var (name, value) in props) { Parameter parameter = resource.Contains($"{name}") ? new UrlSegmentParameter(name, value!) : new QueryParameter(name, value); @@ -66,6 +79,7 @@ public static partial class RestClientExtensions { /// Resource URL /// Request object, must be serializable to JSON /// Cancellation token + /// Optional authenticator to use for the request /// Request object type /// Response object type /// Deserialized response object @@ -73,9 +87,12 @@ public static partial class RestClientExtensions { this RestClient client, string resource, TRequest request, - CancellationToken cancellationToken = default + CancellationToken cancellationToken = default, + IAuthenticator? authenticator = null ) where TRequest : class { - var restRequest = new RestRequest(resource).AddJsonBody(request); + var restRequest = new RestRequest(resource) { + Authenticator = authenticator + }.AddJsonBody(request); return client.PostAsync(restRequest, cancellationToken); } @@ -87,15 +104,19 @@ public static partial class RestClientExtensions { /// Resource URL /// Request object, must be serializable to JSON /// Cancellation token + /// Optional authenticator to use for the request /// Request object type /// Response status code public static async Task PostJsonAsync( this RestClient client, string resource, TRequest request, - CancellationToken cancellationToken = default + CancellationToken cancellationToken = default, + IAuthenticator? authenticator = null ) where TRequest : class { - var restRequest = new RestRequest(resource).AddJsonBody(request); + var restRequest = new RestRequest(resource) { + Authenticator = authenticator + }.AddJsonBody(request); var response = await client.PostAsync(restRequest, cancellationToken).ConfigureAwait(false); return response.StatusCode; } @@ -108,6 +129,7 @@ public static async Task PostJsonAsync( /// Resource URL /// Request object, must be serializable to JSON /// Cancellation token + /// Optional authenticator to use for the request /// Request object type /// Response object type /// Deserialized response object @@ -115,9 +137,12 @@ public static async Task PostJsonAsync( this RestClient client, string resource, TRequest request, - CancellationToken cancellationToken = default + CancellationToken cancellationToken = default, + IAuthenticator? authenticator = null ) where TRequest : class { - var restRequest = new RestRequest(resource).AddJsonBody(request); + var restRequest = new RestRequest(resource) { + Authenticator = authenticator + }.AddJsonBody(request); return client.PutAsync(restRequest, cancellationToken); } @@ -129,15 +154,19 @@ public static async Task PostJsonAsync( /// Resource URL /// Request object, must be serializable to JSON /// Cancellation token + /// Optional authenticator to use for the request /// Request object type /// Response status code public static async Task PutJsonAsync( this RestClient client, string resource, TRequest request, - CancellationToken cancellationToken = default + CancellationToken cancellationToken = default, + IAuthenticator? authenticator = null ) where TRequest : class { - var restRequest = new RestRequest(resource).AddJsonBody(request); + var restRequest = new RestRequest(resource) { + Authenticator = authenticator + }.AddJsonBody(request); var response = await client.PutAsync(restRequest, cancellationToken).ConfigureAwait(false); return response.StatusCode; } diff --git a/src/RestSharp/RestClientExtensions.cs b/src/RestSharp/RestClientExtensions.cs index c91bf10b8..3c2dacfc3 100644 --- a/src/RestSharp/RestClientExtensions.cs +++ b/src/RestSharp/RestClientExtensions.cs @@ -13,6 +13,7 @@ // limitations under the License. using System.Runtime.CompilerServices; +using RestSharp.Authenticators; using RestSharp.Extensions; using RestSharp.Serializers; @@ -306,18 +307,20 @@ public static async Task DeleteAsync(this RestClient client, RestR /// Reads a stream returned by the specified endpoint, deserializes each line to JSON and returns each object asynchronously. /// It is required for each JSON object to be returned in a single line. /// - /// - /// - /// + /// Rest client + /// Resource URL + /// Cancellation token + /// Optional authenticator to use for the request /// /// [PublicAPI] public static async IAsyncEnumerable StreamJsonAsync( this RestClient client, string resource, - [EnumeratorCancellation] CancellationToken cancellationToken + [EnumeratorCancellation] CancellationToken cancellationToken, + IAuthenticator? authenticator = null ) { - var request = new RestRequest(resource); + var request = new RestRequest(resource) { Authenticator = authenticator }; #if NETSTANDARD using var stream = await client.DownloadStreamAsync(request, cancellationToken).ConfigureAwait(false); diff --git a/src/RestSharp/RestClientOptions.cs b/src/RestSharp/RestClientOptions.cs index 83d820660..219e62627 100644 --- a/src/RestSharp/RestClientOptions.cs +++ b/src/RestSharp/RestClientOptions.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation and Contributors +// Copyright © 2009-2021 John Sheehan, Andrew Young, Alexey Zimarev and RestSharp community // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,7 +14,6 @@ // using System.Net; -using System.Net.Http.Headers; using System.Net.Security; using System.Reflection; using System.Security.Cryptography.X509Certificates; @@ -24,21 +23,34 @@ namespace RestSharp; public class RestClientOptions { static readonly Version Version = new AssemblyName(typeof(RestClientOptions).Assembly.FullName!).Version!; - static readonly string DefaultUserAgent = $"RestSharp/{Version}"; + /// + /// Default constructor for default RestClientOptions + /// public RestClientOptions() { } + /// + /// Constructor for RestClientOptions using a specific base URL pass in the Uri format. + /// + /// Base URL to use in Uri format public RestClientOptions(Uri baseUrl) => BaseUrl = baseUrl; + /// + /// Constructor for RestClientOptions using a specific base URL pass as a string. + /// + /// Base URL to use in string format public RestClientOptions(string baseUrl) : this(new Uri(Ensure.NotEmptyString(baseUrl, nameof(baseUrl)))) { } /// - /// Explicit Host header value to use in requests independent from the request URI. - /// If null, default host value extracted from URI is used. + /// Base URI for all requests base with the RestClient. This cannot be changed after the client has been + /// constructed. /// - public Uri? BaseUrl { get; set; } + public Uri? BaseUrl { get; internal set; } + /// + /// Optional callback to allow you to configure the underlying HttpMessageHandler for this client when it is created. + /// public Func? ConfigureMessageHandler { get; set; } /// @@ -57,12 +69,19 @@ public RestClientOptions(string baseUrl) : this(new Uri(Ensure.NotEmptyString(ba /// public bool DisableCharset { get; set; } + /// + /// Option to enable automatic decompression of responses. Defaults to GZip or higher where possible. + /// #if NETSTANDARD public DecompressionMethods AutomaticDecompression { get; set; } = DecompressionMethods.GZip; #else public DecompressionMethods AutomaticDecompression { get; set; } = DecompressionMethods.All; #endif + /// + /// Option to control the maximum number of redirects the client will follow before giving up. If not + /// provided the HttpClient default value of 50 is used. + /// public int? MaxRedirects { get; set; } /// @@ -70,11 +89,25 @@ public RestClientOptions(string baseUrl) : this(new Uri(Ensure.NotEmptyString(ba /// public X509CertificateCollection? ClientCertificates { get; set; } - public IWebProxy? Proxy { get; set; } - public CacheControlHeaderValue? CachePolicy { get; set; } - public bool FollowRedirects { get; set; } = true; - public bool? Expect100Continue { get; set; } = null; - public string? UserAgent { get; set; } = DefaultUserAgent; + /// + /// Define the optional web proxy to use for all requests via this client instance + /// + public IWebProxy? Proxy { get; set; } + + /// + /// Indicates whether the client will follow redirects or not. Defaults to true. + /// + public bool FollowRedirects { get; set; } = true; + + /// + /// Gets or sets a Boolean value that determines whether 100-Continue behavior is used. Default is null. + /// + public bool? Expect100Continue { get; set; } = null; + + /// + /// Gets or sets the default user agent to use for all requests from this client + /// + public string? UserAgent { get; set; } = DefaultUserAgent; /// /// Maximum request duration in milliseconds. When the request timeout is specified using , @@ -82,6 +115,9 @@ public RestClientOptions(string baseUrl) : this(new Uri(Ensure.NotEmptyString(ba /// public int MaxTimeout { get; set; } + /// + /// Set the encoding to use for encoding encoding query strings. By default it uses UTF8. + /// public Encoding Encoding { get; set; } = Encoding.UTF8; [Obsolete("Use MaxTimeout instead")] @@ -121,8 +157,6 @@ public int Timeout { /// public RemoteCertificateValidationCallback? RemoteCertificateValidationCallback { get; set; } - public string? BaseHost { get; set; } - /// /// By default, RestSharp doesn't allow multiple parameters to have the same name. /// This properly allows to override the default behavior. diff --git a/src/RestSharp/Sync/RestClientExtensions.Sync.Json.cs b/src/RestSharp/Sync/RestClientExtensions.Sync.Json.cs index 7c7b88cd2..2ee649159 100644 --- a/src/RestSharp/Sync/RestClientExtensions.Sync.Json.cs +++ b/src/RestSharp/Sync/RestClientExtensions.Sync.Json.cs @@ -14,6 +14,7 @@ // using System.Net; +using RestSharp.Authenticators; using RestSharp.Extensions; namespace RestSharp; @@ -24,12 +25,14 @@ public static partial class RestClientExtensions { /// /// RestClient instance /// Resource URL + /// Optional authenticator to use for the request /// Response object type /// Deserialized response object public static TResponse? GetJson( - this RestClient client, - string resource) - => AsyncHelpers.RunSync(() => client.GetJsonAsync(resource)); + this RestClient client, + string resource, + IAuthenticator? authenticator = null) + => AsyncHelpers.RunSync(() => client.GetJsonAsync(resource, authenticator: authenticator)); /// /// Calls the URL specified in the resource parameter, expecting a JSON response back. Deserializes and returns the response. @@ -37,13 +40,15 @@ public static partial class RestClientExtensions { /// RestClient instance /// Resource URL /// Parameters to pass to the request + /// Optional authenticator to use for the request /// Response object type /// Deserialized response object public static TResponse? GetJson( - this RestClient client, - string resource, - object parameters) - => AsyncHelpers.RunSync(() => client.GetJsonAsync(resource, parameters)); + this RestClient client, + string resource, + object parameters, + IAuthenticator? authenticator = null) + => AsyncHelpers.RunSync(() => client.GetJsonAsync(resource, parameters, authenticator: authenticator)); /// /// Serializes the request object to JSON and makes a POST call to the resource specified in the resource parameter. @@ -52,15 +57,17 @@ public static partial class RestClientExtensions { /// RestClient instance /// Resource URL /// Request object, must be serializable to JSON + /// Optional authenticator to use for the request /// Request object type /// Response object type /// Deserialized response object public static TResponse? PostJson( - this RestClient client, - string resource, - TRequest request + this RestClient client, + string resource, + TRequest request, + IAuthenticator? authenticator = null ) where TRequest : class - => AsyncHelpers.RunSync(() => client.PostJsonAsync(resource, request)); + => AsyncHelpers.RunSync(() => client.PostJsonAsync(resource, request, authenticator: authenticator)); /// /// Serializes the request object to JSON and makes a POST call to the resource specified in the resource parameter. @@ -69,14 +76,16 @@ TRequest request /// RestClient instance /// Resource URL /// Request object, must be serializable to JSON + /// Optional authenticator to use for the request /// Request object type /// Response status code public static HttpStatusCode PostJson( this RestClient client, string resource, - TRequest request + TRequest request, + IAuthenticator? authenticator = null ) where TRequest : class - => AsyncHelpers.RunSync(() => client.PostJsonAsync(resource, request)); + => AsyncHelpers.RunSync(() => client.PostJsonAsync(resource, request, authenticator: authenticator)); /// /// Serializes the request object to JSON and makes a PUT call to the resource specified in the resource parameter. @@ -85,15 +94,17 @@ TRequest request /// RestClient instance /// Resource URL /// Request object, must be serializable to JSON + /// Optional authenticator to use for the request /// Request object type /// Response object type /// Deserialized response object public static TResponse? PutJson( - this RestClient client, - string resource, - TRequest request + this RestClient client, + string resource, + TRequest request, + IAuthenticator? authenticator = null ) where TRequest : class - => AsyncHelpers.RunSync(() => client.PutJsonAsync(resource, request)); + => AsyncHelpers.RunSync(() => client.PutJsonAsync(resource, request, authenticator: authenticator)); /// /// Serializes the request object to JSON and makes a PUT call to the resource specified in the resource parameter. @@ -102,12 +113,14 @@ TRequest request /// RestClient instance /// Resource URL /// Request object, must be serializable to JSON + /// Optional authenticator to use for the request /// Request object type /// Response status code public static HttpStatusCode PutJson( this RestClient client, string resource, - TRequest request + TRequest request, + IAuthenticator? authenticator = null ) where TRequest : class - => AsyncHelpers.RunSync(() => client.PutJsonAsync(resource, request)); + => AsyncHelpers.RunSync(() => client.PutJsonAsync(resource, request, authenticator: authenticator)); } \ No newline at end of file diff --git a/test/RestSharp.InteractiveTests/AuthenticationTests.cs b/test/RestSharp.InteractiveTests/AuthenticationTests.cs index 859c70152..ba0d3ae84 100644 --- a/test/RestSharp.InteractiveTests/AuthenticationTests.cs +++ b/test/RestSharp.InteractiveTests/AuthenticationTests.cs @@ -15,14 +15,13 @@ public static async Task Can_Authenticate_With_OAuth_Async_With_Callback(Twitter var baseUrl = new Uri("https://api.twitter.com"); - var client = new RestClient(baseUrl) { + var client = new RestClient(baseUrl); + var request = new RestRequest("oauth/request_token") { Authenticator = OAuth1Authenticator.ForRequestToken( twitterKeys.ConsumerKey!, twitterKeys.ConsumerSecret, - "https://restsharp.dev" - ) + "https://restsharp.dev") }; - var request = new RestRequest("oauth/request_token"); var response = await client.ExecuteAsync(request); Assert.NotNull(response); @@ -44,15 +43,15 @@ public static async Task Can_Authenticate_With_OAuth_Async_With_Callback(Twitter Console.Write("Enter the verifier: "); var verifier = Console.ReadLine(); - request = new RestRequest("oauth/access_token"); + request = new RestRequest("oauth/access_token") { + Authenticator = OAuth1Authenticator.ForAccessToken( + twitterKeys.ConsumerKey!, + twitterKeys.ConsumerSecret, + oauthToken!, + oauthTokenSecret!, + verifier!) + }; - client.Authenticator = OAuth1Authenticator.ForAccessToken( - twitterKeys.ConsumerKey!, - twitterKeys.ConsumerSecret, - oauthToken!, - oauthTokenSecret!, - verifier! - ); response = await client.ExecuteAsync(request); Assert.NotNull(response); @@ -65,14 +64,14 @@ public static async Task Can_Authenticate_With_OAuth_Async_With_Callback(Twitter Assert.NotNull(oauthToken); Assert.NotNull(oauthTokenSecret); - request = new RestRequest("1.1/account/verify_credentials.json"); + request = new RestRequest("1.1/account/verify_credentials.json") { + Authenticator = OAuth1Authenticator.ForProtectedResource( + twitterKeys.ConsumerKey!, + twitterKeys.ConsumerSecret, + oauthToken!, + oauthTokenSecret!) + }; - client.Authenticator = OAuth1Authenticator.ForProtectedResource( - twitterKeys.ConsumerKey!, - twitterKeys.ConsumerSecret, - oauthToken!, - oauthTokenSecret! - ); response = await client.ExecuteAsync(request); Console.WriteLine($"Code: {response.StatusCode}, response: {response.Content}"); diff --git a/test/RestSharp.InteractiveTests/TwitterClient.cs b/test/RestSharp.InteractiveTests/TwitterClient.cs index e3290ceaa..e5b1c6c1a 100644 --- a/test/RestSharp.InteractiveTests/TwitterClient.cs +++ b/test/RestSharp.InteractiveTests/TwitterClient.cs @@ -24,20 +24,21 @@ public interface ITwitterClient { } public class TwitterClient : ITwitterClient, IDisposable { - readonly RestClient _client; + readonly RestClient _client; + readonly IAuthenticator _authenticator; public TwitterClient(string apiKey, string apiKeySecret) { var options = new RestClientOptions("https://api.twitter.com/2"); - _client = new RestClient(options) { - Authenticator = new TwitterAuthenticator("https://api.twitter.com", apiKey, apiKeySecret) - }; + _client = new RestClient(options); + _authenticator = new TwitterAuthenticator("https://api.twitter.com", apiKey, apiKeySecret); } public async Task GetUser(string user) { var response = await _client.GetJsonAsync>( "users/by/username/{user}", - new { user } + new { user }, + authenticator: _authenticator ); return response!.Data; } @@ -45,18 +46,24 @@ public async Task GetUser(string user) { public async Task AddSearchRules(params AddStreamSearchRule[] rules) { var response = await _client.PostJsonAsync>( "tweets/search/stream/rules", - new AddSearchRulesRequest(rules) + new AddSearchRulesRequest(rules), + authenticator: _authenticator ); return response?.Data; } public async Task GetSearchRules() { - var response = await _client.GetJsonAsync>("tweets/search/stream/rules"); + var response = await _client.GetJsonAsync>( + "tweets/search/stream/rules", + authenticator: _authenticator); return response?.Data; } public async IAsyncEnumerable SearchStream([EnumeratorCancellation] CancellationToken cancellationToken = default) { - var response = _client.StreamJsonAsync>("tweets/search/stream", cancellationToken); + var response = _client.StreamJsonAsync>( + "tweets/search/stream", + cancellationToken, + _authenticator); await foreach (var item in response.WithCancellation(cancellationToken)) { yield return item.Data; @@ -95,12 +102,11 @@ protected override async ValueTask GetAuthenticationParameter(string async Task GetToken() { var options = new RestClientOptions(_baseUrl); - using var client = new RestClient(options) { - Authenticator = new HttpBasicAuthenticator(_clientId, _clientSecret), - }; + using var client = new RestClient(options); - var request = new RestRequest("oauth2/token") - .AddParameter("grant_type", "client_credentials"); + var request = new RestRequest("oauth2/token") { + Authenticator = new HttpBasicAuthenticator(_clientId, _clientSecret), + }.AddParameter("grant_type", "client_credentials"); var response = await client.PostAsync(request); return $"{response!.TokenType} {response!.AccessToken}"; } diff --git a/test/RestSharp.Tests.Integrated/Authentication/AuthenticationTests.cs b/test/RestSharp.Tests.Integrated/Authentication/AuthenticationTests.cs index 76b62f221..14abfe9e1 100644 --- a/test/RestSharp.Tests.Integrated/Authentication/AuthenticationTests.cs +++ b/test/RestSharp.Tests.Integrated/Authentication/AuthenticationTests.cs @@ -21,10 +21,10 @@ public async Task Can_Authenticate_With_Basic_Http_Auth() { const string userName = "testuser"; const string password = "testpassword"; - var client = new RestClient(_fixture.Server.Url) { + var client = new RestClient(_fixture.Server.Url); + var request = new RestRequest("headers") { Authenticator = new HttpBasicAuthenticator(userName, password) }; - var request = new RestRequest("headers"); var response = await client.GetAsync(request); var header = response!.First(x => x.Name == KnownHeaders.Authorization); diff --git a/test/RestSharp.Tests.Integrated/Authentication/OAuth2Tests.cs b/test/RestSharp.Tests.Integrated/Authentication/OAuth2Tests.cs index a773fa2eb..792d01671 100644 --- a/test/RestSharp.Tests.Integrated/Authentication/OAuth2Tests.cs +++ b/test/RestSharp.Tests.Integrated/Authentication/OAuth2Tests.cs @@ -1,5 +1,4 @@ using RestSharp.Authenticators.OAuth2; -using RestSharp.Tests.Integrated.Fixtures; using RestSharp.Tests.Integrated.Server; namespace RestSharp.Tests.Integrated.Authentication; @@ -14,9 +13,8 @@ public class OAuth2Tests { public async Task ShouldHaveProperHeader() { var client = new RestClient(_fixture.Server.Url); var auth = new OAuth2AuthorizationRequestHeaderAuthenticator("token", "Bearer"); - client.Authenticator = auth; - var response = await client.GetJsonAsync("headers"); + var response = await client.GetJsonAsync("headers", authenticator: auth); var authHeader = response!.FirstOrDefault(x => x.Name == KnownHeaders.Authorization); authHeader.Should().NotBeNull(); diff --git a/test/RestSharp.Tests.Integrated/OAuth1Tests.cs b/test/RestSharp.Tests.Integrated/OAuth1Tests.cs index 504f9bf81..3da982f47 100644 --- a/test/RestSharp.Tests.Integrated/OAuth1Tests.cs +++ b/test/RestSharp.Tests.Integrated/OAuth1Tests.cs @@ -33,10 +33,10 @@ public async Task Can_Authenticate_Netflix_With_OAuth() { var baseUrl = new Uri("http://api.netflix.com"); - var client = new RestClient(baseUrl) { + var client = new RestClient(baseUrl); + var request = new RestRequest("oauth/request_token") { Authenticator = OAuth1Authenticator.ForRequestToken(consumerKey, consumerSecret) }; - var request = new RestRequest("oauth/request_token"); var response = await client.ExecuteAsync(request); Assert.NotNull(response); @@ -66,7 +66,7 @@ public async Task Can_Authenticate_Netflix_With_OAuth() { request = new RestRequest("oauth/access_token"); // <-- Breakpoint here, login to netflix - client.Authenticator = OAuth1Authenticator.ForAccessToken( + request.Authenticator = OAuth1Authenticator.ForAccessToken( consumerKey, consumerSecret, oauthToken, @@ -87,7 +87,7 @@ public async Task Can_Authenticate_Netflix_With_OAuth() { Assert.NotNull(oauthTokenSecret); Assert.NotNull(userId); - client.Authenticator = OAuth1Authenticator.ForProtectedResource( + request.Authenticator = OAuth1Authenticator.ForProtectedResource( consumerKey, consumerSecret, oauthToken, @@ -111,14 +111,14 @@ public async Task Can_Authenticate_LinkedIN_With_OAuth() { const string consumerSecret = "TODO_CONSUMER_SECRET_HERE"; // request token - var client = new RestClient("https://api.linkedin.com/uas/oauth") { + var client = new RestClient("https://api.linkedin.com/uas/oauth"); + var requestTokenRequest = new RestRequest("requestToken") { Authenticator = OAuth1Authenticator.ForRequestToken( consumerKey, consumerSecret, "http://localhost" ) }; - var requestTokenRequest = new RestRequest("requestToken"); var requestTokenResponse = await client.ExecuteAsync(requestTokenRequest); Assert.NotNull(requestTokenResponse); @@ -150,15 +150,15 @@ public async Task Can_Authenticate_LinkedIN_With_OAuth() { var requestTokenQueryParameters = new Uri(requestUrl).ParseQuery(); var requestVerifier = requestTokenQueryParameters["oauth_verifier"]; - client.Authenticator = OAuth1Authenticator.ForAccessToken( - consumerKey, - consumerSecret, - requestToken, - requestSecret, - requestVerifier - ); - - var requestAccessTokenRequest = new RestRequest("accessToken"); + var requestAccessTokenRequest = new RestRequest("accessToken") { + Authenticator = OAuth1Authenticator.ForAccessToken( + consumerKey, + consumerSecret, + requestToken, + requestSecret, + requestVerifier + ) + }; var requestActionTokenResponse = await client.ExecuteAsync(requestAccessTokenRequest); Assert.NotNull(requestActionTokenResponse); @@ -208,7 +208,9 @@ public async Task Can_Authenticate_Twitter() { AccessSecret = "" }; - var client = new RestClient("https://api.twitter.com/1.1") { + var client = new RestClient("https://api.twitter.com/1.1"); + + var request = new RestRequest("account/verify_credentials.json") { Authenticator = OAuth1Authenticator.ForProtectedResource( config.ConsumerKey, config.ConsumerSecret, @@ -217,8 +219,6 @@ public async Task Can_Authenticate_Twitter() { ) }; - var request = new RestRequest("account/verify_credentials.json"); - request.AddParameter("include_entities", "true", ParameterType.QueryString); var response = await client.ExecuteAsync(request); @@ -234,10 +234,10 @@ public async Task Can_Authenticate_With_OAuth() { var baseUrl = new Uri("https://api.twitter.com"); - var client = new RestClient(baseUrl) { + var client = new RestClient(baseUrl); + var request = new RestRequest("oauth/request_token", Method.Post) { Authenticator = OAuth1Authenticator.ForRequestToken(consumerKey, consumerSecret) }; - var request = new RestRequest("oauth/request_token", Method.Post); var response = await client.ExecuteAsync(request); Assert.NotNull(response); @@ -261,7 +261,7 @@ public async Task Can_Authenticate_With_OAuth() { request = new RestRequest("oauth/access_token", Method.Post); - client.Authenticator = OAuth1Authenticator.ForAccessToken( + request.Authenticator = OAuth1Authenticator.ForAccessToken( consumerKey, consumerSecret, oauthToken, @@ -282,7 +282,7 @@ public async Task Can_Authenticate_With_OAuth() { request = new RestRequest("/1.1/account/verify_credentials.json"); - client.Authenticator = OAuth1Authenticator.ForProtectedResource( + request.Authenticator = OAuth1Authenticator.ForProtectedResource( consumerKey, consumerSecret, oauthToken, @@ -301,10 +301,10 @@ public async Task Can_Query_Vimeo() { const string consumerSecret = "TODO_CONSUMER_SECRET_HERE"; // arrange - var client = new RestClient("http://vimeo.com/api/rest/v2") { + var client = new RestClient("http://vimeo.com/api/rest/v2"); + var request = new RestRequest() { Authenticator = OAuth1Authenticator.ForRequestToken(consumerKey, consumerSecret) }; - var request = new RestRequest(); request.AddParameter("format", "json"); request.AddParameter("method", "vimeo.videos.search"); @@ -332,7 +332,8 @@ public async Task Can_Retrieve_Member_Profile_Field_Field_Selector_From_LinkedIN const string accessSecret = "TODO_ACCES_SECRET_HERE"; // arrange - var client = new RestClient("http://api.linkedin.com/v1") { + var client = new RestClient("http://api.linkedin.com/v1"); + var request = new RestRequest("people/~:(id,first-name,last-name)") { Authenticator = OAuth1Authenticator.ForProtectedResource( consumerKey, consumerSecret, @@ -340,7 +341,6 @@ public async Task Can_Retrieve_Member_Profile_Field_Field_Selector_From_LinkedIN accessSecret ) }; - var request = new RestRequest("people/~:(id,first-name,last-name)"); // act var response = await client.ExecuteAsync(request); diff --git a/test/RestSharp.Tests.Integrated/RequestTests.cs b/test/RestSharp.Tests.Integrated/RequestTests.cs index b1fd86041..e407bd4f5 100644 --- a/test/RestSharp.Tests.Integrated/RequestTests.cs +++ b/test/RestSharp.Tests.Integrated/RequestTests.cs @@ -56,8 +56,8 @@ public async Task Can_Perform_GET_Async_With_Request_Cookies() { var request = new RestRequest("get-cookies") { CookieContainer = new CookieContainer() }; - request.CookieContainer.Add(new Cookie("cookie", "value", null, _client.Options.BaseUrl.Host)); - request.CookieContainer.Add(new Cookie("cookie2", "value2", null, _client.Options.BaseUrl.Host)); + request.CookieContainer.Add(new Cookie("cookie", "value", null, _client.BaseUrl.Host)); + request.CookieContainer.Add(new Cookie("cookie2", "value2", null, _client.BaseUrl.Host)); var response = await _client.ExecuteAsync(request); response.Content.Should().Be("[\"cookie=value\",\"cookie2=value2\"]"); } @@ -69,7 +69,7 @@ public async Task Can_Perform_GET_Async_With_Response_Cookies() { response.Content.Should().Be("success"); // Check we got all our cookies - var domain = _client.Options.BaseUrl.Host; + var domain = _client.BaseUrl.Host; var cookie = response.Cookies!.First(p => p.Name == "cookie1"); Assert.Equal("value1", cookie.Value); Assert.Equal("/", cookie.Path); diff --git a/test/RestSharp.Tests/JwtAuthTests.cs b/test/RestSharp.Tests/JwtAuthTests.cs index d82a5e137..c86c41319 100644 --- a/test/RestSharp.Tests/JwtAuthTests.cs +++ b/test/RestSharp.Tests/JwtAuthTests.cs @@ -23,11 +23,11 @@ public JwtAuthTests() { [Fact] public async Task Can_Set_ValidFormat_Auth_Header() { - var client = new RestClient { Authenticator = new JwtAuthenticator(_testJwt) }; - var request = new RestRequest(); + var client = new RestClient(); + var request = new RestRequest { Authenticator = new JwtAuthenticator(_testJwt) }; //In real case client.Execute(request) will invoke Authenticate method - await client.Authenticator.Authenticate(client, request); + await request.Authenticator.Authenticate(client, request); var authParam = request.Parameters.Single(p => p.Name.Equals(KnownHeaders.Authorization, StringComparison.OrdinalIgnoreCase)); @@ -37,11 +37,11 @@ public async Task Can_Set_ValidFormat_Auth_Header() { [Fact] public async Task Can_Set_ValidFormat_Auth_Header_With_Bearer_Prefix() { - var client = new RestClient { Authenticator = new JwtAuthenticator($"Bearer {_testJwt}") }; - var request = new RestRequest(); + var client = new RestClient(); + var request = new RestRequest { Authenticator = new JwtAuthenticator($"Bearer {_testJwt}") }; //In real case client.Execute(request) will invoke Authenticate method - await client.Authenticator.Authenticate(client, request); + await request.Authenticator.Authenticate(client, request); var authParam = request.Parameters.Single(p => p.Name.Equals(KnownHeaders.Authorization, StringComparison.OrdinalIgnoreCase)); @@ -51,14 +51,14 @@ public async Task Can_Set_ValidFormat_Auth_Header_With_Bearer_Prefix() { [Fact] public async Task Check_Only_Header_Authorization() { - var client = new RestClient { Authenticator = new JwtAuthenticator(_testJwt) }; - var request = new RestRequest(); + var client = new RestClient(); + var request = new RestRequest { Authenticator = new JwtAuthenticator(_testJwt) }; // Paranoid server needs "two-factor authentication": jwt header and query param key for example request.AddParameter(KnownHeaders.Authorization, "manualAuth", ParameterType.QueryString); // In real case client.Execute(request) will invoke Authenticate method - await client.Authenticator.Authenticate(client, request); + await request.Authenticator.Authenticate(client, request); var paramList = request.Parameters.Where(p => p.Name.Equals(KnownHeaders.Authorization)).ToList(); @@ -78,10 +78,10 @@ public async Task Set_Auth_Header_Only_Once() { request.AddHeader(KnownHeaders.Authorization, "second_header_auth_token"); - client.Authenticator = new JwtAuthenticator(_testJwt); + request.Authenticator = new JwtAuthenticator(_testJwt); //In real case client.Execute(...) will invoke Authenticate method - await client.Authenticator.Authenticate(client, request); + await request.Authenticator.Authenticate(client, request); var paramList = request.Parameters.Where(p => p.Name.Equals(KnownHeaders.Authorization)).ToList(); @@ -96,16 +96,38 @@ public async Task Set_Auth_Header_Only_Once() { [Fact] public async Task Updates_Auth_Header() { - var client = new RestClient(); - var request = new RestRequest(); + var client = new RestClient(); + var request = new RestRequest(); var authenticator = new JwtAuthenticator(_expectedAuthHeaderContent); - client.Authenticator = authenticator; - await client.Authenticator.Authenticate(client, request); + request.Authenticator = authenticator; + await request.Authenticator.Authenticate(client, request); authenticator.SetBearerToken("second_header_auth_token"); - await client.Authenticator.Authenticate(client, request); + await request.Authenticator.Authenticate(client, request); + + var paramList = request.Parameters.Where(p => p.Name.Equals(KnownHeaders.Authorization)).ToList(); + + Assert.Single(paramList); + + var authParam = paramList[0]; + + Assert.True(authParam.Type == ParameterType.HttpHeader); + Assert.NotEqual(_expectedAuthHeaderContent, authParam.Value); + Assert.Equal("Bearer second_header_auth_token", authParam.Value); + } + + [Fact] + public async Task Updates_Auth_Header_New_Authenticator() { + var client = new RestClient(); + var request = new RestRequest(); + + request.Authenticator = new JwtAuthenticator(_expectedAuthHeaderContent); + await request.Authenticator.Authenticate(client, request); + + request.Authenticator = new JwtAuthenticator("second_header_auth_token"); + await request.Authenticator.Authenticate(client, request); var paramList = request.Parameters.Where(p => p.Name.Equals(KnownHeaders.Authorization)).ToList(); diff --git a/test/RestSharp.Tests/RestClientTests.cs b/test/RestSharp.Tests/RestClientTests.cs index 7d7a675c2..d6adab15a 100644 --- a/test/RestSharp.Tests/RestClientTests.cs +++ b/test/RestSharp.Tests/RestClientTests.cs @@ -31,6 +31,13 @@ public void ConfigureHttp_will_set_proxy_to_null_with_no_exceptions_When_no_prox client.ExecuteAsync(req); } + [Fact] + public void BaseUrl_Returns_Url_From_Options() { + var options = new RestClientOptions(BaseUrl); + var client = new RestClient(options); + Assert.Equal(options.BaseUrl, client.BaseUrl); + } + [Fact] public void BuildUri_should_build_with_passing_link_as_Uri() { // arrange @@ -61,6 +68,20 @@ public void BuildUri_should_build_with_passing_link_as_Uri_with_set_BaseUrl() { new Uri(baseUrl, relative).Should().Be(builtUri); } + [Fact] + public void BuildUri_should_build_with_passing_link_as_Uri_with_empty_BaseUrl() { + // arrange + var absolute = new Uri($"{BaseUrl}/foo/bar/baz"); + var req = new RestRequest(absolute); + + // act + var client = new RestClient(); + var builtUri = client.BuildUri(req); + + // assert + absolute.Should().Be(builtUri); + } + [Fact] public void UseJson_leaves_only_json_serializer() { // arrange From 18bf2469af80c5ce4147782937a5008fef280a8b Mon Sep 17 00:00:00 2001 From: Kendall Bennett Date: Wed, 9 Nov 2022 18:04:30 -0500 Subject: [PATCH 2/2] Always throw exceptions in DownloadStreamAsync, as there is no other way to get the exceptions out otherwise. --- src/RestSharp/RestClient.Async.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RestSharp/RestClient.Async.cs b/src/RestSharp/RestClient.Async.cs index 820c618f9..19bddf5ad 100644 --- a/src/RestSharp/RestClient.Async.cs +++ b/src/RestSharp/RestClient.Async.cs @@ -111,7 +111,7 @@ record InternalResponse(HttpResponseMessage? ResponseMessage, Uri Url, Exception var exception = response.Exception ?? response.ResponseMessage?.MaybeException(); if (exception != null) { - return Options.ThrowOnAnyError ? throw exception : null; + throw exception; } if (response.ResponseMessage == null) return null;