diff --git a/Cmdline/Action/AuthToken.cs b/Cmdline/Action/AuthToken.cs
new file mode 100644
index 0000000000..5bb806fc5b
--- /dev/null
+++ b/Cmdline/Action/AuthToken.cs
@@ -0,0 +1,175 @@
+using System;
+using System.Collections.Generic;
+using CommandLine;
+using CommandLine.Text;
+using log4net;
+
+namespace CKAN.CmdLine
+{
+ ///
+ /// Subcommand for managing authentication tokens
+ ///
+ public class AuthToken : ISubCommand
+ {
+ ///
+ /// Initialize the subcommand
+ ///
+ public AuthToken() { }
+
+ ///
+ /// Run the subcommand
+ ///
+ /// Command line parameters not yet handled by parser
+ ///
+ /// Exit code
+ ///
+ public int RunSubCommand(SubCommandOptions unparsed)
+ {
+ string[] args = unparsed.options.ToArray();
+ int exitCode = Exit.OK;
+
+ Parser.Default.ParseArgumentsStrict(args, new AuthTokenSubOptions(), (string option, object suboptions) =>
+ {
+ if (!string.IsNullOrEmpty(option) && suboptions != null)
+ {
+ CommonOptions options = (CommonOptions)suboptions;
+ user = new ConsoleUser(options.Headless);
+ manager = new KSPManager(user);
+ exitCode = options.Handle(manager, user);
+ if (exitCode == Exit.OK)
+ {
+ switch (option)
+ {
+ case "list":
+ exitCode = listAuthTokens(options);
+ break;
+ case "add":
+ exitCode = addAuthToken((AddAuthTokenOptions)options);
+ break;
+ case "remove":
+ exitCode = removeAuthToken((RemoveAuthTokenOptions)options);
+ break;
+ }
+ }
+ }
+ }, () => { exitCode = MainClass.AfterHelp(); });
+ return exitCode;
+ }
+
+ private int listAuthTokens(CommonOptions opts)
+ {
+ List hosts = new List(Win32Registry.GetAuthTokenHosts());
+ if (hosts.Count > 0)
+ {
+ int longestHostLen = hostHeader.Length;
+ int longestTokenLen = tokenHeader.Length;
+ foreach (string host in hosts)
+ {
+ longestHostLen = Math.Max(longestHostLen, host.Length);
+ string token;
+ if (Win32Registry.TryGetAuthToken(host, out token))
+ {
+ longestTokenLen = Math.Max(longestTokenLen, token.Length);
+ }
+ }
+ // Create format string: {0,-longestHostLen} {1,-longestTokenLen}
+ string fmt = string.Format("{0}0,-{2}{1} {0}1,-{3}{1}",
+ "{", "}", longestHostLen, longestTokenLen);
+ user.RaiseMessage(fmt, hostHeader, tokenHeader);
+ user.RaiseMessage(fmt,
+ new string('-', longestHostLen),
+ new string('-', longestTokenLen)
+ );
+ foreach (string host in hosts)
+ {
+ string token;
+ if (Win32Registry.TryGetAuthToken(host, out token))
+ {
+ user.RaiseMessage(fmt, host, token);
+ }
+ }
+ }
+ return Exit.OK;
+ }
+
+ private int addAuthToken(AddAuthTokenOptions opts)
+ {
+ if (Uri.CheckHostName(opts.host) != UriHostNameType.Unknown)
+ {
+ Win32Registry.SetAuthToken(opts.host, opts.token);
+ }
+ else
+ {
+ user.RaiseError("Invalid host name: {0}", opts.host);
+ }
+ return Exit.OK;
+ }
+
+ private int removeAuthToken(RemoveAuthTokenOptions opts)
+ {
+ Win32Registry.SetAuthToken(opts.host, null);
+ return Exit.OK;
+ }
+
+ private const string hostHeader = "Host";
+ private const string tokenHeader = "Token";
+
+ private IUser user;
+ private KSPManager manager;
+ private static readonly ILog log = LogManager.GetLogger(typeof(AuthToken));
+ }
+
+ internal class AuthTokenSubOptions : VerbCommandOptions
+ {
+ [VerbOption("list", HelpText = "List auth tokens")]
+ public CommonOptions ListOptions { get; set; }
+
+ [VerbOption("add", HelpText = "Add an auth token")]
+ public AddAuthTokenOptions AddOptions { get; set; }
+
+ [VerbOption("remove", HelpText = "Delete an auth token")]
+ public RemoveAuthTokenOptions RemoveOptions { get; set; }
+
+ [HelpVerbOption]
+ public string GetUsage(string verb)
+ {
+ HelpText ht = HelpText.AutoBuild(this, verb);
+ // Add a usage prefix line
+ ht.AddPreOptionsLine(" ");
+ if (string.IsNullOrEmpty(verb))
+ {
+ ht.AddPreOptionsLine("ckan authtoken - Manage authentication tokens");
+ ht.AddPreOptionsLine($"Usage: ckan authtoken [options]");
+ }
+ else
+ {
+ ht.AddPreOptionsLine("authtoken " + verb + " - " + GetDescription(verb));
+ switch (verb)
+ {
+ case "add":
+ ht.AddPreOptionsLine($"Usage: ckan authtoken {verb} [options] host token");
+ break;
+ case "remove":
+ ht.AddPreOptionsLine($"Usage: ckan authtoken {verb} [options] host");
+ break;
+ case "list":
+ ht.AddPreOptionsLine($"Usage: ckan authtoken {verb} [options]");
+ break;
+ }
+ }
+ return ht;
+ }
+ }
+
+ internal class AddAuthTokenOptions : CommonOptions
+ {
+ [ValueOption(0)] public string host { get; set; }
+ [ValueOption(1)] public string token { get; set; }
+ }
+
+ internal class RemoveAuthTokenOptions : CommonOptions
+ {
+ [ValueOption(0)] public string host { get; set; }
+ }
+
+}
diff --git a/Cmdline/CKAN-cmdline.csproj b/Cmdline/CKAN-cmdline.csproj
index 409b389768..957f2fb960 100644
--- a/Cmdline/CKAN-cmdline.csproj
+++ b/Cmdline/CKAN-cmdline.csproj
@@ -53,6 +53,7 @@
Properties\GlobalAssemblyInfo.cs
+
diff --git a/Cmdline/Main.cs b/Cmdline/Main.cs
index f2e19adde9..feddcb8f89 100644
--- a/Cmdline/Main.cs
+++ b/Cmdline/Main.cs
@@ -63,20 +63,19 @@ public static int Main(string[] args)
switch (args[0])
{
case "repair":
- var repair = new Repair();
- return repair.RunSubCommand(new SubCommandOptions(args));
+ return (new Repair()).RunSubCommand(new SubCommandOptions(args));
case "ksp":
- var ksp = new KSP();
- return ksp.RunSubCommand(new SubCommandOptions(args));
+ return (new KSP()).RunSubCommand(new SubCommandOptions(args));
case "compat":
- var compat = new CompatSubCommand();
- return compat.RunSubCommand(new SubCommandOptions(args));
+ return (new CompatSubCommand()).RunSubCommand(new SubCommandOptions(args));
case "repo":
- var repo = new Repo();
- return repo.RunSubCommand(new SubCommandOptions(args));
+ return (new Repo()).RunSubCommand(new SubCommandOptions(args));
+
+ case "authtoken":
+ return (new AuthToken()).RunSubCommand(new SubCommandOptions(args));
}
}
catch (NoGameInstanceKraken)
diff --git a/Cmdline/Options.cs b/Cmdline/Options.cs
index e721506cb8..5ceea56985 100644
--- a/Cmdline/Options.cs
+++ b/Cmdline/Options.cs
@@ -88,6 +88,9 @@ internal class Actions : VerbCommandOptions
[VerbOption("ksp", HelpText = "Manage KSP installs")]
public SubCommandOptions KSP { get; set; }
+ [VerbOption("authtoken", HelpText = "Manage authentication tokens")]
+ public AuthTokenSubOptions AuthToken { get; set; }
+
[VerbOption("compat", HelpText = "Manage KSP version compatibility")]
public SubCommandOptions Compat { get; set; }
diff --git a/ConsoleUI/AuthTokenAddDialog.cs b/ConsoleUI/AuthTokenAddDialog.cs
new file mode 100644
index 0000000000..2a608b5879
--- /dev/null
+++ b/ConsoleUI/AuthTokenAddDialog.cs
@@ -0,0 +1,86 @@
+using System;
+using CKAN.ConsoleUI.Toolkit;
+
+namespace CKAN.ConsoleUI {
+
+ ///
+ /// Popup for adding a new authentication token.
+ ///
+ public class AuthTokenAddDialog : ConsoleDialog {
+
+ ///
+ /// Initialize the popup.
+ ///
+ public AuthTokenAddDialog() : base()
+ {
+ CenterHeader = () => "Create Authentication Key";
+
+ int top = (Console.WindowHeight - height) / 2;
+ SetDimensions(6, top, -6, top + height - 1);
+
+ int l = GetLeft(),
+ r = GetRight(),
+ t = GetTop(),
+ b = GetBottom();
+
+ AddObject(new ConsoleLabel(
+ l + 2, t + 2, l + 2 + labelW,
+ () => "Host:",
+ () => ConsoleTheme.Current.PopupBg,
+ () => ConsoleTheme.Current.PopupFg
+ ));
+
+ hostEntry = new ConsoleField(
+ l + 2 + labelW + wPad, t + 2, r - 3
+ ) {
+ GhostText = () => ""
+ };
+ AddObject(hostEntry);
+
+ AddObject(new ConsoleLabel(
+ l + 2, t + 4, l + 2 + labelW,
+ () => "Token:",
+ () => ConsoleTheme.Current.PopupBg,
+ () => ConsoleTheme.Current.PopupFg
+ ));
+
+ tokenEntry = new ConsoleField(
+ l + 2 + labelW + wPad, t + 4, r - 3
+ ) {
+ GhostText = () => ""
+ };
+ AddObject(tokenEntry);
+
+ AddTip("Esc", "Cancel");
+ AddBinding(Keys.Escape, (object sender) => false);
+
+ AddTip("Enter", "Accept", validKey);
+ AddBinding(Keys.Enter, (object sender) => {
+ if (validKey()) {
+ Win32Registry.SetAuthToken(hostEntry.Value, tokenEntry.Value);
+ return false;
+ } else {
+ // Don't close window on Enter unless adding a key
+ return true;
+ }
+ });
+ }
+
+ private bool validKey()
+ {
+ string token;
+ return hostEntry.Value.Length > 0
+ && tokenEntry.Value.Length > 0
+ && Uri.CheckHostName(hostEntry.Value) != UriHostNameType.Unknown
+ && !Win32Registry.TryGetAuthToken(hostEntry.Value, out token);
+ }
+
+ private ConsoleField hostEntry;
+ private ConsoleField tokenEntry;
+
+ private const int wPad = 2;
+ private const int labelW = 6;
+ private const int height = 7;
+ }
+
+}
diff --git a/ConsoleUI/AuthTokenListScreen.cs b/ConsoleUI/AuthTokenListScreen.cs
new file mode 100644
index 0000000000..b043540611
--- /dev/null
+++ b/ConsoleUI/AuthTokenListScreen.cs
@@ -0,0 +1,96 @@
+using System;
+using System.ComponentModel;
+using System.Collections.Generic;
+using CKAN.ConsoleUI.Toolkit;
+
+namespace CKAN.ConsoleUI {
+
+ ///
+ /// Screen for display and editing of authentication tokens.
+ ///
+ public class AuthTokenScreen : ConsoleScreen {
+
+ ///
+ /// Initialize the screen.
+ ///
+ public AuthTokenScreen() : base()
+ {
+ LeftHeader = () => $"CKAN {Meta.GetVersion()}";
+ CenterHeader = () => "Authentication Tokens";
+ mainMenu = new ConsolePopupMenu(new List() {
+ new ConsoleMenuOption("Make a GitHub API token", "",
+ "Open the web page for creating GitHub API authentication tokens",
+ true, openGitHubURL)
+ });
+
+ AddObject(new ConsoleLabel(
+ 1, 2, -1,
+ () => "Authentication tokens for downloads:"
+ ));
+
+ tokenList = new ConsoleListBox(
+ 1, 4, -1, -2,
+ new List(Win32Registry.GetAuthTokenHosts()),
+ new List>() {
+ new ConsoleListBoxColumn() {
+ Header = "Host",
+ Width = 20,
+ Renderer = (string s) => s
+ },
+ new ConsoleListBoxColumn() {
+ Header = "Token",
+ Width = 50,
+ Renderer = (string s) => {
+ string token;
+ return Win32Registry.TryGetAuthToken(s, out token)
+ ? token
+ : missingTokenValue;
+ }
+ }
+ },
+ 0, 0, ListSortDirection.Descending
+ );
+ AddObject(tokenList);
+
+ AddObject(new ConsoleLabel(
+ 3, -1, -1,
+ () => "NOTE: These values are private! Do not share screenshots of this screen!",
+ null,
+ () => ConsoleTheme.Current.AlertFrameFg
+ ));
+
+ AddTip("Esc", "Back");
+ AddBinding(Keys.Escape, (object sender) => false);
+
+ tokenList.AddTip("A", "Add");
+ tokenList.AddBinding(Keys.A, (object sender) => {
+ AuthTokenAddDialog ad = new AuthTokenAddDialog();
+ ad.Run();
+ DrawBackground();
+ tokenList.SetData(new List(Win32Registry.GetAuthTokenHosts()));
+ return true;
+ });
+
+ tokenList.AddTip("R", "Remove", () => tokenList.Selection != null);
+ tokenList.AddBinding(Keys.R, (object sender) => {
+ if (tokenList.Selection != null) {
+ Win32Registry.SetAuthToken(tokenList.Selection, null);
+ tokenList.SetData(new List(Win32Registry.GetAuthTokenHosts()));
+ }
+ return true;
+ });
+ }
+
+ private bool openGitHubURL()
+ {
+ ModInfoScreen.LaunchURL(githubTokenURL);
+ return true;
+ }
+
+ private ConsoleListBox tokenList;
+
+ private const string missingTokenValue = "";
+ private static readonly Uri githubTokenURL = new Uri("https://github.com/settings/tokens");
+ }
+
+}
diff --git a/ConsoleUI/CKAN-ConsoleUI.csproj b/ConsoleUI/CKAN-ConsoleUI.csproj
index 5483d77a78..5d719bed9f 100644
--- a/ConsoleUI/CKAN-ConsoleUI.csproj
+++ b/ConsoleUI/CKAN-ConsoleUI.csproj
@@ -64,6 +64,8 @@
Properties\GlobalAssemblyInfo.cs
+
+
diff --git a/ConsoleUI/ModInfoScreen.cs b/ConsoleUI/ModInfoScreen.cs
index e2fbb4f19d..2742dd1802 100644
--- a/ConsoleUI/ModInfoScreen.cs
+++ b/ConsoleUI/ModInfoScreen.cs
@@ -179,7 +179,14 @@ private bool ViewMetadata()
return true;
}
- private bool LaunchURL(Uri u)
+ ///
+ /// Launch a URL in the system browser.
+ ///
+ /// URL to launch
+ ///
+ /// True.
+ ///
+ public static bool LaunchURL(Uri u)
{
// I'm getting error output on Linux, because this runs xdg-open which
// calls chromium-browser which prints a bunch of stuff about plugins that
diff --git a/ConsoleUI/ModListScreen.cs b/ConsoleUI/ModListScreen.cs
index a2b70444de..ae6fe10635 100644
--- a/ConsoleUI/ModListScreen.cs
+++ b/ConsoleUI/ModListScreen.cs
@@ -256,6 +256,9 @@ public ModListScreen(KSPManager mgr, bool dbg)
new ConsoleMenuOption("Select KSP install...", "",
"Switch to a different game instance",
true, SelectInstall),
+ new ConsoleMenuOption("Authentication tokens...", "",
+ "Edit authentication tokens sent to download servers",
+ true, EditAuthTokens),
null,
new ConsoleMenuOption("Help", helpKey,
"Tips & tricks",
@@ -436,6 +439,12 @@ private bool SelectInstall()
return true;
}
+ private bool EditAuthTokens()
+ {
+ LaunchSubScreen(new AuthTokenScreen());
+ return true;
+ }
+
private void RefreshList()
{
moduleList.SetData(GetAllMods(true));
diff --git a/Core/Net/AutoUpdate.cs b/Core/Net/AutoUpdate.cs
index 38623e30f4..270710f3cf 100644
--- a/Core/Net/AutoUpdate.cs
+++ b/Core/Net/AutoUpdate.cs
@@ -63,8 +63,10 @@ private static bool CanWrite(string path)
///
/// Report whether it's possible to run the auto-updater.
/// Checks whether we can overwrite the running ckan.exe.
+ /// Windows doesn't let us check this because it locks the EXE
+ /// for a running process, so assume we can always overwrite on Windows.
///
- public static readonly bool CanUpdate = CanWrite(exePath);
+ public static readonly bool CanUpdate = Platform.IsWindows || CanWrite(exePath);
///
/// Our metadata is considered fetched if we have a latest version, release notes,
@@ -86,12 +88,15 @@ public void FetchLatestReleaseInfo()
try
{
- fetchedCkanUrl = RetrieveUrl(response, 0);
// Check whether the release includes the auto updater
foreach (var asset in response.assets)
{
string url = asset.browser_download_url.ToString();
- if (url.EndsWith("AutoUpdater.exe"))
+ if (url.EndsWith("ckan.exe"))
+ {
+ fetchedCkanUrl = new Tuple(new Uri(url), (long)asset.size);
+ }
+ else if (url.EndsWith("AutoUpdater.exe"))
{
fetchedUpdaterUrl = new Tuple(new Uri(url), (long)asset.size);
break;
diff --git a/Core/Net/NetAsyncDownloader.cs b/Core/Net/NetAsyncDownloader.cs
index 13d14afd3d..ae6bc963d1 100644
--- a/Core/Net/NetAsyncDownloader.cs
+++ b/Core/Net/NetAsyncDownloader.cs
@@ -54,7 +54,7 @@ public NetAsyncDownloaderDownloadPart(Net.DownloadTarget target, string path = n
{
log.InfoFormat("Using auth token for {0}", this.url.Host);
// Send our auth token to the GitHub API (or whoever else needs one)
- agent.Headers.Add("Authentication", $"token {token}");
+ agent.Headers.Add("Authorization", $"token {token}");
}
}
}
diff --git a/Core/Win32Registry.cs b/Core/Win32Registry.cs
index 42f5677796..334550b2df 100644
--- a/Core/Win32Registry.cs
+++ b/Core/Win32Registry.cs
@@ -110,11 +110,22 @@ public static IEnumerable GetAuthTokenHosts()
/// Set an auth token in the registry
///
/// Host for which to set the token
- /// Token to set
+ /// Token to set, or null to delete
public static void SetAuthToken(string host, string token)
{
ConstructKey(authTokenKeyNoPrefix);
- Microsoft.Win32.Registry.SetValue(authTokenKey, host, token);
+ if (!string.IsNullOrEmpty(host))
+ {
+ if (string.IsNullOrEmpty(token))
+ {
+ RegistryKey key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(authTokenKeyNoPrefix, true);
+ key.DeleteValue(host);
+ }
+ else
+ {
+ Microsoft.Win32.Registry.SetValue(authTokenKey, host, token);
+ }
+ }
}
private static string StripPrefixKey(string keyname)
diff --git a/GUI/SettingsDialog.Designer.cs b/GUI/SettingsDialog.Designer.cs
index de51834fb6..c022fa3f77 100644
--- a/GUI/SettingsDialog.Designer.cs
+++ b/GUI/SettingsDialog.Designer.cs
@@ -35,30 +35,35 @@ private void InitializeComponent()
this.UpRepoButton = new System.Windows.Forms.Button();
this.DeleteRepoButton = new System.Windows.Forms.Button();
this.ReposListBox = new System.Windows.Forms.ListBox();
+ this.AuthTokensGroupBox = new System.Windows.Forms.GroupBox();
+ this.AuthTokensListBox = new System.Windows.Forms.ListBox();
+ this.NewAuthTokenButton = new System.Windows.Forms.Button();
+ this.DeleteAuthTokenButton = new System.Windows.Forms.Button();
this.CacheGroupBox = new System.Windows.Forms.GroupBox();
this.ClearCKANCacheButton = new System.Windows.Forms.Button();
this.CKANCacheLabel = new System.Windows.Forms.Label();
- this.groupBox2 = new System.Windows.Forms.GroupBox();
+ this.AutoUpdateGroupBox = new System.Windows.Forms.GroupBox();
this.HideEpochsCheckbox = new System.Windows.Forms.CheckBox();
this.RefreshOnStartupCheckbox = new System.Windows.Forms.CheckBox();
this.CheckUpdateOnLaunchCheckbox = new System.Windows.Forms.CheckBox();
this.InstallUpdateButton = new System.Windows.Forms.Button();
this.LatestVersionLabel = new System.Windows.Forms.Label();
- this.label4 = new System.Windows.Forms.Label();
+ this.LatestVersionLabelLabel = new System.Windows.Forms.Label();
this.LocalVersionLabel = new System.Windows.Forms.Label();
- this.label3 = new System.Windows.Forms.Label();
+ this.LocalVersionLabelLabel = new System.Windows.Forms.Label();
this.CheckForUpdatesButton = new System.Windows.Forms.Button();
this.RepositoryGroupBox.SuspendLayout();
+ this.AuthTokensGroupBox.SuspendLayout();
this.CacheGroupBox.SuspendLayout();
- this.groupBox2.SuspendLayout();
+ this.AutoUpdateGroupBox.SuspendLayout();
this.SuspendLayout();
//
// NewRepoButton
//
this.NewRepoButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
- this.NewRepoButton.Location = new System.Drawing.Point(6, 224);
+ this.NewRepoButton.Location = new System.Drawing.Point(6, 94);
this.NewRepoButton.Name = "NewRepoButton";
- this.NewRepoButton.Size = new System.Drawing.Size(56, 26);
+ this.NewRepoButton.Size = new System.Drawing.Size(56, 23);
this.NewRepoButton.TabIndex = 6;
this.NewRepoButton.Text = "New";
this.NewRepoButton.UseVisualStyleBackColor = true;
@@ -74,18 +79,18 @@ private void InitializeComponent()
this.RepositoryGroupBox.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.RepositoryGroupBox.Location = new System.Drawing.Point(12, 12);
this.RepositoryGroupBox.Name = "RepositoryGroupBox";
- this.RepositoryGroupBox.Size = new System.Drawing.Size(476, 261);
+ this.RepositoryGroupBox.Size = new System.Drawing.Size(476, 127);
this.RepositoryGroupBox.TabIndex = 12;
this.RepositoryGroupBox.TabStop = false;
- this.RepositoryGroupBox.Text = "Metadata repositories";
+ this.RepositoryGroupBox.Text = "Metadata Repositories";
//
// DownRepoButton
//
this.DownRepoButton.Enabled = false;
this.DownRepoButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
- this.DownRepoButton.Location = new System.Drawing.Point(130, 224);
+ this.DownRepoButton.Location = new System.Drawing.Point(130, 94);
this.DownRepoButton.Name = "DownRepoButton";
- this.DownRepoButton.Size = new System.Drawing.Size(56, 26);
+ this.DownRepoButton.Size = new System.Drawing.Size(56, 23);
this.DownRepoButton.TabIndex = 11;
this.DownRepoButton.Text = "Down";
this.DownRepoButton.UseVisualStyleBackColor = true;
@@ -95,9 +100,9 @@ private void InitializeComponent()
//
this.UpRepoButton.Enabled = false;
this.UpRepoButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
- this.UpRepoButton.Location = new System.Drawing.Point(68, 224);
+ this.UpRepoButton.Location = new System.Drawing.Point(68, 94);
this.UpRepoButton.Name = "UpRepoButton";
- this.UpRepoButton.Size = new System.Drawing.Size(56, 26);
+ this.UpRepoButton.Size = new System.Drawing.Size(56, 23);
this.UpRepoButton.TabIndex = 10;
this.UpRepoButton.Text = "Up";
this.UpRepoButton.UseVisualStyleBackColor = true;
@@ -107,9 +112,9 @@ private void InitializeComponent()
//
this.DeleteRepoButton.Enabled = false;
this.DeleteRepoButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
- this.DeleteRepoButton.Location = new System.Drawing.Point(414, 224);
+ this.DeleteRepoButton.Location = new System.Drawing.Point(414, 94);
this.DeleteRepoButton.Name = "DeleteRepoButton";
- this.DeleteRepoButton.Size = new System.Drawing.Size(56, 26);
+ this.DeleteRepoButton.Size = new System.Drawing.Size(56, 23);
this.DeleteRepoButton.TabIndex = 9;
this.DeleteRepoButton.Text = "Delete";
this.DeleteRepoButton.UseVisualStyleBackColor = true;
@@ -120,10 +125,55 @@ private void InitializeComponent()
this.ReposListBox.FormattingEnabled = true;
this.ReposListBox.Location = new System.Drawing.Point(6, 19);
this.ReposListBox.Name = "ReposListBox";
- this.ReposListBox.Size = new System.Drawing.Size(464, 199);
+ this.ReposListBox.Size = new System.Drawing.Size(464, 72);
this.ReposListBox.TabIndex = 9;
this.ReposListBox.SelectedIndexChanged += new System.EventHandler(this.ReposListBox_SelectedIndexChanged);
//
+ // AuthTokensGroupBox
+ //
+ this.AuthTokensGroupBox.Controls.Add(this.AuthTokensListBox);
+ this.AuthTokensGroupBox.Controls.Add(this.NewAuthTokenButton);
+ this.AuthTokensGroupBox.Controls.Add(this.DeleteAuthTokenButton);
+ this.AuthTokensGroupBox.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
+ this.AuthTokensGroupBox.Location = new System.Drawing.Point(12, 145);
+ this.AuthTokensGroupBox.Name = "AuthTokensGroupBox";
+ this.AuthTokensGroupBox.Size = new System.Drawing.Size(476, 127);
+ this.AuthTokensGroupBox.TabIndex = 13;
+ this.AuthTokensGroupBox.TabStop = false;
+ this.AuthTokensGroupBox.Text = "Authentication Tokens";
+ //
+ // AuthTokensListBox
+ //
+ this.AuthTokensListBox.FormattingEnabled = true;
+ this.AuthTokensListBox.Location = new System.Drawing.Point(6, 19);
+ this.AuthTokensListBox.Name = "AuthTokensListBox";
+ this.AuthTokensListBox.Size = new System.Drawing.Size(464, 72);
+ this.AuthTokensListBox.TabIndex = 14;
+ this.AuthTokensListBox.SelectedIndexChanged += new System.EventHandler(this.AuthTokensListBox_SelectedIndexChanged);
+ //
+ // NewAuthTokenButton
+ //
+ this.NewAuthTokenButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
+ this.NewAuthTokenButton.Location = new System.Drawing.Point(6, 94);
+ this.NewAuthTokenButton.Name = "NewAuthTokenButton";
+ this.NewAuthTokenButton.Size = new System.Drawing.Size(56, 23);
+ this.NewAuthTokenButton.TabIndex = 15;
+ this.NewAuthTokenButton.Text = "New";
+ this.NewAuthTokenButton.UseVisualStyleBackColor = true;
+ this.NewAuthTokenButton.Click += new System.EventHandler(this.NewAuthTokenButton_Click);
+ //
+ // DeleteAuthTokenButton
+ //
+ this.DeleteAuthTokenButton.Enabled = false;
+ this.DeleteAuthTokenButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
+ this.DeleteAuthTokenButton.Location = new System.Drawing.Point(414, 94);
+ this.DeleteAuthTokenButton.Name = "DeleteAuthTokenButton";
+ this.DeleteAuthTokenButton.Size = new System.Drawing.Size(56, 23);
+ this.DeleteAuthTokenButton.TabIndex = 16;
+ this.DeleteAuthTokenButton.Text = "Delete";
+ this.DeleteAuthTokenButton.UseVisualStyleBackColor = true;
+ this.DeleteAuthTokenButton.Click += new System.EventHandler(this.DeleteAuthTokenButton_Click);
+ //
// CacheGroupBox
//
this.CacheGroupBox.Controls.Add(this.ClearCKANCacheButton);
@@ -134,7 +184,7 @@ private void InitializeComponent()
this.CacheGroupBox.Size = new System.Drawing.Size(476, 49);
this.CacheGroupBox.TabIndex = 10;
this.CacheGroupBox.TabStop = false;
- this.CacheGroupBox.Text = "CKAN Cache";
+ this.CacheGroupBox.Text = "Cache";
//
// ClearCKANCacheButton
//
@@ -156,27 +206,27 @@ private void InitializeComponent()
this.CKANCacheLabel.TabIndex = 0;
this.CKANCacheLabel.Text = "There are currently N files in the cache, taking up M MB";
//
- // groupBox2
+ // AutoUpdateGroupBox
//
- this.groupBox2.Controls.Add(this.HideEpochsCheckbox);
- this.groupBox2.Controls.Add(this.RefreshOnStartupCheckbox);
+ this.AutoUpdateGroupBox.Controls.Add(this.HideEpochsCheckbox);
+ this.AutoUpdateGroupBox.Controls.Add(this.RefreshOnStartupCheckbox);
if (AutoUpdate.CanUpdate)
{
- this.groupBox2.Controls.Add(this.CheckUpdateOnLaunchCheckbox);
- this.groupBox2.Controls.Add(this.InstallUpdateButton);
- this.groupBox2.Controls.Add(this.LatestVersionLabel);
- this.groupBox2.Controls.Add(this.label4);
- this.groupBox2.Controls.Add(this.LocalVersionLabel);
- this.groupBox2.Controls.Add(this.label3);
- this.groupBox2.Controls.Add(this.CheckForUpdatesButton);
+ this.AutoUpdateGroupBox.Controls.Add(this.CheckUpdateOnLaunchCheckbox);
+ this.AutoUpdateGroupBox.Controls.Add(this.InstallUpdateButton);
+ this.AutoUpdateGroupBox.Controls.Add(this.LatestVersionLabel);
+ this.AutoUpdateGroupBox.Controls.Add(this.LatestVersionLabelLabel);
+ this.AutoUpdateGroupBox.Controls.Add(this.LocalVersionLabel);
+ this.AutoUpdateGroupBox.Controls.Add(this.LocalVersionLabelLabel);
+ this.AutoUpdateGroupBox.Controls.Add(this.CheckForUpdatesButton);
}
- this.groupBox2.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
- this.groupBox2.Location = new System.Drawing.Point(12, 334);
- this.groupBox2.Name = "groupBox2";
- this.groupBox2.Size = new System.Drawing.Size(476, 105);
- this.groupBox2.TabIndex = 11;
- this.groupBox2.TabStop = false;
- this.groupBox2.Text = "CKAN auto-update";
+ this.AutoUpdateGroupBox.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
+ this.AutoUpdateGroupBox.Location = new System.Drawing.Point(12, 334);
+ this.AutoUpdateGroupBox.Name = "AutoUpdateGroupBox";
+ this.AutoUpdateGroupBox.Size = new System.Drawing.Size(476, 105);
+ this.AutoUpdateGroupBox.TabIndex = 11;
+ this.AutoUpdateGroupBox.TabStop = false;
+ this.AutoUpdateGroupBox.Text = "Auto-Updates";
//
// HideEpochsCheckbox
//
@@ -232,14 +282,14 @@ private void InitializeComponent()
this.LatestVersionLabel.TabIndex = 4;
this.LatestVersionLabel.Text = "???";
//
- // label4
+ // LatestVersionLabelLabel
//
- this.label4.AutoSize = true;
- this.label4.Location = new System.Drawing.Point(7, 43);
- this.label4.Name = "label4";
- this.label4.Size = new System.Drawing.Size(76, 13);
- this.label4.TabIndex = 3;
- this.label4.Text = "Latest version:";
+ this.LatestVersionLabelLabel.AutoSize = true;
+ this.LatestVersionLabelLabel.Location = new System.Drawing.Point(7, 43);
+ this.LatestVersionLabelLabel.Name = "LatestVersionLabelLabel";
+ this.LatestVersionLabelLabel.Size = new System.Drawing.Size(76, 13);
+ this.LatestVersionLabelLabel.TabIndex = 3;
+ this.LatestVersionLabelLabel.Text = "Latest version:";
//
// LocalVersionLabel
//
@@ -250,14 +300,14 @@ private void InitializeComponent()
this.LocalVersionLabel.TabIndex = 2;
this.LocalVersionLabel.Text = "v0.0.0";
//
- // label3
+ // LocalVersionLabelLabel
//
- this.label3.AutoSize = true;
- this.label3.Location = new System.Drawing.Point(7, 20);
- this.label3.Name = "label3";
- this.label3.Size = new System.Drawing.Size(73, 13);
- this.label3.TabIndex = 1;
- this.label3.Text = "Local version:";
+ this.LocalVersionLabelLabel.AutoSize = true;
+ this.LocalVersionLabelLabel.Location = new System.Drawing.Point(7, 20);
+ this.LocalVersionLabelLabel.Name = "LocalVersionLabelLabel";
+ this.LocalVersionLabelLabel.Size = new System.Drawing.Size(73, 13);
+ this.LocalVersionLabelLabel.TabIndex = 1;
+ this.LocalVersionLabelLabel.Text = "Local version:";
//
// CheckForUpdatesButton
//
@@ -275,19 +325,21 @@ private void InitializeComponent()
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(495, 442);
- this.Controls.Add(this.groupBox2);
+ this.Controls.Add(this.AutoUpdateGroupBox);
this.Controls.Add(this.CacheGroupBox);
this.Controls.Add(this.RepositoryGroupBox);
+ this.Controls.Add(this.AuthTokensGroupBox);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Name = "SettingsDialog";
this.Text = "Settings";
this.Load += new System.EventHandler(this.SettingsDialog_Load);
this.RepositoryGroupBox.ResumeLayout(false);
+ this.AuthTokensGroupBox.ResumeLayout(false);
this.CacheGroupBox.ResumeLayout(false);
this.CacheGroupBox.PerformLayout();
- this.groupBox2.ResumeLayout(false);
- this.groupBox2.PerformLayout();
+ this.AutoUpdateGroupBox.ResumeLayout(false);
+ this.AutoUpdateGroupBox.PerformLayout();
this.ResumeLayout(false);
}
@@ -303,11 +355,15 @@ private void InitializeComponent()
private System.Windows.Forms.ListBox ReposListBox;
private System.Windows.Forms.Button UpRepoButton;
private System.Windows.Forms.Button DownRepoButton;
- private System.Windows.Forms.GroupBox groupBox2;
+ private System.Windows.Forms.GroupBox AuthTokensGroupBox;
+ private System.Windows.Forms.ListBox AuthTokensListBox;
+ private System.Windows.Forms.Button NewAuthTokenButton;
+ private System.Windows.Forms.Button DeleteAuthTokenButton;
+ private System.Windows.Forms.GroupBox AutoUpdateGroupBox;
private System.Windows.Forms.Label LatestVersionLabel;
- private System.Windows.Forms.Label label4;
+ private System.Windows.Forms.Label LatestVersionLabelLabel;
private System.Windows.Forms.Label LocalVersionLabel;
- private System.Windows.Forms.Label label3;
+ private System.Windows.Forms.Label LocalVersionLabelLabel;
private System.Windows.Forms.Button CheckForUpdatesButton;
private System.Windows.Forms.Button InstallUpdateButton;
private System.Windows.Forms.CheckBox CheckUpdateOnLaunchCheckbox;
diff --git a/GUI/SettingsDialog.cs b/GUI/SettingsDialog.cs
index 60d958bade..6bb4d61580 100644
--- a/GUI/SettingsDialog.cs
+++ b/GUI/SettingsDialog.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;
+using System.Drawing;
using log4net;
namespace CKAN
@@ -29,6 +30,7 @@ private void SettingsDialog_Load(object sender, EventArgs e)
public void UpdateDialog()
{
RefreshReposListBox();
+ RefreshAuthTokensListBox();
LocalVersionLabel.Text = Meta.GetVersion();
@@ -225,6 +227,149 @@ private void DownRepoButton_Click(object sender, EventArgs e)
RefreshReposListBox();
}
+ private void RefreshAuthTokensListBox()
+ {
+ AuthTokensListBox.Items.Clear();
+ foreach (string host in Win32Registry.GetAuthTokenHosts())
+ {
+ string token;
+ if (Win32Registry.TryGetAuthToken(host, out token))
+ {
+ AuthTokensListBox.Items.Add(string.Format("{0} | {1}", host, token));
+ }
+ }
+ }
+
+ private void AuthTokensListBox_SelectedIndexChanged(object sender, EventArgs e)
+ {
+ DeleteAuthTokenButton.Enabled = AuthTokensListBox.SelectedItem != null;
+ }
+
+ private void NewAuthTokenButton_Click(object sender, EventArgs e)
+ {
+ // Inspired by https://stackoverflow.com/a/17546909/2422988
+ Form newAuthTokenPopup = new Form()
+ {
+ FormBorderStyle = FormBorderStyle.FixedToolWindow,
+ StartPosition = FormStartPosition.CenterParent,
+ ClientSize = new Size(300, 100),
+ Text = "Add Authentication Token"
+ };
+ Label hostLabel = new Label()
+ {
+ AutoSize = true,
+ Location = new Point(3, 6),
+ Size = new Size(271, 13),
+ Text = "Host:"
+ };
+ TextBox hostTextBox = new TextBox()
+ {
+ Location = new Point(45, 6),
+ Size = new Size(newAuthTokenPopup.ClientSize.Width - 40 - 10, 23),
+ Text = ""
+ };
+ Label tokenLabel = new Label()
+ {
+ AutoSize = true,
+ Location = new Point(3, 35),
+ Size = new Size(271, 13),
+ Text = "Token:"
+ };
+ TextBox tokenTextBox = new TextBox()
+ {
+ Location = new Point(45, 35),
+ Size = new Size(newAuthTokenPopup.ClientSize.Width - 40 - 10, 23),
+ Text = ""
+ };
+ Button acceptButton = new Button()
+ {
+ DialogResult = DialogResult.OK,
+ Name = "okButton",
+ Size = new Size(75, 23),
+ Text = "&Accept",
+ Location = new Point((newAuthTokenPopup.ClientSize.Width - 80 - 80) / 2, 64)
+ };
+ acceptButton.Click += (origin, evt) =>
+ {
+ newAuthTokenPopup.DialogResult = validNewAuthToken(hostTextBox.Text, tokenTextBox.Text)
+ ? DialogResult.OK
+ : DialogResult.None;
+ };
+ Button cancelButton = new Button()
+ {
+ DialogResult = DialogResult.Cancel,
+ Name = "cancelButton",
+ Size = new Size(75, 23),
+ Text = "&Cancel",
+ Location = new Point(acceptButton.Location.X + acceptButton.Size.Width + 5, 64)
+ };
+
+ newAuthTokenPopup.Controls.Add(hostLabel);
+ newAuthTokenPopup.Controls.Add(hostTextBox);
+ newAuthTokenPopup.Controls.Add(tokenLabel);
+ newAuthTokenPopup.Controls.Add(tokenTextBox);
+ newAuthTokenPopup.Controls.Add(acceptButton);
+ newAuthTokenPopup.Controls.Add(cancelButton);
+ newAuthTokenPopup.AcceptButton = acceptButton;
+ newAuthTokenPopup.CancelButton = cancelButton;
+
+ switch (newAuthTokenPopup.ShowDialog(this))
+ {
+ case DialogResult.Abort:
+ case DialogResult.Cancel:
+ case DialogResult.Ignore:
+ case DialogResult.No:
+ // User cancelled out, so do nothing
+ break;
+
+ case DialogResult.OK:
+ case DialogResult.Yes:
+ Win32Registry.SetAuthToken(hostTextBox.Text, tokenTextBox.Text);
+ RefreshAuthTokensListBox();
+ break;
+ }
+ }
+
+ private static bool validNewAuthToken(string host, string token)
+ {
+ if (host.Length <= 0)
+ {
+ GUI.user.RaiseError("Host field is required.");
+ return false;
+ }
+ if (token.Length <= 0)
+ {
+ GUI.user.RaiseError("Token field is required.");
+ return false;
+ }
+ if (Uri.CheckHostName(host) == UriHostNameType.Unknown)
+ {
+ GUI.user.RaiseError("{0} is not a valid host name.", host);
+ return false;
+ }
+ string oldToken;
+ if (Win32Registry.TryGetAuthToken(host, out oldToken))
+ {
+ GUI.user.RaiseError("{0} already has an authentication token.", host);
+ return false;
+ }
+
+ return true;
+ }
+
+ private void DeleteAuthTokenButton_Click(object sender, EventArgs e)
+ {
+ if (AuthTokensListBox.SelectedItem != null)
+ {
+ string item = AuthTokensListBox.SelectedItem as string;
+ string host = item?.Split('|')[0].Trim();
+
+ Win32Registry.SetAuthToken(host, null);
+ RefreshAuthTokensListBox();
+ DeleteRepoButton.Enabled = false;
+ }
+ }
+
private void CheckForUpdatesButton_Click(object sender, EventArgs e)
{
try