From 9ca707423aba5f1ece64cef1840c608c87ce593d Mon Sep 17 00:00:00 2001 From: Valentyn Barsehian Date: Tue, 6 Oct 2020 12:48:18 +0300 Subject: [PATCH] fix [BUG] AuthorizeAttribute does not add a header to the request #946 --- README.md | 17 +++++++++++++++-- Refit.Tests/RequestBuilder.cs | 16 ++++++++++++++++ Refit/Attributes.cs | 8 ++++++-- Refit/RequestBuilderImplementation.cs | 6 ++++++ Refit/RestMethodInfo.cs | 26 +++++++++++++++++++++++++- 5 files changed, 68 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2f786ffb1..0b07f0d0f 100644 --- a/README.md +++ b/README.md @@ -471,10 +471,23 @@ Task GetUser(string user, [Header("Authorization")] string authorization); var user = await GetUser("octocat", "token OAUTH-TOKEN"); ``` -#### Authorization (Dynamic Headers redux) +#### Dynamic authorization header with scheme The most common reason to use headers is for authorization. Today most API's use some flavor of oAuth with access tokens that expire and refresh tokens that are longer lived. -One way to encapsulate these kinds of token usage, a custom `HttpClientHandler` can be inserted instead. +If you want to set an access token at runtime and specify the authorization scheme (e.g. "Bearer"), +you can add a dynamic value to a request by applying an `Authorize` attribute to a parameter: + +```csharp +[Get("/users/{user}")] +Task GetUser(string user, [Authorize("Bearer")] string token); + +// Will add the header "Authorization: Bearer OAUTH-TOKEN}" to the request +var user = await GetUser("octocat", "OAUTH-TOKEN"); +``` + +#### Authorization (Dynamic Headers redux) + +Another way to encapsulate these kinds of token usage, a custom `HttpClientHandler` can be inserted instead. There are two classes for doing this: one is `AuthenticatedHttpClientHandler`, which takes a `Func>` parameter, where a signature can be generated without knowing about the request. The other is `AuthenticatedParameterizedHttpClientHandler`, which takes a `Func>` parameter, where the signature requires information about the request (see earlier notes about Twitter's API) diff --git a/Refit.Tests/RequestBuilder.cs b/Refit.Tests/RequestBuilder.cs index 4233e176d..2800c0d9c 100644 --- a/Refit.Tests/RequestBuilder.cs +++ b/Refit.Tests/RequestBuilder.cs @@ -50,6 +50,9 @@ public interface IRestMethodInfoTests [Post("/foo/bar/{id}")] IObservable PostSomeUrlEncodedStuff([AliasAs("id")] int anId, [Body(BodySerializationMethod.UrlEncoded)] Dictionary theData); + [Get("/foo/bar/{id}")] + IObservable FetchSomeStuffWithAuthorizationSchemeSpecified([AliasAs("id")] int anId, [Authorize("Bearer")] string token); + [Get("/foo/bar/{id}")] [Headers("Api-Version: 2 ")] Task FetchSomeStuffWithHardcodedHeaders(int id); @@ -482,6 +485,19 @@ public void FindTheBodyParameter() Assert.Equal(1, fixture.BodyParameterInfo.Item3); } + [Fact] + public void FindTheAuthorizeParameter() + { + var input = typeof(IRestMethodInfoTests); + var fixture = new RestMethodInfo(input, input.GetMethods().First(x => x.Name == nameof(IRestMethodInfoTests.FetchSomeStuffWithAuthorizationSchemeSpecified))); + Assert.Equal("id", fixture.ParameterMap[0].Name); + Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); + + Assert.NotNull(fixture.AuthorizeParameterInfo); + Assert.Empty(fixture.QueryParameterMap); + Assert.Equal(1, fixture.AuthorizeParameterInfo.Item2); + } + [Fact] public void AllowUrlEncodedContent() { diff --git a/Refit/Attributes.cs b/Refit/Attributes.cs index 44ccc4610..c8123a35a 100644 --- a/Refit/Attributes.cs +++ b/Refit/Attributes.cs @@ -207,10 +207,14 @@ public HeaderAttribute(string header) } [AttributeUsage(AttributeTargets.Parameter)] - public class AuthorizeAttribute : HeaderAttribute + public class AuthorizeAttribute : Attribute { public AuthorizeAttribute(string scheme = "Bearer") - : base("Authorization: " + scheme) { } + { + Scheme = scheme; + } + + public string Scheme { get; } } [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] // Property is to allow for form url encoded data diff --git a/Refit/RequestBuilderImplementation.cs b/Refit/RequestBuilderImplementation.cs index 29bdf9742..8f6e3f3c2 100644 --- a/Refit/RequestBuilderImplementation.cs +++ b/Refit/RequestBuilderImplementation.cs @@ -578,6 +578,12 @@ Func> BuildRequestFactoryForMethod(RestMethod isParameterMappedToRequest = true; } + //if authorize, add to request headers with scheme + if (restMethod.AuthorizeParameterInfo != null && restMethod.AuthorizeParameterInfo.Item2 == i) + { + headersToAdd["Authorization"] = $"{restMethod.AuthorizeParameterInfo.Item1} {param}"; + } + // ignore nulls and already processed parameters if (isParameterMappedToRequest || param == null) continue; diff --git a/Refit/RestMethodInfo.cs b/Refit/RestMethodInfo.cs index 2bf5710f1..9e2753908 100644 --- a/Refit/RestMethodInfo.cs +++ b/Refit/RestMethodInfo.cs @@ -25,6 +25,7 @@ public class RestMethodInfo public Dictionary Headers { get; set; } public Dictionary HeaderParameterMap { get; set; } public Tuple BodyParameterInfo { get; set; } + public Tuple AuthorizeParameterInfo { get; set; } public Dictionary QueryParameterMap { get; set; } public Dictionary> AttachmentNameMap { get; set; } public Dictionary ParameterInfoMap { get; set; } @@ -70,6 +71,7 @@ public RestMethodInfo(Type targetInterface, MethodInfo methodInfo, RefitSettings .ToDictionary(x => x.index, x => x.parameter); ParameterMap = BuildParameterMap(RelativePath, parameterList); BodyParameterInfo = FindBodyParameter(parameterList, IsMultipart, hma.Method); + AuthorizeParameterInfo = FindAuthorizationParameter(parameterList); Headers = ParseHeaders(methodInfo); HeaderParameterMap = BuildHeaderParameterMap(parameterList); @@ -98,7 +100,8 @@ public RestMethodInfo(Type targetInterface, MethodInfo methodInfo, RefitSettings { if (ParameterMap.ContainsKey(i) || HeaderParameterMap.ContainsKey(i) || - (BodyParameterInfo != null && BodyParameterInfo.Item3 == i)) + (BodyParameterInfo != null && BodyParameterInfo.Item3 == i) || + (AuthorizeParameterInfo != null && AuthorizeParameterInfo.Item2 == i)) { continue; } @@ -296,6 +299,27 @@ Tuple FindBodyParameter(IList return null; } + Tuple FindAuthorizationParameter(IList parameterList) + { + var authorizeParams = parameterList + .Select(x => new { Parameter = x, AuthorizeAttribute = x.GetCustomAttributes(true).OfType().FirstOrDefault() }) + .Where(x => x.AuthorizeAttribute != null) + .ToList(); + + if (authorizeParams.Count > 1) + { + throw new ArgumentException("Only one parameter can be an Authorize parameter"); + } + + if (authorizeParams.Count == 1) + { + var ret = authorizeParams[0]; + return Tuple.Create(ret.AuthorizeAttribute.Scheme, parameterList.IndexOf(ret.Parameter)); + } + + return null; + } + Dictionary ParseHeaders(MethodInfo methodInfo) { var ret = new Dictionary();