From ea92cc09b4e1861348fa797ddc7531a7cd108101 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Sat, 11 Nov 2017 19:06:37 -0500 Subject: [PATCH] Adds the github login prompt --- TGControlPanel/App.config | 6 + TGControlPanel/GithubLoginPrompt.Designer.cs | 176 ++++++++++++++++++ TGControlPanel/GithubLoginPrompt.cs | 98 ++++++++++ TGControlPanel/GithubLoginPrompt.resx | 120 ++++++++++++ .../Properties/Settings.Designer.cs | 26 ++- TGControlPanel/Properties/Settings.settings | 6 + TGControlPanel/TGControlPanel.csproj | 9 + TGControlPanel/TestMergeManager.cs | 132 +++++++------ 8 files changed, 515 insertions(+), 58 deletions(-) create mode 100644 TGControlPanel/GithubLoginPrompt.Designer.cs create mode 100644 TGControlPanel/GithubLoginPrompt.cs create mode 100644 TGControlPanel/GithubLoginPrompt.resx diff --git a/TGControlPanel/App.config b/TGControlPanel/App.config index 4482a045a4..16dd5b3a27 100644 --- a/TGControlPanel/App.config +++ b/TGControlPanel/App.config @@ -44,6 +44,12 @@ + + + + + + diff --git a/TGControlPanel/GithubLoginPrompt.Designer.cs b/TGControlPanel/GithubLoginPrompt.Designer.cs new file mode 100644 index 0000000000..04df96ab1a --- /dev/null +++ b/TGControlPanel/GithubLoginPrompt.Designer.cs @@ -0,0 +1,176 @@ +namespace TGControlPanel +{ + partial class GitHubLoginPrompt + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.PasswordLabel = new System.Windows.Forms.Label(); + this.UsernameLabel = new System.Windows.Forms.Label(); + this.PasswordTextBox = new System.Windows.Forms.TextBox(); + this.UsernameTextBox = new System.Windows.Forms.TextBox(); + this.DividerLabel = new System.Windows.Forms.Label(); + this.APIKeyLabel = new System.Windows.Forms.Label(); + this.APIKeyTextBox = new System.Windows.Forms.TextBox(); + this.OrLabel = new System.Windows.Forms.Label(); + this.LoginButton = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // PasswordLabel + // + this.PasswordLabel.AutoSize = true; + this.PasswordLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.PasswordLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.PasswordLabel.Location = new System.Drawing.Point(12, 41); + this.PasswordLabel.Name = "PasswordLabel"; + this.PasswordLabel.Size = new System.Drawing.Size(92, 18); + this.PasswordLabel.TabIndex = 25; + this.PasswordLabel.Text = "Password:"; + this.PasswordLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // UsernameLabel + // + this.UsernameLabel.AutoSize = true; + this.UsernameLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.UsernameLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.UsernameLabel.Location = new System.Drawing.Point(12, 9); + this.UsernameLabel.Name = "UsernameLabel"; + this.UsernameLabel.Size = new System.Drawing.Size(97, 18); + this.UsernameLabel.TabIndex = 24; + this.UsernameLabel.Text = "Username:"; + this.UsernameLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // PasswordTextBox + // + this.PasswordTextBox.Location = new System.Drawing.Point(132, 41); + this.PasswordTextBox.Name = "PasswordTextBox"; + this.PasswordTextBox.Size = new System.Drawing.Size(233, 20); + this.PasswordTextBox.TabIndex = 23; + this.PasswordTextBox.UseSystemPasswordChar = true; + // + // UsernameTextBox + // + this.UsernameTextBox.Location = new System.Drawing.Point(132, 9); + this.UsernameTextBox.Name = "UsernameTextBox"; + this.UsernameTextBox.Size = new System.Drawing.Size(233, 20); + this.UsernameTextBox.TabIndex = 22; + // + // DividerLabel + // + this.DividerLabel.AutoSize = true; + this.DividerLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.DividerLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.DividerLabel.Location = new System.Drawing.Point(-24, 59); + this.DividerLabel.Name = "DividerLabel"; + this.DividerLabel.Size = new System.Drawing.Size(468, 18); + this.DividerLabel.TabIndex = 26; + this.DividerLabel.Text = "______________________________________________"; + this.DividerLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // APIKeyLabel + // + this.APIKeyLabel.AutoSize = true; + this.APIKeyLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.APIKeyLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.APIKeyLabel.Location = new System.Drawing.Point(12, 95); + this.APIKeyLabel.Name = "APIKeyLabel"; + this.APIKeyLabel.Size = new System.Drawing.Size(78, 18); + this.APIKeyLabel.TabIndex = 28; + this.APIKeyLabel.Text = "API Key:"; + this.APIKeyLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // APIKeyTextBox + // + this.APIKeyTextBox.Location = new System.Drawing.Point(132, 95); + this.APIKeyTextBox.Name = "APIKeyTextBox"; + this.APIKeyTextBox.Size = new System.Drawing.Size(233, 20); + this.APIKeyTextBox.TabIndex = 27; + this.APIKeyTextBox.UseSystemPasswordChar = true; + // + // OrLabel + // + this.OrLabel.AutoSize = true; + this.OrLabel.Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.OrLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(248)))), ((int)(((byte)(242))))); + this.OrLabel.Location = new System.Drawing.Point(157, 64); + this.OrLabel.Name = "OrLabel"; + this.OrLabel.Size = new System.Drawing.Size(32, 18); + this.OrLabel.TabIndex = 29; + this.OrLabel.Text = "OR"; + this.OrLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // LoginButton + // + this.LoginButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.LoginButton.Location = new System.Drawing.Point(145, 133); + this.LoginButton.Name = "LoginButton"; + this.LoginButton.Size = new System.Drawing.Size(88, 25); + this.LoginButton.TabIndex = 30; + this.LoginButton.Text = InitalLoginButtonText; + this.LoginButton.UseVisualStyleBackColor = true; + this.LoginButton.Click += new System.EventHandler(this.LoginButton_Click); + // + // GitHubLoginPrompt + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(39)))), ((int)(((byte)(40)))), ((int)(((byte)(34))))); + this.ClientSize = new System.Drawing.Size(376, 170); + this.Controls.Add(this.LoginButton); + this.Controls.Add(this.OrLabel); + this.Controls.Add(this.APIKeyLabel); + this.Controls.Add(this.APIKeyTextBox); + this.Controls.Add(this.DividerLabel); + this.Controls.Add(this.PasswordLabel); + this.Controls.Add(this.UsernameLabel); + this.Controls.Add(this.PasswordTextBox); + this.Controls.Add(this.UsernameTextBox); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "GitHubLoginPrompt"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "Login To GitHub"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label PasswordLabel; + private System.Windows.Forms.Label UsernameLabel; + private System.Windows.Forms.TextBox PasswordTextBox; + private System.Windows.Forms.TextBox UsernameTextBox; + private System.Windows.Forms.Label DividerLabel; + private System.Windows.Forms.Label APIKeyLabel; + private System.Windows.Forms.TextBox APIKeyTextBox; + private System.Windows.Forms.Label OrLabel; + private System.Windows.Forms.Button LoginButton; + } +} \ No newline at end of file diff --git a/TGControlPanel/GithubLoginPrompt.cs b/TGControlPanel/GithubLoginPrompt.cs new file mode 100644 index 0000000000..55cf6386f4 --- /dev/null +++ b/TGControlPanel/GithubLoginPrompt.cs @@ -0,0 +1,98 @@ +using Octokit; +using System; +using System.Threading.Tasks; +using System.Windows.Forms; +using TGServiceInterface; + +namespace TGControlPanel +{ + /// + /// Used for recieving a GitHub API key for use in + /// + sealed partial class GitHubLoginPrompt : Form + { + /// + /// The text to appear on + /// + const string InitalLoginButtonText = "Login"; + + /// + /// The to use for OAuth requests + /// + readonly GitHubClient client; + + /// + /// Construct a + /// + /// The to use for requests + public GitHubLoginPrompt(GitHubClient c) + { + InitializeComponent(); + AcceptButton = LoginButton; + DialogResult = DialogResult.Cancel; + client = c; + } + + /// + /// Calls . On success, encrypts it's return value, saves it in and , and closes the . On failure, shows a and exits + /// + /// The sender of the event + /// The + async void LoginButton_Click(object sender, EventArgs e) + { + LoginButton.Text = "Logging in..."; + Enabled = false; + var APIKey = await GetAPIKey(); + if(APIKey == null) + { + MessageBox.Show("Authentication failure!"); + Enabled = true; + LoginButton.Text = "Login"; + return; + } + + //Encrypt it and let's be on our way + var Config = Properties.Settings.Default; + Config.GitHubAPIKey = Helpers.EncryptData(APIKey, out string entropy); + Config.GitHubAPIKeyEntropy = entropy; + DialogResult = DialogResult.OK; + Close(); + } + + /// + /// Retrieves an API key from GitHub based on login information or from the and verifies it + /// + /// The GitHub API key on success, on failure + async Task GetAPIKey() + { + string APIKey; + if (String.IsNullOrWhiteSpace(APIKeyTextBox.Text)) + { + try + { + client.Credentials = new Credentials(UsernameTextBox.Text, PasswordTextBox.Text); + var token = await client.Authorization.Create(new NewAuthorization { Note = "TGControlPanel token to bypass rate limiting" }); + APIKey = token.Token; + } + catch (AuthorizationException) + { + return null; + } + } + else + APIKey = APIKeyTextBox.Text; + + //validate it by pinging a random repository + client.Credentials = new Credentials(APIKey); + try + { + await client.Repository.Get("Dextraspace", "Test"); + } + catch (AuthorizationException) + { + return null; + } + return APIKey; + } + } +} diff --git a/TGControlPanel/GithubLoginPrompt.resx b/TGControlPanel/GithubLoginPrompt.resx new file mode 100644 index 0000000000..1af7de150c --- /dev/null +++ b/TGControlPanel/GithubLoginPrompt.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/TGControlPanel/Properties/Settings.Designer.cs b/TGControlPanel/Properties/Settings.Designer.cs index e3448811d3..86ec264975 100644 --- a/TGControlPanel/Properties/Settings.Designer.cs +++ b/TGControlPanel/Properties/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace TGControlPanel.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.1.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.3.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); @@ -142,5 +142,29 @@ namespace TGControlPanel.Properties { this["RemoteEntropy"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string GitHubAPIKey { + get { + return ((string)(this["GitHubAPIKey"])); + } + set { + this["GitHubAPIKey"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string GitHubAPIKeyEntropy { + get { + return ((string)(this["GitHubAPIKeyEntropy"])); + } + set { + this["GitHubAPIKeyEntropy"] = value; + } + } } } diff --git a/TGControlPanel/Properties/Settings.settings b/TGControlPanel/Properties/Settings.settings index 75fb36e555..54c6fc8cd4 100644 --- a/TGControlPanel/Properties/Settings.settings +++ b/TGControlPanel/Properties/Settings.settings @@ -32,5 +32,11 @@ + + + + + + \ No newline at end of file diff --git a/TGControlPanel/TGControlPanel.csproj b/TGControlPanel/TGControlPanel.csproj index 76aaf2895f..fb3db6a4a6 100644 --- a/TGControlPanel/TGControlPanel.csproj +++ b/TGControlPanel/TGControlPanel.csproj @@ -64,6 +64,12 @@ Form + + Form + + + GitHubLoginPrompt.cs + Form @@ -108,6 +114,9 @@ Form + + GitHubLoginPrompt.cs + InstanceSelector.cs diff --git a/TGControlPanel/TestMergeManager.cs b/TGControlPanel/TestMergeManager.cs index 77addc793a..695495cce1 100644 --- a/TGControlPanel/TestMergeManager.cs +++ b/TGControlPanel/TestMergeManager.cs @@ -30,78 +30,96 @@ namespace TGControlPanel } /// - /// Populate with from github and check off ones that are currently merged + /// Populate with from github and check off ones that are currently merged. Prompts the user to login to GitHub if they hit the rate limit /// - async void LoadPullRequests() + /// The to use. If , one will be created + async void LoadPullRequests(GitHubClient client) { - Enabled = false; - UseWaitCursor = true; + if(client == null) + client = new GitHubClient(new ProductHeaderValue(Assembly.GetExecutingAssembly().GetName().Name)); + var config = Properties.Settings.Default; + if (!String.IsNullOrWhiteSpace(config.GitHubAPIKey)) + client.Credentials = new Credentials(Helpers.DecryptData(config.GitHubAPIKey, config.GitHubAPIKeyEntropy)); try { - PullRequestListBox.Items.Clear(); - - var repo = currentInterface.GetComponent(); - string remote = null, error = null, error2 = null; - IList pulls = null; - - var remoteRequest = WrapServerOp(() => remote = repo.GetRemote(out error)); - - //get started on this while we're processing here - var pullsRequest = Task.Factory.StartNew(() => pulls = repo.MergedPullRequests(out error2)); - - await remoteRequest; - - if (remote == null) - { - MessageBox.Show(String.Format("Error retrieving remote repository: {0}", error)); - return; - } - if (!remote.Contains("github.com")) - { - MessageBox.Show("Pull request support is only available for github based repositories!", "Error"); - return; - } - - //Assume standard gh format: [(git)|(https)]://github.com/owner/repo(.git)[0-1] - var splits = remote.Split('/'); - var repoName = splits[splits.Length - 1]; - var repoOwner = splits[splits.Length - 2]; - - //Search for open PRs - var client = new GitHubClient(new ProductHeaderValue(Assembly.GetExecutingAssembly().GetName().Name)); Enabled = false; UseWaitCursor = true; - SearchIssuesResult result; try { - result = await client.Search.SearchIssues(new SearchIssuesRequest + PullRequestListBox.Items.Clear(); + + var repo = currentInterface.GetComponent(); + string remote = null, error = null, error2 = null; + IList pulls = null; + + var remoteRequest = WrapServerOp(() => remote = repo.GetRemote(out error)); + + //get started on this while we're processing here + var pullsRequest = Task.Factory.StartNew(() => pulls = repo.MergedPullRequests(out error2)); + + await remoteRequest; + + if (remote == null) { - Repos = new RepositoryCollection - { - { repoOwner, repoName } - }, - State = ItemState.Open, - Type = IssueTypeQualifier.PullRequest - }); + MessageBox.Show(String.Format("Error retrieving remote repository: {0}", error)); + return; + } + if (!remote.Contains("github.com")) + { + MessageBox.Show("Pull request support is only available for github based repositories!", "Error"); + return; + } + + //Assume standard gh format: [(git)|(https)]://github.com/owner/repo(.git)[0-1] + var splits = remote.Split('/'); + var repoName = splits[splits.Length - 1]; + var repoOwner = splits[splits.Length - 2]; + + //Search for open PRs + Enabled = false; + UseWaitCursor = true; + SearchIssuesResult result; + try + { + result = await client.Search.SearchIssues(new SearchIssuesRequest + { + Repos = new RepositoryCollection { { repoOwner, repoName } }, + State = ItemState.Open, + Type = IssueTypeQualifier.PullRequest + }); + } + finally + { + Enabled = true; + UseWaitCursor = false; + } + + //now we need to know what's merged + await WrapServerOp(() => pullsRequest.Wait()); + if (pulls == null) + MessageBox.Show(String.Format("Error retrieving currently merged pull requests: {0}", error2)); + + foreach (var I in result.Items) + PullRequestListBox.Items.Add(String.Format("#{0} - {1}", I.Number, I.Title), pulls.Any(x => x.Number == I.Number)); } finally { Enabled = true; UseWaitCursor = false; } - - //now we need to know what's merged - await WrapServerOp(() => pullsRequest.Wait()); - if (pulls == null) - MessageBox.Show(String.Format("Error retrieving currently merged pull requests: {0}", error2)); - - foreach (var I in result.Items) - PullRequestListBox.Items.Add(String.Format("#{0} - {1}", I.Number, I.Title), pulls.Any(x => x.Number == I.Number)); } - finally + catch (ForbiddenException) { - Enabled = true; - UseWaitCursor = false; + if (client.Credentials.AuthenticationType == AuthenticationType.Anonymous) //assume request limit hit + { + if (MessageBox.Show("You seem to have hit the rate limit of 60 requests per hour of the GitHub API for anonymous requests. Would you like to enter credentials to bypass this?", "Rate limited", MessageBoxButtons.YesNo) != DialogResult.Yes) + return; + using (var D = new GitHubLoginPrompt(client)) + if (D.ShowDialog() == DialogResult.OK) + LoadPullRequests(client); + } + else + throw; } } @@ -112,7 +130,7 @@ namespace TGControlPanel /// The void PullRequestManager_Load(object sender, EventArgs e) { - LoadPullRequests(); + LoadPullRequests(null); } /// @@ -202,7 +220,7 @@ namespace TGControlPanel /// The void RefreshButton_Click(object sender, EventArgs e) { - LoadPullRequests(); + LoadPullRequests(null); } } }