Skip to content

Commit

Permalink
fix [BUG] AuthorizeAttribute does not add a header to the request rea…
Browse files Browse the repository at this point in the history
  • Loading branch information
BarsikV committed Oct 6, 2020
1 parent 890ea5d commit 9ca7074
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 5 deletions.
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -471,10 +471,23 @@ Task<User> 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<User> 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<Task<string>>` parameter, where a signature can be generated without knowing about the request.
The other is `AuthenticatedParameterizedHttpClientHandler`, which takes a `Func<HttpRequestMessage, Task<string>>` parameter, where the signature requires information about the request (see earlier notes about Twitter's API)

Expand Down
16 changes: 16 additions & 0 deletions Refit.Tests/RequestBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ public interface IRestMethodInfoTests
[Post("/foo/bar/{id}")]
IObservable<string> PostSomeUrlEncodedStuff([AliasAs("id")] int anId, [Body(BodySerializationMethod.UrlEncoded)] Dictionary<string, string> theData);

[Get("/foo/bar/{id}")]
IObservable<string> FetchSomeStuffWithAuthorizationSchemeSpecified([AliasAs("id")] int anId, [Authorize("Bearer")] string token);

[Get("/foo/bar/{id}")]
[Headers("Api-Version: 2 ")]
Task<string> FetchSomeStuffWithHardcodedHeaders(int id);
Expand Down Expand Up @@ -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()
{
Expand Down
8 changes: 6 additions & 2 deletions Refit/Attributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions Refit/RequestBuilderImplementation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,12 @@ Func<object[], Task<HttpRequestMessage>> 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;

Expand Down
26 changes: 25 additions & 1 deletion Refit/RestMethodInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class RestMethodInfo
public Dictionary<string, string> Headers { get; set; }
public Dictionary<int, string> HeaderParameterMap { get; set; }
public Tuple<BodySerializationMethod, bool, int> BodyParameterInfo { get; set; }
public Tuple<string, int> AuthorizeParameterInfo { get; set; }
public Dictionary<int, string> QueryParameterMap { get; set; }
public Dictionary<int, Tuple<string, string>> AttachmentNameMap { get; set; }
public Dictionary<int, ParameterInfo> ParameterInfoMap { get; set; }
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -296,6 +299,27 @@ Tuple<BodySerializationMethod, bool, int> FindBodyParameter(IList<ParameterInfo>
return null;
}

Tuple<string, int> FindAuthorizationParameter(IList<ParameterInfo> parameterList)
{
var authorizeParams = parameterList
.Select(x => new { Parameter = x, AuthorizeAttribute = x.GetCustomAttributes(true).OfType<AuthorizeAttribute>().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<string, string> ParseHeaders(MethodInfo methodInfo)
{
var ret = new Dictionary<string, string>();
Expand Down

0 comments on commit 9ca7074

Please sign in to comment.