Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial pass of rewrite to use new Git Credential API

  • Loading branch information...
commit 92969a9f99f08c02f2fe727084aeeeec5a909c15 1 parent 563616d
@anurse authored
View
6 gitstorecred.sln → git-credential-winstore.sln
@@ -1,7 +1,7 @@

-Microsoft Visual Studio Solution File, Format Version 11.00
-# Visual Studio 2010
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gitstorecred", "gitstorecred\gitstorecred.csproj", "{B9FF2C14-839E-4770-9499-7BFE0252EC01}"
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 11
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "git-credential-winstore", "git-credential-winstore\git-credential-winstore.csproj", "{B9FF2C14-839E-4770-9499-7BFE0252EC01}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
View
29 git-credential-winstore/Extensions.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Git.Credential.WinStore
+{
+ static class Extensions
+ {
+ public static TValue GetOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> self, TKey key, TValue def)
+ {
+ TValue ret;
+ if (!self.TryGetValue(key, out ret))
+ {
+ return def;
+ }
+ return ret;
+ }
+
+ public static string TruncateTo(this string self, int maxlength)
+ {
+ if (self.Length > maxlength)
+ {
+ return self.Substring(0, maxlength);
+ }
+ return self;
+ }
+ }
+}
View
53 git-credential-winstore/NativeMethods.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Runtime.InteropServices;
+
+namespace Git.Credential.WinStore
+{
+ internal static class NativeMethods
+ {
+ [DllImport("Advapi32.dll", SetLastError = true, EntryPoint = "CredWriteW", CharSet = CharSet.Unicode)]
+ public static extern bool CredWrite(ref CREDENTIAL userCredential, UInt32 flags);
+
+ [DllImport("advapi32.dll", EntryPoint="CredReadW", CharSet=CharSet.Unicode, SetLastError = true)]
+ public static extern bool CredRead(string target, CRED_TYPE type, int reservedFlag, out IntPtr credentialPtr);
+
+ [DllImport("advapi32.dll", EntryPoint = "CredDeleteW", CharSet = CharSet.Unicode)]
+ public static extern bool CredDelete(string target, CRED_TYPE type, int flags);
+
+ [DllImport("advapi32.dll")]
+ public static extern void CredFree(IntPtr credentialPtr);
+
+ public enum CRED_TYPE : int
+ {
+ GENERIC = 1,
+ DOMAIN_PASSWORD = 2,
+ DOMAIN_CERTIFICATE = 3,
+ DOMAIN_VISIBLE_PASSWORD = 4,
+ MAXIMUM = 5
+ }
+
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ public struct CREDENTIAL
+ {
+ public int flags;
+ public int type;
+ [MarshalAs(UnmanagedType.LPWStr)]
+ public string targetName;
+ [MarshalAs(UnmanagedType.LPWStr)]
+ public string comment;
+ public System.Runtime.InteropServices.ComTypes.FILETIME lastWritten;
+ public int credentialBlobSize;
+ public IntPtr credentialBlob;
+ public int persist;
+ public int attributeCount;
+ public IntPtr credAttribute;
+ [MarshalAs(UnmanagedType.LPWStr)]
+ public string targetAlias;
+ [MarshalAs(UnmanagedType.LPWStr)]
+ public string userName;
+ }
+ }
+}
View
176 git-credential-winstore/Program.cs
@@ -0,0 +1,176 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Runtime.InteropServices;
+using System.Text.RegularExpressions;
+using System.ComponentModel;
+
+namespace Git.Credential.WinStore
+{
+ class Program
+ {
+ private static Dictionary<string, Func<IDictionary<string, string>, IEnumerable<Tuple<string, string>>>> _commands = new Dictionary<string, Func<IDictionary<string, string>, IEnumerable<Tuple<string, string>>>>(StringComparer.OrdinalIgnoreCase)
+ {
+ { "get", GetCommand },
+ { "store", StoreCommand },
+ { "erase", EraseCommand }
+ };
+
+ static void Main(string[] args)
+ {
+ // Read arguments
+ IDictionary<string, string> parameters = ReadGitParameters();
+
+ // Parse command
+ Func<IDictionary<string, string>, IEnumerable<Tuple<string, string>>> command = null;
+ string cmd;
+ if (args.Length == 0)
+ {
+ // Debugging mode, cmd is a parameter
+ cmd = parameters.GetOrDefault("cmd", "get");
+ }
+ else
+ {
+ cmd = args[0];
+ }
+
+ if (!_commands.TryGetValue(cmd, out command))
+ {
+ WriteUsage();
+ return;
+ }
+ IDictionary<string, string> response = command(parameters).ToDictionary(
+ t => t.Item1,
+ t => t.Item2);
+ WriteGitParameters(response);
+ }
+
+ private static void WriteGitParameters(IDictionary<string, string> response)
+ {
+ foreach (var pair in response)
+ {
+ Console.WriteLine("{0}={1}", pair.Key, pair.Value);
+ }
+ Console.WriteLine();
+ }
+
+ private static IDictionary<string, string> ReadGitParameters()
+ {
+ string line;
+ Dictionary<string, string> values = new Dictionary<string, string>();
+ while (!String.IsNullOrWhiteSpace((line = Console.ReadLine())))
+ {
+ string[] splitted = line.Split('=');
+ values[splitted[0]] = splitted[1];
+ }
+ return values;
+ }
+
+ private static void WriteUsage()
+ {
+ Console.WriteLine("Figure it out yourself. But seriously, I just haven't written this yet. Sorry :(");
+ }
+
+ static IEnumerable<Tuple<string, string>> GetCommand(IDictionary<string, string> args)
+ {
+ // Build the URL
+ Uri url = ExtractUrl(args);
+
+ IntPtr credPtr = IntPtr.Zero;
+ try
+ {
+ // Check for a credential
+ string target = GetTargetName(url);
+ if (!NativeMethods.CredRead(target, NativeMethods.CRED_TYPE.GENERIC, 0, out credPtr))
+ {
+ // Don't have a credential for this user.
+ yield break;
+ }
+
+ // Decode the credential
+ NativeMethods.CREDENTIAL cred = (NativeMethods.CREDENTIAL)Marshal.PtrToStructure(credPtr, typeof(NativeMethods.CREDENTIAL));
+ yield return Tuple.Create("username", cred.userName);
+ yield return Tuple.Create("password", Marshal.PtrToStringBSTR(cred.credentialBlob));
+ }
+ finally
+ {
+ if (credPtr != IntPtr.Zero)
+ {
+ NativeMethods.CredFree(credPtr);
+ }
+ }
+ }
+
+ static IEnumerable<Tuple<string, string>> StoreCommand(IDictionary<string, string> args)
+ {
+ // Build the URL
+ Uri url = ExtractUrl(args);
+ string userName = args.GetOrDefault("username", String.Empty);
+ string password = args.GetOrDefault("password", String.Empty);
+
+ bool abort = false;
+ if(abort |= String.IsNullOrEmpty(userName)) {
+ Console.Error.WriteLine("username parameter must be provided");
+ }
+ if(abort |= String.IsNullOrEmpty(password)) {
+ Console.Error.WriteLine("password parameter must be provided");
+ }
+ if (!abort)
+ {
+ string target = GetTargetName(url);
+ IntPtr passwordPtr = Marshal.StringToBSTR(password);
+ NativeMethods.CREDENTIAL cred = new NativeMethods.CREDENTIAL()
+ {
+ type = 0x01, // Generic
+ targetName = target,
+ credentialBlob = Marshal.StringToCoTaskMemUni(password),
+ persist = 0x03, // Enterprise (roaming)
+ attributeCount = 0,
+ userName = userName
+ };
+ cred.credentialBlobSize = Encoding.Unicode.GetByteCount(password);
+ if (!NativeMethods.CredWrite(ref cred, 0))
+ {
+ Console.Error.WriteLine(
+ "Failed to write credential: " +
+ GetLastErrorMessage());
+ }
+ }
+ return Enumerable.Empty<Tuple<string, string>>();
+ }
+
+ static IEnumerable<Tuple<string, string>> EraseCommand(IDictionary<string, string> args)
+ {
+ Uri url = ExtractUrl(args);
+ if (!NativeMethods.CredDelete(GetTargetName(url), NativeMethods.CRED_TYPE.GENERIC, 0))
+ {
+ Console.Error.WriteLine(
+ "Failed to erase credential: " +
+ GetLastErrorMessage());
+ }
+ yield break;
+ }
+
+ private static string GetLastErrorMessage()
+ {
+ return new Win32Exception(Marshal.GetLastWin32Error()).Message;
+ }
+
+ private static Uri ExtractUrl(IDictionary<string, string> args)
+ {
+ Uri url = new UriBuilder()
+ {
+ Scheme = args.GetOrDefault("protocol", "https"),
+ Host = args.GetOrDefault("host", "no-host.git"),
+ Path = args.GetOrDefault("path", "/")
+ }.Uri;
+ return url;
+ }
+
+ private static string GetTargetName(Uri url)
+ {
+ return "git:" + url.AbsoluteUri;
+ }
+ }
+}
View
0  gitstorecred/Properties/AssemblyInfo.cs → git-credential-winstore/Properties/AssemblyInfo.cs
File renamed without changes
View
5 gitstorecred/gitstorecred.csproj → ...redential-winstore/git-credential-winstore.csproj
@@ -8,8 +8,8 @@
<ProjectGuid>{B9FF2C14-839E-4770-9499-7BFE0252EC01}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
- <RootNamespace>gitstorecred</RootNamespace>
- <AssemblyName>gitstorecred</AssemblyName>
+ <RootNamespace>Git.Credential.WinStore</RootNamespace>
+ <AssemblyName>git-credential-winstore</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<TargetFrameworkProfile>Client</TargetFrameworkProfile>
<FileAlignment>512</FileAlignment>
@@ -43,6 +43,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
+ <Compile Include="Extensions.cs" />
<Compile Include="NativeMethods.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
View
68 gitstorecred/NativeMethods.cs
@@ -1,68 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Runtime.InteropServices;
-
-namespace gitstorecred
-{
- internal static class NativeMethods
- {
- [DllImport("credui.dll", CharSet = CharSet.Unicode)]
- public static extern CredUIReturnCodes CredUIPromptForCredentials(
- ref CREDUI_INFO creditUR,
- string targetName,
- IntPtr reserved1,
- int iError,
- StringBuilder userName,
- int maxUserName,
- StringBuilder password,
- int maxPassword,
- [MarshalAs(UnmanagedType.Bool)] ref bool pfSave,
- CREDUI_FLAGS flags);
-
- [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
- public struct CREDUI_INFO
- {
- public int cbSize;
- public IntPtr hwndParent;
- public string pszMessageText;
- public string pszCaptionText;
- public IntPtr hbmBanner;
- }
-
- [Flags]
- public enum CREDUI_FLAGS
- {
- INCORRECT_PASSWORD = 0x1,
- DO_NOT_PERSIST = 0x2,
- REQUEST_ADMINISTRATOR = 0x4,
- EXCLUDE_CERTIFICATES = 0x8,
- REQUIRE_CERTIFICATE = 0x10,
- SHOW_SAVE_CHECK_BOX = 0x40,
- ALWAYS_SHOW_UI = 0x80,
- REQUIRE_SMARTCARD = 0x100,
- PASSWORD_ONLY_OK = 0x200,
- VALIDATE_USERNAME = 0x400,
- COMPLETE_USERNAME = 0x800,
- PERSIST = 0x1000,
- SERVER_CREDENTIAL = 0x4000,
- EXPECT_CONFIRMATION = 0x20000,
- GENERIC_CREDENTIALS = 0x40000,
- USERNAME_TARGET_CREDENTIALS = 0x80000,
- KEEP_USERNAME = 0x100000,
- }
-
- public enum CredUIReturnCodes
- {
- NO_ERROR = 0,
- ERROR_CANCELLED = 1223,
- ERROR_NO_SUCH_LOGON_SESSION = 1312,
- ERROR_NOT_FOUND = 1168,
- ERROR_INVALID_ACCOUNT_NAME = 1315,
- ERROR_INSUFFICIENT_BUFFER = 122,
- ERROR_INVALID_PARAMETER = 87,
- ERROR_INVALID_FLAGS = 1004,
- }
- }
-}
View
87 gitstorecred/Program.cs
@@ -1,87 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Runtime.InteropServices;
-using System.Text.RegularExpressions;
-
-namespace gitstorecred
-{
- class Program
- {
- static Regex GitRequestRegex = new Regex(@"(?<req>(Username)|(Password)) for '(?<url>[^']*)':\s*");
-
- static void Main(string[] args)
- {
- // Parse args
- string cmd = String.Join(" ", args);
- if (String.IsNullOrWhiteSpace(cmd))
- {
- cmd = Console.ReadLine();
- }
-
- Match m = GitRequestRegex.Match(cmd);
- if (!m.Success)
- {
- Console.WriteLine("Unexpected arguments");
- return;
- }
- string request = m.Groups["req"].Value;
- Uri url = (new Uri(m.Groups["url"].Value));
-
- // Extract the URL without user name to use as realm
- string realm = url.GetComponents(
- UriComponents.SchemeAndServer,
- UriFormat.Unescaped);
-
- bool save = true;
- int userNameSize = 255;
- int domainNameSize = 255;
- int passwordSize = 255;
- StringBuilder userName = new StringBuilder(userNameSize);
- StringBuilder domainName = new StringBuilder(domainNameSize);
- StringBuilder password = new StringBuilder(passwordSize);
-
- NativeMethods.CREDUI_FLAGS flags =
- NativeMethods.CREDUI_FLAGS.EXCLUDE_CERTIFICATES |
- NativeMethods.CREDUI_FLAGS.GENERIC_CREDENTIALS |
- NativeMethods.CREDUI_FLAGS.SHOW_SAVE_CHECK_BOX;
- string message = realm;
- if(String.Equals("Password", request, StringComparison.OrdinalIgnoreCase) && !String.IsNullOrEmpty(url.UserInfo)) {
- message = "anurse on " + realm;
- flags |= NativeMethods.CREDUI_FLAGS.KEEP_USERNAME;
- }
-
- NativeMethods.CREDUI_INFO info = new NativeMethods.CREDUI_INFO() {
- pszCaptionText = "Git Credentials",
- pszMessageText = "Enter your credentials for " + message,
- };
- info.cbSize = Marshal.SizeOf(info);
-
- string target = realm;
- NativeMethods.CredUIReturnCodes ret = NativeMethods.CredUIPromptForCredentials(
- ref info,
- targetName: target,
- reserved1: IntPtr.Zero,
- iError: 0,
- userName: userName,
- maxUserName: 255,
- password: password,
- maxPassword: 255,
- pfSave: ref save,
- flags: flags);
- if (ret != NativeMethods.CredUIReturnCodes.NO_ERROR)
- {
- Console.Error.WriteLine("Error Prompting for Credentials: " + ret.ToString());
- Environment.Exit((int)ret);
- return;
- }
-
- if(String.Equals("Username", request, StringComparison.OrdinalIgnoreCase)) {
- Console.Write(userName.ToString());
- } else if(String.Equals("Password", request, StringComparison.OrdinalIgnoreCase)) {
- Console.Write(password.ToString());
- }
- }
- }
-}
Please sign in to comment.
Something went wrong with that request. Please try again.