Skip to content

Add a GTCred wrapper. #254

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 28 commits into from
Oct 1, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
c9d19c4
Add a GTCred wrapper.
tiennou Sep 10, 2013
4767309
Merge remote-tracking branch 'origin/master' into git-cred
tiennou Sep 15, 2013
7d58522
Rename `GTCred` => `GTCredential`.
tiennou Sep 15, 2013
e8c3dde
Add GTCredentialProvider class + documentation.
tiennou Sep 16, 2013
c472898
Merge branch 'master' into git-cred
tiennou Sep 16, 2013
cff6753
One line `userInfo` creation.
tiennou Sep 10, 2013
23b4253
Update as per `libgit2` documentation.
tiennou Sep 16, 2013
2823345
Small documentation fixes.
tiennou Sep 16, 2013
6cc9a66
Implement the 3rd method.
tiennou Sep 16, 2013
64c0c6d
Allow passing a GTCredentialProvider when cloning.
tiennou Sep 16, 2013
05c21a1
Merge branch 'master' into git-cred
tiennou Sep 16, 2013
4b5b35a
Use `NSURL` for files.
tiennou Sep 17, 2013
194543b
Remove unused property.
tiennou Sep 17, 2013
f3db7d0
`assign` => `copy`.
tiennou Sep 17, 2013
3ad8518
Tab-indent.
tiennou Sep 17, 2013
47e0426
Oops.
tiennou Sep 17, 2013
4b53195
Merge branch 'master' into git-cred
tiennou Sep 25, 2013
7617ee7
Remove 3rd authentication method.
tiennou Sep 25, 2013
b66ba1a
Merge branch 'master' into git-cred
tiennou Sep 25, 2013
f21c87a
Move `-DGIT_SSH` at the project level.
tiennou Sep 25, 2013
4832625
Provide the paths to the public and private keys in the error message.
tiennou Sep 25, 2013
cc2670f
No need for formatting.
tiennou Sep 25, 2013
29f132b
Style: explicit nil comparison.
tiennou Sep 25, 2013
3fb9a46
Also move that one to the project level
tiennou Sep 25, 2013
0f9b7c9
Use the typedef-ed block in that signature.
tiennou Sep 30, 2013
9d69126
Make the provider's block ivar `nonatomic, readonly`.
tiennou Sep 30, 2013
e29d347
Copy that.
tiennou Oct 1, 2013
32c00c2
Use a C string instead of NSString.
tiennou Oct 1, 2013
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions Classes/GTCredential+Private.h
Original file line number Diff line number Diff line change
@@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be done with a method on GTCredentialProvider now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh ? That's the C trampoline that calls GTCredentialProvider, so no, I can't do that. You're supposed to pass it to libgit2 APIs that take a git_cred_aquire_cb (as in the fetch example I've given above).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, sorry, you're right.

76 changes: 76 additions & 0 deletions Classes/GTCredential.h
Original file line number Diff line number Diff line change
@@ -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
108 changes: 108 additions & 0 deletions Classes/GTCredential.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
//
// GTCredential.m
// ObjectiveGitFramework
//
// Created by Etienne on 10/09/13.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//

#import <ObjectiveGit/NSError+Git.h>
#import "GTCredential.h"
#import "GTCredential+Private.h"
#import <libssh2.h>

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be an NSError, not an assertion (e.g., in case the path/URL is user-provided).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see two problems ;-). Maybe it's better to assert because it forces the developer to validate the path before blindly giving it to us (it's gonna get used while the operation is in progress, which means a network operation that is doomed to fail if you don't check before. The other problem is that our current NSError machinery isn't really adapted to non-libgit2 errors (yet ;-)).
Also, this assert merely checks that the user isn't passing anything else than a file:// URL in there (I'm asserting for nil just above, but we require a local path in there).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

About the "yet" part, would you prefer me to rename the category and make it our own GTError ? I don't plan on having any instance methods in there, just the current class methods less the git_ prefix. It's just that I find the category to be a bother, and git_errorForGitError: sounds a little too much git ;-).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine for now, I guess. Your points about validation are good.


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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird indentation in this function.


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;
}
3 changes: 3 additions & 0 deletions Classes/GTRepository.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions Classes/GTRepository.m
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;

Expand Down
Loading