Dropbox Sync API AccessToken - ios

When I used the core API I simply used the code
[dbsession updateAccessToken:#"..." accessTokenSecret:#"..." forUserId:#"..."];
to access my dropbox account from any copy of the app. But now I found out of this new Sync API that is easier and more flexible, but I didn't find any equivalent for the code displayed above. It now is:
DBAccountManager* accountMgr = [[DBAccountManager alloc] initWithAppKey:#"..." secret:#"..."];
[DBAccountManager setSharedManager:accountMgr];
??[DBAccountManager updateAccessToken:#"..." accessTokenSecret:#"..." forUserId:#"..."];??
How can I access my account? Where can I insert the AccessToken?

From your question, it seems that this method on DBAccountManager is the one for using your appKey and secret:
- (id)initWithAppKey:(NSString *)key secret:(NSString *)secret
From the documentation description, it says this method "...create[s] a new account manager with your app’s app key and secret. You can register your app or find your key at the apps page."
After you create an instance of DBAccountManager and set it to be the shared manager using [DBAccountManager setSharedManager:], you can login the specific user by calling this method:
[[DBAccountManager sharedManager] linkFromController:YOUR_ROOT_CONTROLLER];
Here's a description from the dropbox iOS tutorial:
"To start interacting with the Sync API, you'll need to create a DBAccountManager object. This object lets you link to a Dropbox user's account which is the first step to working with data on their behalf"
"...the linking process will switch to the Dropbox mobile app if it's installed. Once the user completes the authorization step, Dropbox will redirect them back to your app using the URL scheme you registered when setting up the SDK. Your app needs to handle those requests to complete the auth flow."
The final step as mentioned in the tutorial is to handle the redirect. Here's some code to do this:
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url sourceApplication:(NSString *)source annotation:(id)annotation {
DBAccount *account = [[DBAccountManager sharedManager] handleOpenURL:url];
if (account) {
NSLog(#"App linked successfully!");
return YES;
}
}
The user's account information can now be obtained through [DBAccountManager sharedManager].linkedAccount which is a DBAccount with properties like userId and accountInfo.
Here's a link to the docs for reference. Hope this helps!
Update
It seems I may have misunderstood your question. I am giving you instructions on how to use the Sync API and didn't quite clarify that there is actually no place for a user's accessToken in the API. This has been replaced with the web flow that I describe above.

You can achieve what you want by generating a callback url that dropbox uses in the sync API. First you need to set the dropbox.sync.nonce user setting to match whatever you pass in as the state parameter in the NSURL. Then set the oauth_token, oauth_token_secret, and uid params with what you used to pass into [DBAccountManager updateAccessToken:#"..." accessTokenSecret:#"..." forUserId:#"..."];. See below:
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:#"9b0aa24b0bd50ce3a1a904db9d309c50"
forKey:#"dropbox.sync.nonce"];
[userDefaults synchronize];
NSURL *url =
[NSURL URLWithString:#"db-APP_KEY://1/connect?
oauth_token=updateAccessToken&
oauth_token_secret=accessTokenSecret&
uid=forUserId&
state=9b0aa24b0bd50ce3a1a904db9d309c50"];
[[UIApplication sharedApplication] openURL:url];
Notice how the state parameter is the same as the value stored in the user defaults. Keep in mind this is undocumented and may change in a later API version.

Related

Evernote iOS SDK - How do I authenticate with a token?

I am using Evernote SDK for iOS and I am saving the authentication token when the user has authorized access.
Once the user installs my application on a different device, I want to use that token to reauthenticate automatically, but it looks like SDK doesn't support that. Is there a way to do that?
I had the same issue last week, and their SDK indeed doesn't support it out-of-the-box, but after some research I found a solution that works perfectly. This solution mimics a valid authentication flow.
A little background:
When the ENSession class initializes, it retrieves the credentials that are saved on the keychain (unless [[ENSession sharedSession] unauthenticate] was called earlier). The problem is that the keychain is empty when using a different device, so our goal is to add a valid ENCredentials instance to the ENCredentialStore.
Solution:
Add the following imports to your code: ENCredentials.h and ENCredentialStore.h. We will need them later.
Initialize the ENSession like you already do, using setSharedSessionConsumerKey:(NSString *)key consumerSecret:(NSString *)secret optionalHost:(NSString *)host.
In order to create a valid ENCredentials object, we need to provide the following objects:
NSString * host
NSString * edamUserId
NSString * noteStoreUrl
NSString * webApiUrlPrefix
NSString * authenticationToken
NSDate * expirationDate
The host is always www.evernote.com (as defined in ENSession under ENSessionBootstrapServerBaseURLStringUS).
edamUserId is the user id you received when you got the original token. Same for the expirationDate. If you are not sure how to get them then you should use [[ENSession sharedSession].userStore getUserWithSuccess:^(EDAMUser *user) once authenticated.
So the only objects that are actually missing are noteStoreUrl and webApiUrlPrefix. Their format is always:
noteStoreUrl: https://www.evernote.com/shard/edam_shard/notestore
webApiUrlPrefix: https://www.evernote.com/shard/edam_shard/
Luckily, your token already contains edam_shared (value of S=, see this):
#"S=s161:U=5ce3f20:E=1561182201b:C=24eb9d000f8:P=285:A=app:V=2:H=e8ebf56eac26aaacdef2f3caed0bc309"
If you extract s161 and put it in the URLs above it will work (I am sure you know how to extract that, but let me know if you're having problems).
Now we are ready to authenticate using the token. First, expose the necessary functions from ENSession using a category:
#interface ENSession(Authentication)
- (void)startup;
- (void)addCredentials:(ENCredentials *)credentials;
#end
And authenticate using the token:
ENCredentials *credentials = [[ENCredentials alloc] initWithHost:ENSessionBootstrapServerBaseURLStringUS edamUserId:userId noteStoreUrl:noteStoreUrl webApiUrlPrefix:webApiUrlPrefix authenticationToken:token expirationDate:expirationDate];
[[ENSession sharedSession] addCredentials:credentials];
[[ENSession sharedSession] startup];
The last line is important in order to refresh the ENSession and retrieve the new stored credentials.
Now you are authenticated and ready to query the SDK. Good luck.

How do I use an iOS app's bundle identifier to 'authorize' upload to Google Cloud Storage?

Our service is using Google App Engine as our backend, and we're now implementing an upload-function for images etc.
Using the answers from several different questions here on stack, I have made it working, but not completely as I want. We are not using the built-in OAuth etc, and for now we want the storage to be public, but not entirely public. We would like to limit it to users of our own app (I.E no authentication). In the Cloud-console we can create an API-key for iOS. When doing this, we copy the API-key to the app, and pass it along with every upload-request. This is currently working, when the bucket-permission is set to allUsers - WRITE
However, inside the API-key, we can supply our app's own Bundle Identifier, so that, supposedly, only requests from our app is allowed. (App Store ID/URL is also permitted, apparently).
Adding this bundle-id does nothing as long as the bucket has the permission allUsers - WRITE. If I change the bundle-id to not match the actual bundle-id, it still works. So which permission should it use for the bucket to make the bundle-id in the API-key apply? And what should be sent along in the upload-code on iOS (acl?)?.
If I remove the allUsers-permission, and use something else, I get this error when trying to upload:
{message:"There is a per-IP or per-Referer restriction configured
on your API key and the request does not match these
restrictions. Please use the Google Developers Console
to update your API key configuration if request from this
IP or referer should be allowed." data:[1] code:403}}
This is how I'm using it right now (though I have tried several different things, all picked up from different questions/answers):
GTLServiceStorage *serv = [[GTLServiceStorage alloc] init];
serv.additionalHTTPHeaders = [NSDictionary dictionaryWithObjectsAndKeys:
#"[my project id]", #"x-goog-project-id",
#"application/json-rpc", #"Content-Type",
#"application/json-rpc", #"Accept", nil];
serv.APIKey = #"[my iOS API key, gotten from console, (linked to bundle-id?)]";
serv.retryEnabled = YES;
GTLStorageBucket *bucket = [[GTLStorageBucket alloc] init];
bucket.name = #"[my bucket]";
GTLUploadParameters *params = [GTLUploadParameters uploadParametersWithFileHandle:fileHandle MIMEType:#"image/jpeg"];
GTLStorageObject *storageObject = [[GTLStorageObject alloc] init];
storageObject.name = #"testFile.jpg";
//I have no idea what I'm doing with the following stuff, but I've tried several things:
GTLStorageObjectAccessControl *objAccessControl
= [GTLStorageObjectAccessControl new];
//This is working
objAccessControl.entity = #"allUsers";
objAccessControl.email = #"[my app-id]#project.gserviceaccount.com";
objAccessControl.role = #"OWNER";
//If I try this instead, it is not working.
//objAccessControl.domain = #"[my app-id].apps.googleusercontent.com";
//objAccessControl.role = #"WRITER";
//Probably because it's bullshit, I have no idea what I'm doing.
storageObject.acl = #[objAccessControl];
[...] //Bucket and upload and stuff. It seems like it's the ACL-thing above that's not working..
It seems like I have to connect the permissions on the bucket to the iOS API Key somehow, but I don't know if it's even possible.
What I want: All users to be able to use the cloud, given that they are requesting it from my iOS app.
As this question never got an answer I'll add one here, based on the information currently in the post.
The reason you got the error 'There is a per-IP or per-Referer restriction..' when calling the GCS API with the iOS API Key is simply because the GCS API doesn't work with API Keys for private data, only Bearer Tokens (ie. using OAuth). There isn't anything you could have done to make the API Key work with the GCS API directly with private data. The reason it worked when you had 'allUsers - WRITE' set as the ACL is simply because that ACL allows public access.
To access the private data without user intervention requires a Service Account, however the Google APIs Objective-C Client only supports OAuth2 Client IDs. The rationale being that Service Accounts are intended for server-side authentication only. Using a Service Account in a client would involve distributing the private key along with the app, which could easily be compromised. For reference, here's a sample of how you might authorize the GCS service using OAuth:
NSString *keychainItemName = #"My App";
NSString *clientID = <your-client-id>;
NSString *clientSecret = <your-client-secret>;
// How to check for existing credentials in the keychain
GTMOAuth2Authentication *auth;
auth = [GTMOAuth2WindowController authForGoogleFromKeychainForName:kKeychainItemName
clientID:clientID
clientSecret:clientSecret];
...
// How to set up a window controller for sign-in
NSBundle *frameworkBundle = [NSBundle bundleForClass:[GTMOAuth2WindowController class]];
GTMOAuth2WindowController *windowController;
windowController = [GTMOAuth2WindowController controllerWithScope:kGTLAuthScopeStorageDevstorageFullControl
clientID:clientID
clientSecret:clientSecret
keychainItemName:kKeychainItemName
resourceBundle:frameworkBundle];
[windowController signInSheetModalForWindow:[self window]
completionHandler:^(GTMOAuth2Authentication *auth,
NSError *error) {
if (error == nil) {
self.storageService.authorizer = auth;
}
}];
...
// Initialize service with auth
GTLServiceStorage *serv = [[GTLServiceStorage alloc] init];
serv.authorizer = auth;
All of this was taken from the storage sample on the google-api-objectivec-client GitHub page, so you can refer to it for a complete example with context.
This still leaves the question of how to implement access to GCS from iOS without user authorization. The short answer to this is that the iOS Key can be used to restrict access to your own backend API hosted on Google Cloud Endpoints, and that backend application can authorize against GCS using a Service Account (usually the Application Default Service Account). The Cloud Storage Client Library Examples page has samples using the default credentials for different languages.
Further details on how to implement an Endpoints API for this purpose are probably getting outside of the scope of this question, but this should serve as a good starting point.

How can I authorize more than one Dropbox account in an app?

I'm looking to update one of my apps (which is a Dropbox client) to have support for multiple accounts, but I can't seem to find a way to do it.
I have analyzed the SDK many times and no matter how many times I look at, it looks like an account using the official SDK can only support one account at a time. Although I'm sure it can support more as I know of many apps that allow you to link more than one.
Any pointers on doing this will be highly appreciated. I can't even find a way to fetch tokens to store them separately later.
I found this to be a challenge but finally made it work after lots of experimentation. Here are some bits of information that should help:
Each Dropbox (DB) account has a userid (uid) associated with it once the user has been authorized. In your own app's model for an account, you need to keep track of the uid. Initially, before the user links their DB account, this uid will be nil.
When the user wants to access their DB account, you get your associated uid for the account. If the uid isn't nil you setup the DBRestClient as follows:
_client = [[DBRestClient alloc] initWithSession:[DBSession sharedSession] userId:uid];
If the uid isn't set yet, you need to present the login screen.
[[DBSession sharedSession] linkFromController:someController];
This, of course, launches the DB app to present the login (or presents a web interface if the DB app isn't installed). Either way, your app will be launched again by DB when the user finishes the authorization process.
In your app delegate's application:openURL:sourceApplication:annotation: method you do something like:
if ([[DBSession sharedSession] handleOpenURL:url]) {
NSString *query = url.query;
if ([[url absoluteString] rangeOfString:#"cancel"].location == NSNotFound) {
NSDictionary *urlData = [DBSession parseURLParams:query];
NSString *uid = [urlData objectForKey:#"uid"];
if ([[[DBSession sharedSession] userIds] containsObject:uid]) {
// At this point we know the login succeeded and we have the newly linked userid
// make a call to process the uid
}
} else {
// user cancelled the login
}
}
In the code that processes the newly linked uid, you can store the uid in your own account data model. Then you use the uid to create the DBRestClient like I showed earlier.
If you have a uid, you can determine if the uid is properly linked with a simple check:
if ([[[DBSession sharedSession] userIds] containsObject:uid]) {
// the uid is linked
}
To unlink a user based on their uid you can do:
[[DBSession sharedSession] unlinkUserId:uid];
At that point I would also clear out the saved uid from your own account model.
Hopefully that is enough pieces to build the puzzle. Good luck.

Facebook iOS SDK: authorize with permissions requires 2 attempts

I have an app that still uses the deprecated Facebook class to connect with Facebook. If I authorize with no extended permissions, everything works fine. But if I do include permissions, the first round trip to authorize always fails (even though it gets a valid token!). Am I missing a step?
Here's the code to initiate Facebook authorization for the app
- (IBAction) doConnect:(id)sender
{
NSArray* permissions = [NSArray arrayWithObjects:
#"email",#"publish_actions",nil];
[self.facebook authorize:permissions];
}
Here's the code that gets invoked after the user has granted permissions and control returns to my app. The url always includes a nice looking token, even the first time through.
// handle the incoming url from app switching
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
return [self.facebook handleOpenURL:url];
}
And here's the FBSessionDelegate method that gets invoked after a successful connect. Even though the url above contained a token, it's gone the first time we get here. But if I invoke the doConnect method above, the token will be present when we get here.
// FBSessionDelegate
- (void)fbDidLogin
{
if( [self.facebook accessToken] == nil )
{
NSLog(#"Had an access token above, but not now!");
// If I reinvade the doConnect: method again, it will work!!!
}
// ...
}
Looking deep in the sdk code in FBSession.m, it seems that the requested permissions haven't been associated with the new token first time through, causing the session to ignore the new token. First time through, cachedPermissions is always an empty list
// get the cached permissions, and do a subset check
NSArray *cachedPermissions = [tokenInfo objectForKey:FBTokenInformationPermissionsKey];
BOOL isSubset = [FBSession areRequiredPermissions:permissions
aSubsetOfPermissions:cachedPermissions];
You are asking for two types of permissions, a read type (email) and a write type (publish_actions).
You should be using the latest Facebook SDK, v3.1.1 and you'll have to split up read and writes separately - especially if you wish to support iOS6. You can only ask for reads initially. See the note in https://developers.facebook.com/docs/tutorial/iossdk/upgrading-from-3.0-to-3.1/ and the section on asking for read and writes separately.
For Facebook SDK 3.1, use [FBSession activeSession]'s
reauthorizeWithPublishPermissions: defaultAudience:completionHandler:
For Facebook SDK 3.2, use [FBSession activeSession]'s
requestNewPublishPermissions: defaultAudience:completionHandler:
we can authorise user in single request. It does not need two times attempt. To achieve this what we need do is "We need to ask for publish permission first". Call the below method
let fbLoginMngr = FBSDKLoginManager();
fbLoginMngr.logOut()
fbLoginMngr.logInWithPublishPermissions
it will ask first profile details then asks for requested publish permission. Once we get a call back we query to Graph api to extract the profile data like below.
let fbRequest = FBSDKGraphRequest(graphPath:"me", parameters:self.FB_REQ_PARAMS);
fbRequest.startWithCompletionHandler { (connection : FBSDKGraphRequestConnection!, result : AnyObject!, error : NSError!) -> Void in
if error == nil {
debugPrint(result)
} else {
handleError(error)
}
}

fbDidLogin never called (facebook-ios-sdk)

I'm using a pop up Facebook dialog for the user login and the publishing of a post on his/her stream
NSMutableDictionary* params = [NSMutableDictionary dictionaryWithObjectsAndKeys: apiKey, #"api_key", nil];
[facebook dialog:#"stream.publish" andParams:params andDelegate:self];
It works, but when the user logs in the fbDidLogin method is never called.
Why?
In this way I can't request the user name and the access token is always null.
EDIT
With the october facebook api I have in part resolved my problem.
I just had the same problem and solved it. You NEED to implement
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url
in your app delegate class, not your custom UIViewController or any other class. Then direct the call from your app delegate to whatever controller is responsible for your Facebook.
Did you register you app to handle your facebook application id URL? If not, there are instructions how to do it on the Facebook iOS SDK website. You also need to tell your app delegate to handle calls to this URL. To do this, add the following code to your app delegate implementation:
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
return [[controller facebook] handleOpenURL:url];
}
Where [controller facebook] is the instance of your Facebook class.
You'll need to set the sessionDelegate member variable of the facebook handle in front of calling any dialog methods.
self.facebook.sessionDelegate = self;
Also you'll need to implement the FBSessionDelegate callbacks in your object.
I did NOT check this for stream.publish, but this is what for
[facebook dialog:#"oauth" andParams:params andDelegate:self];
is neccessary.
The Mike Bretz method worked for me.
I have a class (a view Controller) that has a button that does the following :
NSMutableDictionary * params = [NSMutableDictionary dictionary];
[params setObject:FACEBOOK_APP_ID forKey:#"client_id"];
[[SHARED_APP facebook] setSessionDelegate:self];
[[SHARED_APP facebook] dialog:#"oauth" andParams:params andDelegate:self];
where my controller also conforms to the protocol FBSessionDelegateand SHARED_APP is my [[UIApplication sharedApplication] delegate]
I implemented the method
-(void)fbDidLogin {
NSLog(#"fbDidLogin");
}
and it worked !
I can then make subsequent calls to methods like
[[SHARED_APP facebook] requestWithGraphPath:#"me/friends" andDelegate:self];
to retrieve JSON data (with FBRequestDelegate)
Be careful, for every request, to test the return value such as explained here
http://developers.facebook.com/blog/post/500 to keep looking that you're still authenticated
Hope it helps
I just had this problem and believe I may have found your solution. When you create the app on facebook you must edit your app settings to allow for native ios app. The ios bundle id selected must match EXACTLY to your bundle ID in the app you are creating. Facebook states that they check this prior to authorizing your app. Funny thing is if you remove the facebook app from the device and then try to authenticate it uses the web browser and seemingly bipasses this "security" completely. Facebook doesn't send an error message back so it was a lot of fun to track down. I figured I'd throw this answer out there in hopes to save some future devs some trouble. I hope this helps.
In my case fbDidLogin was not called because of a mistake in the info.plist.
URL Schemes should be added to the URL Types. And here, fbMyAppId string is added as an item 0. (mistakenly I added URL Identifiers instead of URL Schemes)
You only have to implement that method within your App Delegate as this...
- (void)fbDidLogin {
NSLog(#"DID LOGIN!");
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:[[self facebook] accessToken] forKey:#"FBAccessTokenKey"];
[defaults setObject:[[self facebook] expirationDate] forKey:#"FBExpirationDateKey"];
[defaults synchronize];
}
The mistake your are making is that you are implementing this method in your Custom View.
EDIT:
The above code works perfect, but if you want to get the control on your custom viewcontroller you can maintain that code on your appDelegate and add this code on your CustomViewController:
delegate= (YourAppDelegate *)[[UIApplication sharedApplication] delegate];
[delegate facebook].sessionDelegate=self;
So, you will able to override fbDidLogin within your CustomViewController, don't forget add the FBSessionDelegate on your CustomViewController.h (Header).
Kind Regards.
When you follow steps from the sample app, into your custom app, make the controller class as singleton, and then it works!!!
Most of the developers alloc and init 2 instances, because of which the delegate fbDidlogin is never called.
Hope this helps.
fbDidLogin is being called by Facebook object after login successful,So please remember to assign seesionDeletegate:
facebook = [[Facebook alloc] initWithAppId:kFacebookApiKey];
facebook.sessionDelegate = self;
map sure it is in the application delegate class i.e.: MYAPPAppDelegate.m
meaning the application:(UIApplication *) app openURL: methods

Resources