{"action":"created","comment":{"url":"https://api.github.com/repos/aspnet/Security/pulls/comments/181453156","pull_request_review_id":112089847,"id":181453156,"diff_hunk":"@@ -148,53 +149,52 @@ public TwitterHandler(IOptionsMonitor<TwitterOptions> options, ILoggerFactory lo\n             await Events.RedirectToAuthorizationEndpoint(redirectContext);\n         }\n \n-        private async Task<RequestToken> ObtainRequestTokenAsync(string callBackUri, AuthenticationProperties properties)\n+        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)\n         {\n-            Logger.ObtainRequestToken();\n-\n-            var nonce = Guid.NewGuid().ToString(\"N\");\n+            var canonicalizedRequestBuilder = new StringBuilder();\n+            canonicalizedRequestBuilder.Append(httpMethod.Method);\n+            canonicalizedRequestBuilder.Append(\"&\");\n+            canonicalizedRequestBuilder.Append(Uri.EscapeDataString(url));\n \n-            var authorizationParts = new SortedDictionary<string, string>\n+            var authorizationParts = new SortedDictionary<string, string>(extraOAuthPairs ?? new Dictionary<string, string>())\n             {\n-                { \"oauth_callback\", callBackUri },\n                 { \"oauth_consumer_key\", Options.ConsumerKey },\n-                { \"oauth_nonce\", nonce },\n+                { \"oauth_nonce\", Guid.NewGuid().ToString(\"N\") },\n                 { \"oauth_signature_method\", \"HMAC-SHA1\" },\n                 { \"oauth_timestamp\", GenerateTimeStamp() },\n                 { \"oauth_version\", \"1.0\" }\n             };\n \n-            var parameterBuilder = new StringBuilder();\n-            foreach (var authorizationKey in authorizationParts)\n+            if (accessToken != null)\n             {\n-                parameterBuilder.AppendFormat(\"{0}={1}&\", UrlEncoder.Encode(authorizationKey.Key), UrlEncoder.Encode(authorizationKey.Value));\n+                authorizationParts.Add(\"oauth_token\", accessToken.Token);\n             }\n-            parameterBuilder.Length--;\n-            var parameterString = parameterBuilder.ToString();\n \n-            var canonicalizedRequestBuilder = new StringBuilder();\n-            canonicalizedRequestBuilder.Append(HttpMethod.Post.Method);\n-            canonicalizedRequestBuilder.Append(\"&\");\n-            canonicalizedRequestBuilder.Append(UrlEncoder.Encode(RequestTokenEndpoint));\n+            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));\n+            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));\n             canonicalizedRequestBuilder.Append(\"&\");\n-            canonicalizedRequestBuilder.Append(UrlEncoder.Encode(parameterString));\n+            canonicalizedRequestBuilder.Append(Uri.EscapeDataString(parameterString));\n \n-            var signature = ComputeSignature(Options.ConsumerSecret, null, canonicalizedRequestBuilder.ToString());\n+            var signature = ComputeSignature(Options.ConsumerSecret, accessToken?.TokenSecret, canonicalizedRequestBuilder.ToString());\n             authorizationParts.Add(\"oauth_signature\", signature);\n \n-            var authorizationHeaderBuilder = new StringBuilder();\n-            authorizationHeaderBuilder.Append(\"OAuth \");\n-            foreach (var authorizationPart in authorizationParts)\n+            var queryString = (queryParameters != null && queryParameters.Any() ? \"?\" + queryParameters.Select(x => x.Key + \"=\" + x.Value).Aggregate((x, y) => x + \"&\" + y) : \"\");\n+            var request = new HttpRequestMessage(httpMethod, url + queryString);\n+            request.Headers.Add(\"Authorization\", \"OAuth \" + authorizationParts.Select(x => string.Format(\"{0}=\\\"{1}\\\"\", x.Key, Uri.EscapeDataString(x.Value))).Aggregate((x, y) => string.Format(\"{0},{1}\", x, y)));\n+\n+            if (formData != null)\n             {\n-                authorizationHeaderBuilder.AppendFormat(\n-                    \"{0}=\\\"{1}\\\", \", authorizationPart.Key, UrlEncoder.Encode(authorizationPart.Value));\n+                request.Content = new FormUrlEncodedContent(formData);\n             }\n-            authorizationHeaderBuilder.Length = authorizationHeaderBuilder.Length - 2;\n \n-            var request = new HttpRequestMessage(HttpMethod.Post, RequestTokenEndpoint);\n-            request.Headers.Add(\"Authorization\", authorizationHeaderBuilder.ToString());\n+            return await Backchannel.SendAsync(request, Context.RequestAborted);\n+        }\n+\n+        private async Task<RequestToken> ObtainRequestTokenAsync(string callBackUri, AuthenticationProperties properties)\n+        {\n+            Logger.ObtainRequestToken();\n \n-            var response = await Backchannel.SendAsync(request, Context.RequestAborted);\n+            var response = await ExecuteRequestAsync(RequestTokenEndpoint, HttpMethod.Post, null, new Dictionary<string, string>() { { \"oauth_callback\", callBackUri } });","path":"src/Microsoft.AspNetCore.Authentication.Twitter/TwitterHandler.cs","position":93,"original_position":93,"commit_id":"941414c6b1e9856064969fdcf5f4b76c6b8db09e","original_commit_id":"941414c6b1e9856064969fdcf5f4b76c6b8db09e","user":{"login":"Tratcher","id":1821173,"avatar_url":"https://avatars3.githubusercontent.com/u/1821173?v=4","gravatar_id":"","url":"https://api.github.com/users/Tratcher","html_url":"https://github.com/Tratcher","followers_url":"https://api.github.com/users/Tratcher/followers","following_url":"https://api.github.com/users/Tratcher/following{/other_user}","gists_url":"https://api.github.com/users/Tratcher/gists{/gist_id}","starred_url":"https://api.github.com/users/Tratcher/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Tratcher/subscriptions","organizations_url":"https://api.github.com/users/Tratcher/orgs","repos_url":"https://api.github.com/users/Tratcher/repos","events_url":"https://api.github.com/users/Tratcher/events{/privacy}","received_events_url":"https://api.github.com/users/Tratcher/received_events","type":"User","site_admin":false},"body":"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.","created_at":"2018-04-13T17:11:08Z","updated_at":"2018-04-13T17:19:14Z","html_url":"https://github.com/aspnet/Security/pull/1720#discussion_r181453156","pull_request_url":"https://api.github.com/repos/aspnet/Security/pulls/1720","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/aspnet/Security/pulls/comments/181453156"},"html":{"href":"https://github.com/aspnet/Security/pull/1720#discussion_r181453156"},"pull_request":{"href":"https://api.github.com/repos/aspnet/Security/pulls/1720"}}},"pull_request":{"url":"https://api.github.com/repos/aspnet/Security/pulls/1720","id":179949937,"html_url":"https://github.com/aspnet/Security/pull/1720","diff_url":"https://github.com/aspnet/Security/pull/1720.diff","patch_url":"https://github.com/aspnet/Security/pull/1720.patch","issue_url":"https://api.github.com/repos/aspnet/Security/issues/1720","number":1720,"state":"open","locked":false,"title":"Correct Twitter OAuth signing code. Also, refactor copy/pasted code & make reusable TwitterHandler.ExecuteRequestAsync public","user":{"login":"speige","id":2002030,"avatar_url":"https://avatars1.githubusercontent.com/u/2002030?v=4","gravatar_id":"","url":"https://api.github.com/users/speige","html_url":"https://github.com/speige","followers_url":"https://api.github.com/users/speige/followers","following_url":"https://api.github.com/users/speige/following{/other_user}","gists_url":"https://api.github.com/users/speige/gists{/gist_id}","starred_url":"https://api.github.com/users/speige/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/speige/subscriptions","organizations_url":"https://api.github.com/users/speige/orgs","repos_url":"https://api.github.com/users/speige/repos","events_url":"https://api.github.com/users/speige/events{/privacy}","received_events_url":"https://api.github.com/users/speige/received_events","type":"User","site_admin":false},"body":"https://github.com/aspnet/Security/issues/1695\r\n\r\nTested in my app & working","created_at":"2018-04-06T13:34:00Z","updated_at":"2018-04-13T17:19:14Z","closed_at":null,"merged_at":null,"merge_commit_sha":"a65a68416efd622ed48346d843e7f8af2f358414","assignee":{"login":"Tratcher","id":1821173,"avatar_url":"https://avatars3.githubusercontent.com/u/1821173?v=4","gravatar_id":"","url":"https://api.github.com/users/Tratcher","html_url":"https://github.com/Tratcher","followers_url":"https://api.github.com/users/Tratcher/followers","following_url":"https://api.github.com/users/Tratcher/following{/other_user}","gists_url":"https://api.github.com/users/Tratcher/gists{/gist_id}","starred_url":"https://api.github.com/users/Tratcher/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Tratcher/subscriptions","organizations_url":"https://api.github.com/users/Tratcher/orgs","repos_url":"https://api.github.com/users/Tratcher/repos","events_url":"https://api.github.com/users/Tratcher/events{/privacy}","received_events_url":"https://api.github.com/users/Tratcher/received_events","type":"User","site_admin":false},"assignees":[{"login":"Tratcher","id":1821173,"avatar_url":"https://avatars3.githubusercontent.com/u/1821173?v=4","gravatar_id":"","url":"https://api.github.com/users/Tratcher","html_url":"https://github.com/Tratcher","followers_url":"https://api.github.com/users/Tratcher/followers","following_url":"https://api.github.com/users/Tratcher/following{/other_user}","gists_url":"https://api.github.com/users/Tratcher/gists{/gist_id}","starred_url":"https://api.github.com/users/Tratcher/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Tratcher/subscriptions","organizations_url":"https://api.github.com/users/Tratcher/orgs","repos_url":"https://api.github.com/users/Tratcher/repos","events_url":"https://api.github.com/users/Tratcher/events{/privacy}","received_events_url":"https://api.github.com/users/Tratcher/received_events","type":"User","site_admin":false}],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/aspnet/Security/pulls/1720/commits","review_comments_url":"https://api.github.com/repos/aspnet/Security/pulls/1720/comments","review_comment_url":"https://api.github.com/repos/aspnet/Security/pulls/comments{/number}","comments_url":"https://api.github.com/repos/aspnet/Security/issues/1720/comments","statuses_url":"https://api.github.com/repos/aspnet/Security/statuses/941414c6b1e9856064969fdcf5f4b76c6b8db09e","head":{"label":"speige:dev","ref":"dev","sha":"941414c6b1e9856064969fdcf5f4b76c6b8db09e","user":{"login":"speige","id":2002030,"avatar_url":"https://avatars1.githubusercontent.com/u/2002030?v=4","gravatar_id":"","url":"https://api.github.com/users/speige","html_url":"https://github.com/speige","followers_url":"https://api.github.com/users/speige/followers","following_url":"https://api.github.com/users/speige/following{/other_user}","gists_url":"https://api.github.com/users/speige/gists{/gist_id}","starred_url":"https://api.github.com/users/speige/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/speige/subscriptions","organizations_url":"https://api.github.com/users/speige/orgs","repos_url":"https://api.github.com/users/speige/repos","events_url":"https://api.github.com/users/speige/events{/privacy}","received_events_url":"https://api.github.com/users/speige/received_events","type":"User","site_admin":false},"repo":{"id":128271340,"name":"Security","full_name":"speige/Security","owner":{"login":"speige","id":2002030,"avatar_url":"https://avatars1.githubusercontent.com/u/2002030?v=4","gravatar_id":"","url":"https://api.github.com/users/speige","html_url":"https://github.com/speige","followers_url":"https://api.github.com/users/speige/followers","following_url":"https://api.github.com/users/speige/following{/other_user}","gists_url":"https://api.github.com/users/speige/gists{/gist_id}","starred_url":"https://api.github.com/users/speige/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/speige/subscriptions","organizations_url":"https://api.github.com/users/speige/orgs","repos_url":"https://api.github.com/users/speige/repos","events_url":"https://api.github.com/users/speige/events{/privacy}","received_events_url":"https://api.github.com/users/speige/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/speige/Security","description":"Middleware for security and authorization of web apps.","fork":true,"url":"https://api.github.com/repos/speige/Security","forks_url":"https://api.github.com/repos/speige/Security/forks","keys_url":"https://api.github.com/repos/speige/Security/keys{/key_id}","collaborators_url":"https://api.github.com/repos/speige/Security/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/speige/Security/teams","hooks_url":"https://api.github.com/repos/speige/Security/hooks","issue_events_url":"https://api.github.com/repos/speige/Security/issues/events{/number}","events_url":"https://api.github.com/repos/speige/Security/events","assignees_url":"https://api.github.com/repos/speige/Security/assignees{/user}","branches_url":"https://api.github.com/repos/speige/Security/branches{/branch}","tags_url":"https://api.github.com/repos/speige/Security/tags","blobs_url":"https://api.github.com/repos/speige/Security/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/speige/Security/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/speige/Security/git/refs{/sha}","trees_url":"https://api.github.com/repos/speige/Security/git/trees{/sha}","statuses_url":"https://api.github.com/repos/speige/Security/statuses/{sha}","languages_url":"https://api.github.com/repos/speige/Security/languages","stargazers_url":"https://api.github.com/repos/speige/Security/stargazers","contributors_url":"https://api.github.com/repos/speige/Security/contributors","subscribers_url":"https://api.github.com/repos/speige/Security/subscribers","subscription_url":"https://api.github.com/repos/speige/Security/subscription","commits_url":"https://api.github.com/repos/speige/Security/commits{/sha}","git_commits_url":"https://api.github.com/repos/speige/Security/git/commits{/sha}","comments_url":"https://api.github.com/repos/speige/Security/comments{/number}","issue_comment_url":"https://api.github.com/repos/speige/Security/issues/comments{/number}","contents_url":"https://api.github.com/repos/speige/Security/contents/{+path}","compare_url":"https://api.github.com/repos/speige/Security/compare/{base}...{head}","merges_url":"https://api.github.com/repos/speige/Security/merges","archive_url":"https://api.github.com/repos/speige/Security/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/speige/Security/downloads","issues_url":"https://api.github.com/repos/speige/Security/issues{/number}","pulls_url":"https://api.github.com/repos/speige/Security/pulls{/number}","milestones_url":"https://api.github.com/repos/speige/Security/milestones{/number}","notifications_url":"https://api.github.com/repos/speige/Security/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/speige/Security/labels{/name}","releases_url":"https://api.github.com/repos/speige/Security/releases{/id}","deployments_url":"https://api.github.com/repos/speige/Security/deployments","created_at":"2018-04-05T22:30:52Z","updated_at":"2018-04-07T16:56:39Z","pushed_at":"2018-04-07T16:56:36Z","git_url":"git://github.com/speige/Security.git","ssh_url":"git@github.com:speige/Security.git","clone_url":"https://github.com/speige/Security.git","svn_url":"https://github.com/speige/Security","homepage":"","size":4303,"stargazers_count":0,"watchers_count":0,"language":"C#","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"other","name":"Other","spdx_id":null,"url":null},"forks":0,"open_issues":0,"watchers":0,"default_branch":"dev"}},"base":{"label":"aspnet:dev","ref":"dev","sha":"e98a0d243a7a5d8076ab85c3438739118cdd53ff","user":{"login":"aspnet","id":6476660,"avatar_url":"https://avatars3.githubusercontent.com/u/6476660?v=4","gravatar_id":"","url":"https://api.github.com/users/aspnet","html_url":"https://github.com/aspnet","followers_url":"https://api.github.com/users/aspnet/followers","following_url":"https://api.github.com/users/aspnet/following{/other_user}","gists_url":"https://api.github.com/users/aspnet/gists{/gist_id}","starred_url":"https://api.github.com/users/aspnet/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aspnet/subscriptions","organizations_url":"https://api.github.com/users/aspnet/orgs","repos_url":"https://api.github.com/users/aspnet/repos","events_url":"https://api.github.com/users/aspnet/events{/privacy}","received_events_url":"https://api.github.com/users/aspnet/received_events","type":"Organization","site_admin":false},"repo":{"id":17723699,"name":"Security","full_name":"aspnet/Security","owner":{"login":"aspnet","id":6476660,"avatar_url":"https://avatars3.githubusercontent.com/u/6476660?v=4","gravatar_id":"","url":"https://api.github.com/users/aspnet","html_url":"https://github.com/aspnet","followers_url":"https://api.github.com/users/aspnet/followers","following_url":"https://api.github.com/users/aspnet/following{/other_user}","gists_url":"https://api.github.com/users/aspnet/gists{/gist_id}","starred_url":"https://api.github.com/users/aspnet/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aspnet/subscriptions","organizations_url":"https://api.github.com/users/aspnet/orgs","repos_url":"https://api.github.com/users/aspnet/repos","events_url":"https://api.github.com/users/aspnet/events{/privacy}","received_events_url":"https://api.github.com/users/aspnet/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/aspnet/Security","description":"Middleware for security and authorization of web apps.","fork":false,"url":"https://api.github.com/repos/aspnet/Security","forks_url":"https://api.github.com/repos/aspnet/Security/forks","keys_url":"https://api.github.com/repos/aspnet/Security/keys{/key_id}","collaborators_url":"https://api.github.com/repos/aspnet/Security/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/aspnet/Security/teams","hooks_url":"https://api.github.com/repos/aspnet/Security/hooks","issue_events_url":"https://api.github.com/repos/aspnet/Security/issues/events{/number}","events_url":"https://api.github.com/repos/aspnet/Security/events","assignees_url":"https://api.github.com/repos/aspnet/Security/assignees{/user}","branches_url":"https://api.github.com/repos/aspnet/Security/branches{/branch}","tags_url":"https://api.github.com/repos/aspnet/Security/tags","blobs_url":"https://api.github.com/repos/aspnet/Security/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/aspnet/Security/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/aspnet/Security/git/refs{/sha}","trees_url":"https://api.github.com/repos/aspnet/Security/git/trees{/sha}","statuses_url":"https://api.github.com/repos/aspnet/Security/statuses/{sha}","languages_url":"https://api.github.com/repos/aspnet/Security/languages","stargazers_url":"https://api.github.com/repos/aspnet/Security/stargazers","contributors_url":"https://api.github.com/repos/aspnet/Security/contributors","subscribers_url":"https://api.github.com/repos/aspnet/Security/subscribers","subscription_url":"https://api.github.com/repos/aspnet/Security/subscription","commits_url":"https://api.github.com/repos/aspnet/Security/commits{/sha}","git_commits_url":"https://api.github.com/repos/aspnet/Security/git/commits{/sha}","comments_url":"https://api.github.com/repos/aspnet/Security/comments{/number}","issue_comment_url":"https://api.github.com/repos/aspnet/Security/issues/comments{/number}","contents_url":"https://api.github.com/repos/aspnet/Security/contents/{+path}","compare_url":"https://api.github.com/repos/aspnet/Security/compare/{base}...{head}","merges_url":"https://api.github.com/repos/aspnet/Security/merges","archive_url":"https://api.github.com/repos/aspnet/Security/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/aspnet/Security/downloads","issues_url":"https://api.github.com/repos/aspnet/Security/issues{/number}","pulls_url":"https://api.github.com/repos/aspnet/Security/pulls{/number}","milestones_url":"https://api.github.com/repos/aspnet/Security/milestones{/number}","notifications_url":"https://api.github.com/repos/aspnet/Security/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/aspnet/Security/labels{/name}","releases_url":"https://api.github.com/repos/aspnet/Security/releases{/id}","deployments_url":"https://api.github.com/repos/aspnet/Security/deployments","created_at":"2014-03-13T19:44:34Z","updated_at":"2018-04-13T14:35:32Z","pushed_at":"2018-04-10T16:43:18Z","git_url":"git://github.com/aspnet/Security.git","ssh_url":"git@github.com:aspnet/Security.git","clone_url":"https://github.com/aspnet/Security.git","svn_url":"https://github.com/aspnet/Security","homepage":"","size":4301,"stargazers_count":884,"watchers_count":884,"language":"C#","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":418,"mirror_url":null,"archived":false,"open_issues_count":74,"license":{"key":"other","name":"Other","spdx_id":null,"url":null},"forks":418,"open_issues":74,"watchers":884,"default_branch":"dev"}},"_links":{"self":{"href":"https://api.github.com/repos/aspnet/Security/pulls/1720"},"html":{"href":"https://github.com/aspnet/Security/pull/1720"},"issue":{"href":"https://api.github.com/repos/aspnet/Security/issues/1720"},"comments":{"href":"https://api.github.com/repos/aspnet/Security/issues/1720/comments"},"review_comments":{"href":"https://api.github.com/repos/aspnet/Security/pulls/1720/comments"},"review_comment":{"href":"https://api.github.com/repos/aspnet/Security/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/aspnet/Security/pulls/1720/commits"},"statuses":{"href":"https://api.github.com/repos/aspnet/Security/statuses/941414c6b1e9856064969fdcf5f4b76c6b8db09e"}},"author_association":"NONE"}}
{"actor":{"display_login":"Tratcher"}}