8000 fix [BUG] AuthorizeAttribute does not add a header to the request #946 by BarsikV · Pull Request #979 · reactiveui/refit · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

fix [BUG] AuthorizeAttribute does not add a header to the request #946 #979

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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([Alia 10000 sAs("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)
10000 {
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