Unattended uploading to Box via API

So, you’ve got yourself a fancy box.com account, woohoo, cloud storage, lovely. What else? You’ve got yourself some unix servers that, guess what, have files or data that changes from time to time and perhaps you want that data in a folder in Box.

Want to know what make getting that data from your servers to Box really easy? If Box had something like a URL-based non-API non-authenticated upload target you could use to send files to a specific folder with no further access provided. Oh wait, they do have such a thing, it’s called File Request. It requires all of two clicks to use; you click Create Link, and then you click Copy to copy the new link to wherever you need to use it:

Oh wait, no, this super easy simple solution to such a problem doesn’t work for unattended automated use on a server because Box decided the fucking thing needs some dumb javascript bullshit to work. So you can’t simply do the following:

curl -d @/path/to/data.json https://app.box.com/f/asdfCode language: JavaScript (javascript)

They instead tell you you need to use their API to upload in such a situation. Well that’s lovely, because now you need something with API credentials living on a server you perhaps didn’t want them on, along with of course a dramatically more complex implementation just to move an f’ing file from point A to point B each day.

So how do we accomplish this while also minimizing the security risk Box forces on you? We can do it with a Box Limited Access App:

https://developer.box.com/guides/applications/app-types/limited-access-apps/

A Limited Access App is an ‘application’, but not really because Box is stupid and calls credentials applications, so it’s really nothing more than a service account with API token credentials that an enterprise admin must get involved in approving. The Limited Access App is restricted to five API calls (create folder, upload file, download file, delete file, get embed link): https://developer.box.com/guides/authentication/app-token/endpoints/

The benefit to this type of authentication is that the new service account you end up with can then be invited only to the folders you want it to be able to send files to, possibly with just uploader permission if you don’t want it to later be able to delete the files or download them (such as if the server it’s being used from were compromised).

The downside to the Limited Access App type of account is that it can NOT replace an existing file of the same name unless it first deletes it, and has delete permission. It also can only delete by knowing the Box file ID of the previously uploaded file. It also has no way to determine such a file ID unless the ID had been recorded from the prior upload and provided to the program doing the new upload. This limitation made things far more complex for me because I have a server I wanted to send the same file to the same Box folder each day, replacing the prior day’s, as it’s only data used for reference on an as-needed basis and needs to be from the prior day.

So, now instead of just one f’ing curl command, I need a Limited Access App, credentials, admin approval, add a user to a folder, give the user more permissions than I would have liked (to be able to delete), and now my upload application must be stateful so it can track the uploaded file ID from the prior day to be able to delete the file the next day before replacing it. Thanks Box! Oh, and yes I interacted with support about this; I got the standard response of hey go post your dumb idea on our Uservoice forum where if you can round up ten thousand people to upvote your idea maybe we’ll give a shit, regardless of what you pay us each month.

So, let’s get going on this process.

Start by going into the Box Developer Console and creating a Limited Access App and giving it a name your enterprise admin will like:

From the page that follows, you will need to collect the “Client ID” field and contact your Box enterprise admin. They will need to go into the Box admin console, to Apps, Custom Apps Manager, and then click the “Add App” button:

At this point they’ll need that Client ID string, they’ll see your app name and scopes, and can then click Authorize.

Now, back to your Box Dev console. Navigate back into your app, and first stop at the General Settings tab. You need the Service Account Info email address, which will take the format AutomationUser_####_####@boxdevedition.com:

Next, click to the Configuration tab, and then click “Generate Key” for Primary Access Token. It will give you a string to use for authentication by your own application. You’ll need that, plus your client ID, for your program to be able to authenticate and upload.

Finally, navigate in Box web to the folder where you want these uploads to end up. Click into it, and grab its ID from the URL. On that same folder, “Share” it to the new email address of your service account and set the specific privileges needed, such as Uploader or Editor. This is the part that sucks, because if you need the file to have the same name and be replaced each day, now this user, whose credentials live in your source server, must have Editor permissions on the folder in question. In theory the data won’t be accessible without knowing a file ID, but it is what it is. If you only need to upload new files whose names will never conflict, then Uploader permission is fine:

This article is going to tell you how to do your uploads in python since that seemed like the easiest option for me to get out of this nightmare. You’ll need python3 installed and then hopefully you can run the following:

pip install boxsdk
pip install "boxsdk[jwt]"Code language: JavaScript (javascript)

I’m not going to get too far into the weeds with error handling, etc. but here’s a rough idea of what you need to make this work in python after those box libs are installed. It is making the assumption that we need to track and replace the uploaded file each day. It also supports sending the same file to multiple places in case different people need access to the file in question. Here’s how it works:

  1. Read a file called tracking.txt which has lines of the following format:
    # comment lines
    <Numeric Box folder ID>,<Box File ID>
  2. After reading in, iterate through the lines, skipping comments, and then:
  3. Issue a Box delete command against the file ID on the line in question
  4. Upload the new file to the folder ID in question
  5. Store the returned Box file ID in place of the one that had just been deleted
  6. Upon completion, write tracking.txt back out

You can of course chop this code up to get rid of all the tracking and deletion stuff if all you were trying to do was upload a uniquely-named non-existing file. The client_secret field is not used in the Limited Access App type of authentication, the client ID and token come from the areas of Box I described earlier:

#!/usr/bin/python3

# load box SDK
from boxsdk import OAuth2, Client
from boxsdk.exception import BoxAPIException

# authenticate to Box as Limited Access App
auth = OAuth2(
    client_id='myBoxAppClientID',
    client_secret='',
    access_token='superSecretToken'
)
client = Client(auth)

upload_file = '/mnt/exports/daily.json'
box_tracking = '/home/boxupload/tracking.txt'

# read the box tracking file, it takes the format:
# <folder ID>,<file ID>
# and also supports comment lines which will be preserved

with open(box_tracking, 'r') as file:
    lines = file.readlines()

new_lines = []
for line in lines:
    if line.startswith('#'):
        new_lines.append(line)
    else:
        uploadFolder,yesterdayFile = line.strip().split(',')
        # Delete yesterday file
        try:
            file_to_delete = client.file(file_id=yesterdayFile).get()
            file_to_delete.delete()
        except BoxAPIException as e:
            print(f"Error deleting file: {e}")

        # Upload replacement file
        folder = client.folder(uploadFolder)
        uploadedFile = folder.upload(upload_file)

        # store returned file ID for the uploaded file
        new_file_id = uploadedFile.id
        new_lines.append(f"{uploadFolder},{new_file_id}\n")

# upon completion, write the new upload id's back to the tracking file
with open(box_tracking, 'w') as file:
    file.writelines(new_lines)
Code language: PHP (php)

Just want super simple upload to folder ID 22222? This is the same as what’s on Box’s site, with the auth stuff added since they don’t make that clear whatsoever:

#!/usr/bin/python3

# load box SDK
from boxsdk import OAuth2, Client

# authenticate to Box as Limited Access App
auth = OAuth2(
    client_id='myBoxAppClientID',
    client_secret='',
    access_token='superSecretToken'
)
client = Client(auth)

folder_id = '22222'
new_file = client.folder(folder_id).upload('/home/me/document.pdf')
print(f'File "{new_file.name}" uploaded to Box with file ID {new_file.id}')
Code language: PHP (php)

Leave a Reply

Your email address will not be published. Required fields are marked *