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