Is it possible to download files from my app folder (Dropbox) to my app without linking an account? If so, how would I do that?
[Cross-linking for reference: https://stackoverflow.com/questions/34603685/how-to-download-files-to-app-from-app-folder-without-linking-an-account-via-drop ]
Based on the additional information you posted in your StackOverflow question, it sounds like you just want your app to connect to your own Dropbox account, as opposed to the accounts of your end-users.
The API was designed with the intention that each user would link their own Dropbox account, in order to interact with their own files. However, it is technically possible to connect to just one account. The SDKs don't offer explicit support for it and we don't recommend doing so, for various technical and security reasons.
However if you did want to go this route, instead of kicking off the authorization flow, you would use an existing access token for your account and app. (Just be careful not to revoke it, e.g. via https://www.dropbox.com/account/security .) Also, note that while embedding an access token isn't great, you definitely shouldn't embed your username and password.
For reference, here are some other similar questions on StackOverflow where I've posted answers:
https://stackoverflow.com/questions/15014001/allow-dropbox-api-to-access-my-account-on-users-devicehttps://stackoverflow.com/questions/27834922/auto-login-dropbox-account-on-core-api-without-login-prompt
Thank you so much! I looked through your previous answers and I think I got it. I'll try implementing it when I get home. I saw that you cited security concerns. I would of course only be downloading non-personal PDFs and Word documents. (Informational packets and forms)
There wouldn't be any problem getting my app approved to the App Store would there? Also, are there any alternate solutions that would allow me to check each file individually in a folder and only download it if it has been updated?
My goal is to check every file in the folder and decide whether or not download it without explicitly knowing what the file is. (So I can add and remove files from the folder if needed) That's why I was straying away from explicit share links.
No problem. To elaborate a bit on the security concerns, note that client-side applications can't keep secrets, meaning that any access token stored in a distributed app could be extracted directly, or sniffed in transit. That means that a malicious user could get the access token, and use it to access the Dropbox API directly, bypassing any access controls your app attempted to enforce. They could even replace the files with other data, or revoke the access token entirely, breaking the integration for all of your users.
Anyway, I can't speak to any concerns with getting your app on the App Store. You'd have to refer to Apple for that.
Using the API would certainly be a useful way to download files and watch for when they change. As an alternative, you could use the etag header value returned on shared links, but that's not officially documented.
Do you know of any way to encrypt the access key that would fit the way I'm using it?
There are any number of ways obfuscate or encrypt it, but it's impossible to actually protect a secret in a client-side application. (E.g., if you encrypt it, the encryption key eventually needs to be on the device too, and so can be similarly stolen, etc.) No matter what, an attacker could extract the secret from the app. Or, even if the access token is encrypted at rest, they could just perform a man in the middle attack and pull the access token out as it goes over the network when making actual API calls.
Okay, I'll figure that out. I know how to get my access token. How do I retrieve my access token secret or is it just the app secret?
Also where can I find my user id?
The access token secret is not the same thing as the app secret.
If you're using OAuth 1, the access token secret is the "oauth_token_secret" returned by /oauth/access_token.
If you're using OAuth 2, the access token is just one string, and doesn't have a separate "secret" portion.
You can get your user ID from the account information API call (/account/info on v1, or /users/get_current_account on v2).
Can you explain how to use /oauth/access_token and /account/info and /users/get_current_account? I also don't know how to tell if I'm using Oauth 1 or 2 and V1 or V2. I apologize as I am somewhat new to all that.
Are you using an SDK or library? If so, which?
I'm using the sdk following the tutorial on https://www.dropbox.com/developers-v1/core/start/ios which I now see has v1 in the URL but if you could explain that still, that would be great.
That SDK uses OAuth 1 and version 1 of the Dropbox API, a.k.a. the Core API. The DBRestClient.loadAccountInfo method corresponds to /account/info.
Okay so I'd put this somewhere:
DBRestClient.loadAccountInfo;
How would I view/retrieve the resulting info?
What is the corresponding function to get the access token secret? And is the access token you can generate from the account page the correct one to use with V1 or is it only for V2? (It doesn't give a token secret.)
I recommend working through the tutorial to see how to call methods in that SDK. For example:
[self.restClient loadMetadata:@/];
So, using loadAccountInfo would look something like:
[self.restClient loadAccountInfo];
And you'd need to implement these delegate methods to get the response:
- (void)restClient:(DBRestClient*)client loadedAccountInfo:(DBAccountInfo*)info;- (void)restClient:(DBRestClient*)client loadAccountInfoFailedWithError:(NSError*)error;
The App Console only lets you generate an OAuth 2 access token, so that wouldn't suit your needs, as you need an OAuth 1 access token. You can get that by implementing the app authorization flow per the tutorial just to use for your account, and then pulling the access token out using DBSession.credentialStoreForUserId. (Again though, I should emphasize that this is not a recommended way of using the API, which is why this isn't particularly easy. If you just need to distribute content to the users of your app, a CDN might be a better solution.)
I figured out how to get the user id. All I need is the access token with secret and I'll be set. I'm not sure where to implement DBSession.credentialStoreForUserID though. I'm doing this within the example DBRoulette Xcode project. Would I call it like this?
[[DBSession sharedSession] credentialStoreForUserID];
Where would I put that and how would I retrieve the value for the access token? The most I can find online is this in terms of getting a value and I don't know where I would put that either.
DBSession.sharedSession().credentialStoreForUserId(userId).accessToken
If you could use the DBRoulette project for reference, that would help a lot.
By the way, thank you for sticking with me. I understand that this is not intended use, but I still wish the documentation was more straightforward. I don't see anything about access tokens in the tutorial you linked. I did follow that earlier for setting up my actual app.
You just need to call credentialStoreForUserID once to get your access token/secret, which you can do anywhere in the app, as long as the account is already linked. That would look like:
MPOAuthCredentialConcreteStore *creds = [[DBSession sharedSession] credentialStoreForUserId:@12345]; NSLog(@access token key: %@ secret: %@", creds.accessToken, creds.accessTokenSecret);
(Where 12345 is your user ID.)
Okay, that did allow me to get an access token and secret. However, I still don't seem to be able to retrieve a file from my folder using this method. If I login normally to link my dropbox, it does work. (I basically pull a text file from my Dropbox and display the text.) I'm not sure what I'm doing wrong here. The one thing I did notice was that I get a new access token and secret every time I login normally. Do the tokens expire? Anyways, here is my code cutting out irrelevant portions and replacing sensitive info:
In my app delegate:
.h
#import <UIKit/UIKit.h>@interface AppDelegate : NSObject <UIApplicationDelegate>@property (strong, nonatomic) UIWindow *window;@end
.m
#import "AppDelegate.h"#import <DropboxSDK/DropboxSDK.h>@implementation AppDelegate- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{ DBSession *dbSession = [[DBSession alloc] initWithAppKey:@key appSecret:@secret root:kDBRootAppFolder]; // either kDBRootAppFolder or kDBRootDropbox [DBSession setSharedSession:dbSession]; //[[DBSession sharedSession] unlinkAll]; [dbSession updateAccessToken:@token accessTokenSecret:@token secret forUserId:@id number];}@end
In my view controller:
.h file
#import <UIKit/UIKit.h>@class DBRestClient;@interface homeVC : UIViewController{ DBRestClient *restClient;}@end
.m file
#import "homeVC.h"#import <DropboxSDK/DropboxSDK.h>@interface homeVC () <UITableViewDataSource, UIAccelerometerDelegate, DBRestClientDelegate>@property (weak, nonatomic) IBOutlet UITableView *tableView;@property (nonatomic, strong) DBRestClient *restClient;@end@implementation homeVC- (void)viewDidLoad{ [super viewDidLoad]; NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; NSString* path2 = [documentsDirectory stringByAppendingPathComponent: [NSString stringWithFormat:@%@", @Welcome.txt]]; [self.restClient loadMetadata:@/]; [self.restClient loadFile:@/Welcome.txt intoPath:path2]; NSString* content = [NSString stringWithContentsOfFile:path2 encoding:NSUTF8StringEncoding error:NULL]; NSLog(@%@",content);}
Tokens don't expire by themselves (but they can be manually revoked).
What doesn't work exactly? What error or output are you getting?
Basically, when I try to output the words in my text file, all I get back is null as so in my NSLog:
2016-01-08 22:38:45.124 My App Name[804:200098] (null)
Versus:
2016-01-08 22:42:30.358 DBRoulette[812:201530] Testing...
I know that the code I used works, because I tested it in DBRoulette logging in normally rather than using a token. I basically put the code inside the "Random photo" button's function and it worked fine. I got exactly what was in my text file to display in the NSLog as shown above.
Have you implemented both of the delegate methods?
- (void)restClient:(DBRestClient*)client loadedFile:(NSString*)destPath;
- (void)restClient:(DBRestClient*)client loadFileFailedWithError:(NSError*)error;
What do you get from those?
On a side note, I've noticed that the file seems to remain cached (in DBRoulette) as when the function is called before linking it still displays text but the old text. I've tested this by changing the text in my text file between builds. Is this supposed to happen? Will all my files automatically cache for the user too? This is good if so, but I just wanted to make sure.
Anyways, I just tried implementing both delegate methods and added NSLogs inside them. In DBRoulette, which I know is working I did see the message I put in the loadFile: function so I confirmed again that it works there.
However, in my actual app I got nothing at all (not counting the previous (null) NSLog for displaying the contents of the file). Not even the NSLog error message in loadFileFailedWithError:. I don't think it would fail to load without an error so maybe it isn't loading at all? I have to be missing something, but I'm not quite sure what. Again, it has to be something with how I'm linking my account manually, because when I login to link it works perfectly fine with all the rest of the code being the same but not hen I use a token. You have my code above, but the only difference other than linking I can point out is that in the working one I put the download code in a function but in my actual app I put it in in the viewDidLoad
The iOS Core SDK doesn't do any caching for you, and I'm not sure what functionality you're referring to in DBRoulette. The DBRoulette sample app displays images, not text, anyway, unless you're referring to a modified version of it?
Anyway, there are a few things that might cause your delegate methods to not be called:
1. Your rest client is nil or is being released (e.g., by ARC) prematurely. 2. You're making the call in a background thread that doesn't have a run loop. 3. Your delegate method that should be called back has a typo in it. Unfortunately the SDK doesn't warn you if it can't find a delegate method to call; it just completes without telling anyone.
Yes, I was using a modified version. Do you see anything missing in the code for my app a few responses above? If not, where else should I check?
Nothing jumps out at me, besides the lack of implementation of the delegate methods. Have you finished implementing those and looked into the causes I mentioned that may lead to them not being called?
Which delegate methods have I not implemented? I wasn't sure what was needed since I'm not going to have a normal login. Maybe that's what I'm missing.
For loadFile, you just need these two:
Earlier you did say you implemented them, but I wanted to check since that's the only thing that seemed to be missing from the latest code you've shared.
For loadMetadata you need:
- (void)restClient:(DBRestClient*)client loadedMetadata:(DBMetadata*)metadata;- (void)restClient:(DBRestClient*)client loadMetadataFailedWithError:(NSError*)error;
Two other things I just noticed:
- You don't seem to actually be constructing your client anywhere that you've shared. E.g.:
self.restClient = [[DBRestClient alloc] initWithSession:[DBSession sharedSession]]; self.restClient.delegate = self;
- You're printing out the contents of the local file immediately after kicking off the API calls, but note that these calls are asynchronous, so you may be reading that data before the API call finished to download the latest version. That may be the "caching" you were referring to before.
Okay, that worked! I added those initialization lines to the viewDidLoad, but it still didn't work. I then moved the code for downloading the file and displaying it in an NSLog into a separate function outside of viewDidLoad and called it. That was what finally allowed it to work. Thanks so much for all your help!