1 /*
2  *             Copyright Andrej Mitrovic 2013.
3  *  Distributed under the Boost Software License, Version 1.0.
4  *     (See accompanying file LICENSE_1_0.txt or copy at
5  *           http://www.boost.org/LICENSE_1_0.txt)
6  */
7 module git.credentials;
8 
9 import std.conv;
10 import std.exception;
11 import std..string;
12 import std.typecons;
13 
14 import deimos.git2.transport;
15 
16 import git.exception;
17 import git.util;
18 import git.version_;
19 
20 version (GIT_SSH)
21 {
22     static assert(0, "dlibgit does not support SSH yet.");
23 }
24 
25 static if (targetLibGitVersion == VersionInfo(0, 19, 0)):
26 
27 /* The base structure for all credential types. */
28 struct GitCred
29 {
30     // internal
31     private this(git_cred* cred)
32     {
33         _data = Data(cred);
34     }
35 
36     /**
37         Return the actual credential type of this credential.
38         Use the $(D get) template to cast this type to the
39         type tagged as $(D credType).
40     */
41     @property GitCredType credType()
42     {
43         return cast(GitCredType)_data._payload.credtype;
44     }
45 
46     /** Throw if the target credential type is not equal to credType that's stored. */
47     void verifyTypeMatch(GitCredType target)
48     {
49         enforceEx!GitException(credType == target,
50                                format("Tried to cast GitCred of type '%s' to type '%s'",
51                                       credType, target));
52     }
53 
54     /**
55         Cast this credential to the structure type matching
56         the $(D cred) enum tag.
57 
58         If the underlying type does not match the target type,
59         a $(D GitException) is thrown.
60     */
61     auto get(GitCredType cred)()
62     {
63         return get!(_credToType!cred);
64     }
65 
66     /**
67         Cast this credential to a specific credential type $(D T).
68 
69         If the underlying type does not match the target type,
70         a $(D GitException) is thrown.
71     */
72     T get(T)() if (isGitCredential!T)
73     {
74         verifyTypeMatch(T.credType);
75         return getImpl!T;
76     }
77 
78 package:
79     /**
80      * The internal libgit2 handle for this object.
81      *
82      * Care should be taken not to escape the reference outside a scope where
83      * a GitCred encapsulating the handle is kept alive.
84      */
85     @property git_cred* cHandle()
86     {
87         return _data._payload;
88     }
89 
90 private:
91 
92     T getImpl(T : GitCred_PlainText)()
93     {
94         auto cred = cast(T.c_cred_struct*)_data._payload;
95 
96         T result;
97         result.parent = this;
98         result.username = to!string(cred.username);
99         result.password = to!string(cred.password);
100         return result;
101     }
102 
103     version (GIT_SSH)
104     {
105         static assert(0, "dlibgit does not support SSH yet.");
106 
107         T getImpl(T : GitCred_KeyFilePassPhrase)()
108         {
109             auto cred = cast(T.c_cred_struct*)_data._payload;
110 
111             T result;
112             result.parent = this;
113             result.publickey = to!string(cred.publickey);
114             result.privatekey = to!string(cred.privatekey);
115             result.passphrase = to!string(cred.passphrase);
116             return result;
117         }
118 
119         T getImpl(T : GitCred_PublicKey)()
120         {
121             auto cred = cast(T.c_cred_struct*)_data._payload;
122 
123             T result;
124             result.parent = this;
125             result.publickey = cred.publickey[0 .. cred.publickey_len];
126             result.sign_callback = cred.sign_callback;
127             result.sign_data = cred.sign_data;
128             return result;
129         }
130     }
131 
132     /** Payload for the $(D git_cred) object which should be refcounted. */
133     struct Payload
134     {
135         this(git_cred* payload)
136         {
137             _payload = payload;
138         }
139 
140         ~this()
141         {
142             //~ writefln("- %s", __FUNCTION__);
143 
144             if (_payload !is null)
145             {
146                 _payload.free(_payload);
147                 _payload = null;
148             }
149         }
150 
151         /// Should never perform copy
152         @disable this(this);
153 
154         /// Should never perform assign
155         @disable void opAssign(typeof(this));
156 
157         git_cred* _payload;
158     }
159 
160     alias RefCounted!(Payload, RefCountedAutoInitialize.no) Data;
161     Data _data;
162 }
163 
164 ///
165 enum GitCredType
166 {
167     ///
168 	plaintext = GIT_CREDTYPE_USERPASS_PLAINTEXT,
169 
170     ///
171 	passphrase = GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE,
172 
173     ///
174 	publickey = GIT_CREDTYPE_SSH_PUBLICKEY,
175 }
176 
177 /* A plaintext username and password. */
178 struct GitCred_PlainText
179 {
180     ///
181     enum credType = GitCredType.plaintext;
182 
183     ///
184 	GitCred parent;
185 
186     ///
187 	string username;
188 
189     ///
190 	string password;
191 
192     private alias c_cred_struct = git_cred_userpass_plaintext;
193 }
194 
195 version (GIT_SSH)
196 {
197     static assert(0, "dlibgit does not support SSH yet.");
198 
199     /* A ssh key file and passphrase. */
200     struct GitCred_KeyFilePassPhrase
201     {
202         ///
203         enum credType = GitCredType.passphrase;
204 
205         ///
206         GitCred parent;
207 
208         ///
209         string publicKey;
210 
211         ///
212         string privateKey;
213 
214         ///
215         string passPhrase;
216 
217         private alias c_cred_struct = git_cred_ssh_keyfile_passphrase;
218     }
219 
220     /* A ssh public key and authentication callback. */
221     struct GitCred_PublicKey
222     {
223         ///
224         enum credType = GitCredType.publickey;
225 
226         ///
227         GitCred parent;
228 
229         ///
230         ubyte[] publicKey;
231 
232         ///
233         void* signCallback;
234 
235         ///
236         void* signData;
237 
238         private alias c_cred_struct = git_cred_ssh_publickey;
239     }
240 }
241 
242 /** Check if type $(D T) is one of the supported git credential types. */
243 template isGitCredential(T)
244 {
245     version (GIT_SSH)
246     {
247         static assert(0, "dlibgit does not support SSH yet.");
248 
249         enum bool isGitCredential = is(T == GitCred_PlainText) ||
250                                     is(T == GitCred_KeyFilePassPhrase) ||
251                                     is(T == GitCred_PublicKey);
252     }
253     else
254     {
255         enum bool isGitCredential = is(T == GitCred_PlainText);
256     }
257 }
258 
259 // helper
260 private template _credToType(GitCredType credType)
261 {
262     static if (credType == GitCredType.plaintext)
263         alias _credToType = GitCred_PlainText;
264     else
265     version (GIT_SSH)
266     {
267         static assert(0, "dlibgit does not support SSH yet.");
268 
269         static if (credType == GitCredType.passphrase)
270             alias _credToType = GitCred_KeyFilePassPhrase;
271         else
272         static if (credType == GitCredType.publickey)
273             alias _credToType = GitCred_PublicKey;
274         else
275         static assert(0);
276     }
277     else
278     static assert(0);
279 }
280 
281 /**
282     Creates a new plain-text username and password credential object.
283     The supplied credential parameter will be internally duplicated.
284 */
285 GitCred getCredPlainText(string username, string password)
286 {
287     git_cred* _git_cred;
288     require(git_cred_userpass_plaintext_new(&_git_cred, username.gitStr, password.gitStr) == 0);
289     return GitCred(_git_cred);
290 }
291 
292 ///
293 unittest
294 {
295     auto cred = getCredPlainText("user", "pass");
296 
297     switch (cred.credType) with (GitCredType)
298     {
299         case plaintext:
300         {
301             version (GIT_SSH)
302             {
303                 static assert(0, "dlibgit does not support SSH yet.");
304 
305                 // throw when trying to cast to an inappropriate type
306                 assertThrown!GitException(cred.get!passphrase);
307 
308                 // ditto
309                 assertThrown!GitException(cred.get!GitCred_KeyFilePassPhrase);
310             }
311 
312             // use enum for the get template
313             auto cred1 = cred.get!plaintext;
314             assert(cred1.username == "user");
315             assert(cred1.password == "pass");
316 
317             // or use a type
318             auto cred2 = cred.get!GitCred_PlainText;
319             assert(cred2.username == "user");
320             assert(cred2.password == "pass");
321 
322             break;
323         }
324 
325         default: assert(0, text(cred.credType));
326     }
327 }
328 
329 version (GIT_SSH)
330 {
331     static assert(0, "dlibgit does not support SSH yet.");
332 
333     /**
334         Creates a new ssh key file and passphrase credential object.
335         The supplied credential parameter will be internally duplicated.
336 
337         Params:
338 
339         - $(D publicKey): The path to the public key of the credential.
340         - $(D privateKey): The path to the private key of the credential.
341         - $(D passPhrase): The passphrase of the credential.
342     */
343     GitCred getCredKeyFilePassPhrase(string publicKey, string privateKey, string passPhrase)
344     {
345         git_cred* _git_cred;
346         require(git_cred_ssh_keyfile_passphrase_new(&_git_cred, publicKey.gitStr, privateKey.gitStr, passPhrase.gitStr) == 0);
347         return GitCred(_git_cred);
348     }
349 
350     ///
351     unittest
352     {
353         auto cred = getCredKeyFilePassPhrase("public", "private", "passphrase");
354 
355         switch (cred.credType) with (GitCredType)
356         {
357             case passphrase:
358             {
359                 // throw when trying to cast to an inappropriate type
360                 assertThrown!GitException(cred.get!plaintext);
361 
362                 // ditto
363                 assertThrown!GitException(cred.get!GitCred_PlainText);
364 
365                 // use enum for the get template
366                 auto cred1 = cred.get!passphrase;
367                 assert(cred1.publicKey == "public");
368                 assert(cred1.privateKey == "private");
369                 assert(cred1.passPhrase == "passphrase");
370 
371                 // or use a type
372                 auto cred2 = cred.get!GitCred_KeyFilePassPhrase;
373                 assert(cred2.publicKey == "public");
374                 assert(cred2.privateKey == "private");
375                 assert(cred2.passPhrase == "passphrase");
376 
377                 break;
378             }
379 
380             default: assert(0, text(cred.credType));
381         }
382     }
383 
384     /**
385         Creates a new ssh public key credential object.
386         The supplied credential parameter will be internally duplicated.
387 
388         Params:
389 
390         - $(D publicKey): The bytes of the public key.
391         - $(D signCallback): The callback method for authenticating.
392         - $(D signData): The abstract data sent to the $(D signCallback) method.
393     */
394     GitCred getCredPublicKey(ubyte[] publicKey, void* signCallback, void* signData)
395     {
396         git_cred* _git_cred;
397         require(git_cred_ssh_publickey_new(&_git_cred, publicKey.ptr, publicKey.length, signCallback, signData) == 0);
398         return GitCred(_git_cred);
399     }
400 
401     ///
402     unittest
403     {
404         auto cred = getCredPublicKey([], null, null);
405 
406         switch (cred.credType) with (GitCredType)
407         {
408             case publickey:
409             {
410                 // throw when trying to cast to an inappropriate type
411                 assertThrown!GitException(cred.get!plaintext);
412 
413                 // ditto
414                 assertThrown!GitException(cred.get!GitCred_PlainText);
415 
416                 // use enum for the get template
417                 auto cred1 = cred.get!publickey;
418                 assert(cred1.publicKey == []);
419                 assert(cred1.signCallback is null);
420                 assert(cred1.signData is null);
421 
422                 // or use a type
423                 auto cred2 = cred.get!GitCred_PublicKey;
424                 assert(cred2.publicKey == []);
425                 assert(cred2.signCallback is null);
426                 assert(cred2.signData is null);
427 
428                 break;
429             }
430 
431             default: assert(0, text(cred.credType));
432         }
433     }
434 }
435 
436 alias GitCredAcquireDelegate = GitCred delegate(
437     in char[] url,
438     in char[] usernameFromURL,
439     uint allowedTypes);