diff --git a/Classes/GTCredential+Private.h b/Classes/GTCredential+Private.h new file mode 100644 index 000000000..866b389a7 --- /dev/null +++ b/Classes/GTCredential+Private.h @@ -0,0 +1,30 @@ +// +// GTCredential+Private.h +// ObjectiveGitFramework +// +// Created by Etienne on 10/09/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "GTCredential.h" + +// If you need to authenticate an operation in libgit2, you'll have to have +// a `GTCredentialProvider` handy, like a parameter in the method you're writing, +// setup a GTCredentialAcquireCallbackInfo struct on the stack, and pass both as +// the arguments to the operation you're attempting. +// +// Example: ``` +// struct GTCredentialAcquireCallbackInfo info = { .credProvider = myProvider } +// git_remote_set_cred_acquire_cb(&git_remote, GTCredentialAcquireCallback, &payload); +// ``` +// +// `GTCredentialAcquireCallback` will act as a trampoline, and will ask the +// `GTCredentialProvider` for a `GTCredential` object corresponding to the +// information requested by `libgit2`. It is the providers's responsibility +// to check the auth type and initialize its `GTCredential` object accordingly. + +typedef struct { + __unsafe_unretained GTCredentialProvider *credProvider; +} GTCredentialAcquireCallbackInfo; + +int GTCredentialAcquireCallback(git_cred **cred, const char *url, const char *username_from_url, unsigned int allowed_types, void *payload); diff --git a/Classes/GTCredential.h b/Classes/GTCredential.h new file mode 100644 index 000000000..c68ca0179 --- /dev/null +++ b/Classes/GTCredential.h @@ -0,0 +1,76 @@ +// +// GTCredential.h +// ObjectiveGitFramework +// +// Created by Etienne on 10/09/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import "git2.h" + +// An enum describing the data needed for authentication. +// See `git_credtype_t`. +typedef enum { + GTCredentialTypeUserPassPlaintext = GIT_CREDTYPE_USERPASS_PLAINTEXT, + GTCredentialTypeSSHKeyFilePassPhrase = GIT_CREDTYPE_SSH_KEYFILE_PASSPHRASE, + GTCredentialTypeSSHPublicKey = GIT_CREDTYPE_SSH_PUBLICKEY, +} GTCredentialType; + +@class GTCredential; + +// The GTCredentialProvider acts as a proxy for GTCredential requests. +// +// The default implementation is used through `+providerWithBlock:`, +// passing your own block that will build a GTCredential object. +// But you're allowed to subclass it and handle more complex workflows. +@interface GTCredentialProvider : NSObject + +// Creates a provider from a block. +// +// credentialBlock - a block that will be called when credentials are requested. ++ (instancetype)providerWithBlock:(GTCredential *(^)(GTCredentialType type, NSString *URL, NSString *userName))credentialBlock; + +// Default credential provider method. +// +// This method will get called when an operation requests credentials from the +// provider. +// +// The default implementation calls through the `providedBlock` passed +// in `providerWithBlock:` above, but your subclass is expected to override it +// to do its specific work. +// +// type - the credential types allowed by the operation. +// URL - the URL the operation is authenticating against. +// userName - the user name provided by the operation. Can be nil, and might be ignored. +- (GTCredential *)credentialForType:(GTCredentialType)type URL:(NSString *)URL userName:(NSString *)userName; +@end + +// The GTCredential class is used to provide authentication data. +// It acts as a wrapper around `git_cred` objects. +@interface GTCredential : NSObject + +// Create a credential object from a username/password pair. +// +// userName - The username to authenticate as. +// password - The password belonging to that user. +// error - If not NULL, set to any errors that occur. +// +// Return a new GTCredential instance, or nil if an error occurred ++ (instancetype)credentialWithUserName:(NSString *)userName password:(NSString *)password error:(NSError **)error; + +// Create a credential object from a SSH keyfile +// +// userName - The username to authenticate as. +// publicKeyURL - The URL to the public key for that user. +// Can be omitted to reconstruct the public key from the private key. +// privateKeyURL - The URL to the private key for that user. +// passphrase - The passPhrase for the private key. Optional if the private key has no password. +// error - If not NULL, set to any errors that occur. +// +// Return a new GTCredential instance, or nil if an error occurred ++ (instancetype)credentialWithUserName:(NSString *)userName publicKeyURL:(NSURL *)publicKeyURL privateKeyURL:(NSURL *)privateKeyURL passphrase:(NSString *)passphrase error:(NSError **)error; + +// The underlying `git_cred` object. +- (git_cred *)git_cred __attribute__((objc_returns_inner_pointer)); + +@end diff --git a/Classes/GTCredential.m b/Classes/GTCredential.m new file mode 100644 index 000000000..60bf1097f --- /dev/null +++ b/Classes/GTCredential.m @@ -0,0 +1,108 @@ +// +// GTCredential.m +// ObjectiveGitFramework +// +// Created by Etienne on 10/09/13. +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// + +#import +#import "GTCredential.h" +#import "GTCredential+Private.h" +#import + +typedef GTCredential *(^GTCredentialProviderBlock)(GTCredentialType allowedTypes, NSString *URL, NSString *userName); + +@interface GTCredentialProvider () +@property (nonatomic, readonly, copy) GTCredentialProviderBlock credBlock; +@end + +@implementation GTCredentialProvider ++ (instancetype)providerWithBlock:(GTCredentialProviderBlock)credentialBlock { + NSParameterAssert(credentialBlock != nil); + + GTCredentialProvider *provider = [[self alloc] init]; + + provider->_credBlock = [credentialBlock copy]; + + return provider; +} + +- (GTCredential *)credentialForType:(GTCredentialType)type URL:(NSString *)URL userName:(NSString *)userName { + NSAssert(self.credBlock != nil, @"Provider asked for credentials without block being set."); + + return self.credBlock(type, URL, userName); +} + +@end + +@interface GTCredential () +@property (nonatomic, assign, readonly) git_cred *git_cred; +@end + +@implementation GTCredential + ++ (instancetype)credentialWithUserName:(NSString *)userName password:(NSString *)password error:(NSError **)error { + git_cred *cred; + int gitError = git_cred_userpass_plaintext_new(&cred, userName.UTF8String, password.UTF8String); + if (gitError != GIT_OK) { + if (error) *error = [NSError git_errorFor:gitError description:@"Failed to create credentials object" failureReason:@"There was an error creating a credential object for username %@.", userName]; + return nil; + } + + return [[self alloc] initWithGitCred:cred]; +} + ++ (instancetype)credentialWithUserName:(NSString *)userName publicKeyURL:(NSURL *)publicKeyURL privateKeyURL:(NSURL *)privateKeyURL passphrase:(NSString *)passphrase error:(NSError **)error { + NSParameterAssert(privateKeyURL != nil); + NSString *publicKeyPath = publicKeyURL.filePathURL.path; + NSString *privateKeyPath = privateKeyURL.filePathURL.path; + NSAssert(privateKeyPath != nil, @"Invalid file URL passed: %@", privateKeyURL); + + git_cred *cred; + int gitError = git_cred_ssh_keyfile_passphrase_new(&cred, userName.UTF8String, publicKeyPath.fileSystemRepresentation, privateKeyPath.fileSystemRepresentation, passphrase.UTF8String); + if (gitError != GIT_OK) { + if (error) *error = [NSError git_errorFor:gitError description:@"Failed to create credentials object" failureReason:@"There was an error creating a credential object for username %@ with the provided public/private key pair.\nPublic key: %@\nPrivate key: %@", userName, publicKeyURL, privateKeyURL]; + return nil; + } + + return [[self alloc] initWithGitCred:cred]; +} + +- (instancetype)initWithGitCred:(git_cred *)cred { + NSParameterAssert(cred != nil); + self = [self init]; + + if (self == nil) return nil; + + _git_cred = cred; + + return self; +} + +@end + +int GTCredentialAcquireCallback(git_cred **git_cred, const char *url, const char *username_from_url, unsigned int allowed_types, void *payload) { + NSCParameterAssert(git_cred != NULL); + NSCParameterAssert(payload != NULL); + + GTCredentialAcquireCallbackInfo *info = payload; + GTCredentialProvider *provider = info->credProvider; + + if (provider == nil) { + giterr_set_str(GIT_EUSER, "No GTCredentialProvider set, but authentication was requested."); + return GIT_ERROR; + } + + NSString *URL = (url != NULL ? @(url) : nil); + NSString *userName = (username_from_url != NULL ? @(username_from_url) : nil); + + GTCredential *cred = [provider credentialForType:(GTCredentialType)allowed_types URL:URL userName:userName]; + if (cred == nil) { + giterr_set_str(GIT_EUSER, "GTCredentialProvider failed to provide credentials."); + return GIT_ERROR; + } + + *git_cred = cred.git_cred; + return GIT_OK; +} diff --git a/Classes/GTRepository.h b/Classes/GTRepository.h index 9488891c8..bf4eeda65 100644 --- a/Classes/GTRepository.h +++ b/Classes/GTRepository.h @@ -114,6 +114,9 @@ extern NSString *const GTRepositoryCloneOptionsBare; // Default value is `YES`. extern NSString *const GTRepositoryCloneOptionsCheckout; +// A `GTCredentialProvider`, that will be used to authenticate against the remote. +extern NSString *const GTRepositoryCloneOptionsCredentialProvider; + typedef void (^GTRepositoryStatusBlock)(NSURL *fileURL, GTRepositoryFileStatus status, BOOL *stop); @interface GTRepository : NSObject diff --git a/Classes/GTRepository.m b/Classes/GTRepository.m index f9c50433e..070993fb6 100644 --- a/Classes/GTRepository.m +++ b/Classes/GTRepository.m @@ -43,10 +43,13 @@ #import "NSError+Git.h" #import "NSString+Git.h" #import "GTDiffFile.h" +#import "GTCredential.h" +#import "GTCredential+Private.h" NSString *const GTRepositoryCloneOptionsBare = @"GTRepositoryCloneOptionsBare"; NSString *const GTRepositoryCloneOptionsCheckout = @"GTRepositoryCloneOptionsCheckout"; NSString *const GTRepositoryCloneOptionsTransportFlags = @"GTRepositoryCloneOptionsTransportFlags"; +NSString *const GTRepositoryCloneOptionsCredentialProvider = @"GTRepositoryCloneOptionsCredentialProvider"; // The type of block passed to -enumerateSubmodulesRecursively:usingBlock:. typedef void (^GTRepositorySubmoduleEnumerationBlock)(GTSubmodule *submodule, BOOL *stop); @@ -192,6 +195,13 @@ + (id)cloneFromURL:(NSURL *)originURL toWorkingDirectory:(NSURL *)workdirURL opt cloneOptions.checkout_opts = checkoutOptions; } + GTCredentialProvider *provider = options[GTRepositoryCloneOptionsCredentialProvider]; + if (provider) { + GTCredentialAcquireCallbackInfo info = { .credProvider = provider }; + cloneOptions.cred_acquire_cb = GTCredentialAcquireCallback; + cloneOptions.cred_acquire_payload = &info; + } + cloneOptions.fetch_progress_cb = transferProgressCallback; cloneOptions.fetch_progress_payload = (__bridge void *)transferProgressBlock; diff --git a/ObjectiveGitFramework.xcodeproj/project.pbxproj b/ObjectiveGitFramework.xcodeproj/project.pbxproj index 9f98f762a..804369c21 100644 --- a/ObjectiveGitFramework.xcodeproj/project.pbxproj +++ b/ObjectiveGitFramework.xcodeproj/project.pbxproj @@ -116,6 +116,9 @@ 30FDC08216835A8100654BF0 /* GTDiffLine.m in Sources */ = {isa = PBXBuildFile; fileRef = 30FDC07E16835A8100654BF0 /* GTDiffLine.m */; }; 3E0A23E5159E0FDB00A6068F /* GTObjectDatabase.h in Headers */ = {isa = PBXBuildFile; fileRef = 55C8054C13861F34004DCB0F /* GTObjectDatabase.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4D26799F178DAF31002A2795 /* GTTreeEntry+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D26799D178DAF31002A2795 /* GTTreeEntry+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 4D79C0EE17DF9F4D00997DE4 /* GTCredential.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D79C0EC17DF9F4D00997DE4 /* GTCredential.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4D79C0EF17DF9F4D00997DE4 /* GTCredential.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D79C0ED17DF9F4D00997DE4 /* GTCredential.m */; }; + 4D79C0F717DFAA7100997DE4 /* GTCredential+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D79C0F617DFAA7100997DE4 /* GTCredential+Private.h */; }; 4DE864351794A37E00371A65 /* GTRepository+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DE864341794A37E00371A65 /* GTRepository+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 4DE864361794A37E00371A65 /* GTRepository+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DE864341794A37E00371A65 /* GTRepository+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 55C8054F13861FE7004DCB0F /* GTObjectDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = 55C8054D13861F34004DCB0F /* GTObjectDatabase.m */; }; @@ -387,6 +390,9 @@ 30FDC07E16835A8100654BF0 /* GTDiffLine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTDiffLine.m; sourceTree = ""; }; 32DBCF5E0370ADEE00C91783 /* ObjectiveGitFramework_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjectiveGitFramework_Prefix.pch; sourceTree = ""; }; 4D26799D178DAF31002A2795 /* GTTreeEntry+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTTreeEntry+Private.h"; sourceTree = ""; }; + 4D79C0EC17DF9F4D00997DE4 /* GTCredential.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTCredential.h; sourceTree = ""; }; + 4D79C0ED17DF9F4D00997DE4 /* GTCredential.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTCredential.m; sourceTree = ""; }; + 4D79C0F617DFAA7100997DE4 /* GTCredential+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTCredential+Private.h"; sourceTree = ""; }; 4DE864341794A37E00371A65 /* GTRepository+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTRepository+Private.h"; sourceTree = ""; }; 55C8054C13861F34004DCB0F /* GTObjectDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTObjectDatabase.h; sourceTree = ""; }; 55C8054D13861F34004DCB0F /* GTObjectDatabase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = GTObjectDatabase.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; @@ -777,6 +783,9 @@ 8821547C17147B3600D76B76 /* GTOID.m */, D09C2E341755F16200065E36 /* GTSubmodule.h */, D09C2E351755F16200065E36 /* GTSubmodule.m */, + 4D79C0EC17DF9F4D00997DE4 /* GTCredential.h */, + 4D79C0ED17DF9F4D00997DE4 /* GTCredential.m */, + 4D79C0F617DFAA7100997DE4 /* GTCredential+Private.h */, ); path = Classes; sourceTree = ""; @@ -953,6 +962,8 @@ D09C2E361755F16200065E36 /* GTSubmodule.h in Headers */, 4D26799F178DAF31002A2795 /* GTTreeEntry+Private.h in Headers */, 4DE864351794A37E00371A65 /* GTRepository+Private.h in Headers */, + 4D79C0EE17DF9F4D00997DE4 /* GTCredential.h in Headers */, + 4D79C0F717DFAA7100997DE4 /* GTCredential+Private.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1321,6 +1332,7 @@ 8821547F17147B3600D76B76 /* GTOID.m in Sources */, 5BE6128A1745EE3400266D8C /* GTTreeBuilder.m in Sources */, D09C2E381755F16200065E36 /* GTSubmodule.m in Sources */, + 4D79C0EF17DF9F4D00997DE4 /* GTCredential.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1404,6 +1416,10 @@ "External/ios-openssl/lib", "External/libssh2-ios/lib", ); + OTHER_CFLAGS = ( + "$(inherited)", + "-DGIT_SSH", + ); OTHER_LDFLAGS = ( "-lgit2-iOS", "-all_load", @@ -1429,6 +1445,10 @@ "External/ios-openssl/lib", "External/libssh2-ios/lib", ); + OTHER_CFLAGS = ( + "$(inherited)", + "-DGIT_SSH", + ); OTHER_LDFLAGS = ( "-lgit2-iOS", "-all_load", @@ -1447,10 +1467,6 @@ DYLIB_CURRENT_VERSION = 1; FRAMEWORK_VERSION = A; GCC_PREFIX_HEADER = ObjectiveGitFramework_Prefix.pch; - HEADER_SEARCH_PATHS = ( - "$(inherited)", - /usr/local/include, - ); INFOPLIST_FILE = Info.plist; OTHER_LDFLAGS = ( "-lgit2", @@ -1472,10 +1488,6 @@ DYLIB_CURRENT_VERSION = 1; FRAMEWORK_VERSION = A; GCC_PREFIX_HEADER = ObjectiveGitFramework_Prefix.pch; - HEADER_SEARCH_PATHS = ( - "$(inherited)", - /usr/local/include, - ); INFOPLIST_FILE = Info.plist; OTHER_LDFLAGS = ( "-lgit2", @@ -1496,13 +1508,20 @@ ARCHS = "$(ARCHS_STANDARD_64_BIT)"; GCC_STRICT_ALIASING = NO; GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = NO; - HEADER_SEARCH_PATHS = External/libgit2/include; + HEADER_SEARCH_PATHS = ( + External/libgit2/include, + /usr/local/include, + ); IPHONEOS_DEPLOYMENT_TARGET = 5.0; LIBRARY_SEARCH_PATHS = ( ., External, ); MACOSX_DEPLOYMENT_TARGET = 10.7; + OTHER_CFLAGS = ( + "$(inherited)", + "-DGIT_SSH", + ); TEST_AFTER_BUILD = NO; }; name = Debug; @@ -1514,13 +1533,20 @@ ARCHS = "$(ARCHS_STANDARD_64_BIT)"; GCC_STRICT_ALIASING = NO; GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = NO; - HEADER_SEARCH_PATHS = External/libgit2/include; + HEADER_SEARCH_PATHS = ( + External/libgit2/include, + /usr/local/include, + ); IPHONEOS_DEPLOYMENT_TARGET = 5.0; LIBRARY_SEARCH_PATHS = ( ., External, ); MACOSX_DEPLOYMENT_TARGET = 10.7; + OTHER_CFLAGS = ( + "$(inherited)", + "-DGIT_SSH", + ); TEST_AFTER_BUILD = NO; }; name = Release; @@ -1616,13 +1642,20 @@ ARCHS = "$(ARCHS_STANDARD_64_BIT)"; GCC_STRICT_ALIASING = NO; GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = NO; - HEADER_SEARCH_PATHS = External/libgit2/include; + HEADER_SEARCH_PATHS = ( + External/libgit2/include, + /usr/local/include, + ); IPHONEOS_DEPLOYMENT_TARGET = 5.0; LIBRARY_SEARCH_PATHS = ( ., External, ); MACOSX_DEPLOYMENT_TARGET = 10.7; + OTHER_CFLAGS = ( + "$(inherited)", + "-DGIT_SSH", + ); TEST_AFTER_BUILD = NO; }; name = Profile; @@ -1635,10 +1668,6 @@ DYLIB_CURRENT_VERSION = 1; FRAMEWORK_VERSION = A; GCC_PREFIX_HEADER = ObjectiveGitFramework_Prefix.pch; - HEADER_SEARCH_PATHS = ( - "$(inherited)", - /usr/local/include, - ); INFOPLIST_FILE = Info.plist; OTHER_LDFLAGS = ( "-lgit2", @@ -1667,6 +1696,10 @@ "External/ios-openssl/lib", "External/libssh2-ios/lib", ); + OTHER_CFLAGS = ( + "$(inherited)", + "-DGIT_SSH", + ); OTHER_LDFLAGS = ( "-lgit2-iOS", "-all_load",