-
Notifications
You must be signed in to change notification settings - Fork 586
Correct Twitter OAuth signing code. Also, refactor copy/pasted code & make reusable TwitterHandler.ExecuteRequestAsync public #1720
Conversation
Tratcher
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before I review this too closely, please add a sample or test showing how you expect the new public APIs to be called from an application. Getting access to an instance of the handler is non-trivial.
| namespace Microsoft.AspNetCore.Authentication.Twitter | ||
| { | ||
| public class TwitterHandler : RemoteAuthenticationHandler<TwitterOptions> | ||
| public class TwitterHandler : RemoteAuthenticationHandler<TwitterOptions> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spacing changed? Revert.
|
|
||
| public async Task<HttpResponseMessage> ExecuteRequestAsync(string url, HttpMethod httpMethod, RequestToken accessToken = null, Dictionary<string, string> extraOAuthPairs = null, Dictionary<string, string> queryParameters = null, Dictionary<string, string> formData = null) | ||
| { | ||
| StringBuilder canonicalizedRequestBuilder = new StringBuilder(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also use var everywhere in the new code.
|
Accidental whitespace changes removed |
|
updated based on request to use "var" instead of specific types |
|
@Tratcher To get an instance of TwitterHandler via the constructor is painful. However, with DI it's not too bad. I'm use .net core with SimpleInjector instead of the base DI. You have to use CrossWire to make classes registered with the built-in DI available to your custom classes. I assume it's trivial with the built-in DI, so I'm leaving out the DI code itself. After DI of IHttpContextAccessor & AuthenticationHandlerProvider. You could also DI TwitterHandler, but the Scheme won't be set, so you have to call InitializeAsync (which also requires DI of HttpContext). |
|
Yeah, that's about what I'd expect. Can you add something to https://github.com/aspnet/Security/blob/dev/test/Microsoft.AspNetCore.Authentication.Test/TwitterTests.cs and/or https://github.com/aspnet/Security/blob/dev/samples/SocialSample/Startup.cs? We don't want to have any public APIs that we don't call, we'll never know if they break. |
|
@Tratcher I'm confused. Pretty much every test in TwitterTests.cs already calls ExecuteRequestAsync. If you add a breakpoint to the method & debug unit tests, you'll see it. I'm kindof braindead when it comes to unit testing, so I'm not confident I can write a test that will be satisfactory. Would you mind coding that portion if it's important for the pull request to be accepted? Sorry :( |
|
Sorry, I meant that shows what it's like to call the API directly like an end user would. |
|
Makes sense. I'll throw something together. |
|
Having public APIs in the handlers classes reminds me of a cool story about Pandora and a Box... IMHO, pointing folks to user-friendly Twitter clients like https://github.com/JoeMayo/LinqToTwitter or https://github.com/linvi/tweetinvi would be much better than exposing an obscure method in the handler itself (both will take care of generating the |
|
I'm not opposed to leaving the method private if it raises concerns. The initial intent of the pull request was because the current signing method did not match the spec. However, in that case, why have aspnet/Security implement the signing code itself instead of also referencing a 3rd party library? |
|
Let's keep it private for now because it might otherwise be difficult code to maintain forever. |
Tratcher
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has a few tradeoffs that need to be worked out. I like how you've reduced the duplication, but some of the refactors have also reduced the readability and efficiency.
| Logger.ObtainRequestToken(); | ||
|
|
||
| var response = await Backchannel.SendAsync(request, Context.RequestAborted); | ||
| var response = await ExecuteRequestAsync(RequestTokenEndpoint, HttpMethod.Post, null, new Dictionary<string, string>() { { "oauth_callback", callBackUri } }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can drop the null param, it has a default, and use a named parameter for the last param. The same goes for the other calls to ExecuteRequestAsync.
| } | ||
|
|
||
| private async Task<RequestToken> ObtainRequestTokenAsync(string callBackUri, AuthenticationProperties properties) | ||
| public async Task<HttpResponseMessage> ExecuteRequestAsync(string url, HttpMethod httpMethod, RequestToken accessToken = null, Dictionary<string, string> extraOAuthPairs = null, Dictionary<string, string> queryParameters = null, Dictionary<string, string> formData = null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's make this private for now.
Wrap long lines over ~160 chars
| canonicalizedRequestBuilder.Append("&"); | ||
| canonicalizedRequestBuilder.Append(UrlEncoder.Encode(RequestTokenEndpoint)); | ||
| var signatureParts = new SortedDictionary<string, string>((queryParameters ?? new Dictionary<string, string>()).Union(authorizationParts).Union(formData ?? new Dictionary<string, string>()).ToDictionary(x => x.Key, x => x.Value)); | ||
| string parameterString = signatureParts.Select(x => string.Format("{0}={1}", Uri.EscapeDataString(x.Key), Uri.EscapeDataString(x.Value))).Aggregate((x, y) => string.Format("{0}&{1}", x, y)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please revert the complex linq expressions. It reduces readability and code efficiency compared to the StringBuilder pattern that was used before.
| canonicalizedRequestBuilder.Append(HttpMethod.Post.Method); | ||
| canonicalizedRequestBuilder.Append("&"); | ||
| canonicalizedRequestBuilder.Append(UrlEncoder.Encode(RequestTokenEndpoint)); | ||
| var signatureParts = new SortedDictionary<string, string>((queryParameters ?? new Dictionary<string, string>()).Union(authorizationParts).Union(formData ?? new Dictionary<string, string>()).ToDictionary(x => x.Key, x => x.Value)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the one linq expression that it may make sense to keep, but it needs a more readable layout rather than stringing it out all on one line.
|
Thanks for the help |
#1695
Tested in my app & working