cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
Announcements
What’s new: end-to-end encryption, Replay and Dash updates. Find out more about these updates, new features and more here.

Dropbox API Support & Feedback

Find help with the Dropbox API from other developers.

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Folders that are visible from the SDK don't appear to be visible via the CLI

Folders that are visible from the SDK don't appear to be visible via the CLI

dsoprea
Helpful | Level 6

We have a "Full Dropbox" app installed on a team account. We're setting DROPBOX_MANAGE_APP_KEY and DROPBOX_MANAGE_APP_SECRET. I've also tried with DROPBOX_PERSONAL_APP_KEY and DROPBOX_PERSONAL_APP_SECRET, and DROPBOX_TEAM_APP_KEY and DROPBOX_TEAM_APP_SECRET. For some reason, dbxcli is using my personal home as the root folder, and can't see the team-level folders. It works fine when using the SDK from our webhooks, though, using the exact same credentials. 

 

This is essentially the loop in some webhook code that we have that responds to events in our team account:

 

 

# Get non-team scoped resource
dbx = dropbox.dropbox_client.Dropbox(
        app_key=_DROPBOX_KEY,
        app_secret=_DROPBOX_SECRET,
        oauth2_refresh_token=_DROPBOX_REFRESH_TOKEN)

cursor_id = None
has_more = True
while has_more is True:

    if cursor_id is None:
        # This is either the first time we've ever enumerated the
        # folder, or we've cleared our cursor

        # Enumerate the root of our scoped path. This is a required
        # parameter and the root must be represented as an empty string.
        #
        # This call is nonrecursive by default, which is fine by us
        result = \
            dbx.files_list_folder(
                path='/ManagedIntake',
                recursive=True)

    else:

        #
        # IMPORTANT
        #
        # Multi-file operations will usually induce separate webhook
        # notifications, but the earlier requests will often have
        # already read the full cursor before the later requests.
        # Therefore, some of the requests will show nothing to do.

        result = dbx.files_list_folder_continue(cursor_id)


    # Update cursor
    cursor_id = result.cursor

    for entry in result.entries:
        yield entry, cursor_id


    # Repeat only if there's m ore to do
    has_more = result.has_more

 

 

 

However, when we try to do this from the CLI, it won't let us access that root folder:

 

$ ./dbxcli ls -l
Revision Size Last modified Path
-        -    -             /dustin@<hidden>’s files 
-        -    -             /PRODUCT IMAGES                       

$ ./dbxcli ls -l /ManagedIntake
Error: path/not_found/

 

 

I also tried passing `--as-member <member ID>`, but got the same result.

 

For reference, this is my UI view:

dsoprea_1-1703079239385.png

 

I must be missing an obvious and/or fundamental truth. Any thoughts?

 

This seems like a root-path issue. Typically, when using the API, we can go and get whichever root-path is relevant to our context using the API and set it via the `Dropbox-API-Path-Root` header for those calls. It's still not clear why our SDK integration doesn't have the particular issue that we're trying to deal with, but the CLI doesn't even seem to have the ability to expose path-IDs nor to set that header. How are we supposed to manage this via the CLI?

1 Accepted Solution

Accepted Solutions

dsoprea
Helpful | Level 6

Okay. Yes. That's what I was missing. I saw enough entries that has non-None team-member IDs that I missed that the one I was looking for had a None one.

 

Yes. That's fine. I'm not dealing with pagination given that this is a PoC that didn't require it.

 

Since this is a team-level folder, I've just enumerated the members and grabbed the first one:

# Get team-scoped resource
dbxt = dropbox.dropbox_client.DropboxTeam(
        app_key=_DROPBOX_KEY,
        app_secret=_DROPBOX_SECRET,
        oauth2_refresh_token=_DROPBOX_REFRESH_TOKEN)

# Get members

result = dbxt.team_members_list()

# Grab the first member returned

first_member_id = None
for member in result.members:
    first_member_id = member.profile.team_member_id
    break

assert \
    first_member_id is not None, \
    "No members found."

# Get the namespace whose root we want to use

r = dbxt.team_namespaces_list()
namespace_id = None
for namespace in r.namespaces:
    if namespace.name != 'ManagedIntake':
        continue

    namespace_id = namespace.namespace_id

assert \
    namespace_id is not None, \
    "Could not find images namespace."

# Get object scoped to the member and root-path above

dbxtm = dbxt.as_user(first_member_id)

path = dropbox.common.PathRoot.namespace_id(namespace_id)
dbxtmp = dbxtm.with_path_root(path)

result = dbxtmp.files_list_folder("")
for entry in result.entries:
    print(entry.name)

 

So, that works, and works as desired.

 

How would the account-level access work? Since all of our apps would have to be team-scoped, are you just saying that removing the team-scopes will magically allow the app to have account -specific access? I had originally created a separate app for this and hadn't given it any team-scopes, but I was unable to even see this folder, presumably because it lives in the team space. Is that accurate?

View solution in original post

6 Replies 6

Greg-DB
Dropbox Staff

By default, API calls to the Dropbox API operate in the "member folder" of the connected account, not the "team space". That means that by default, the contents of the team space will not be found. From the screenshot you shared, the "ManagedIntake" folder is in the team space.

 

API calls can be configured to operate in the "team space" instead though, in order to interact with files/folders in the team space, by using the "Dropbox-API-Path-Root" header as you mentioned. The official Dropbox SDKs, such as the official Dropbox Python SDK you're using in your code, do offer the ability to set that header if/when needed.

 

It looks like the dbxcli tool in particular though does not implement that header, and so cannot access the team space. It would default to operating inside the member folder, where the '/ManagedIntake' path doesn't refer to anything, accordingly resulting in the 'path/not_found' error.

dsoprea
Helpful | Level 6

Okay, so rather than getting sucked into testing a PR that already professed to add that support or creating a PR of my own (because that PR had issues), I just reverted to using Python to have more control. I use a Team object to enumerate the namespaces, filter for the right one, and grab the namespace ID, and then set the path on the non-Team object:

# Get non-team scoped resource
dbx = dropbox.dropbox_client.Dropbox(
        app_key=_DROPBOX_KEY,
        app_secret=_DROPBOX_SECRET,
        oauth2_refresh_token=_DROPBOX_REFRESH_TOKEN)

# Get team-scoped resource
dbxt = dropbox.dropbox_client.DropboxTeam(
        app_key=_DROPBOX_KEY,
        app_secret=_DROPBOX_SECRET,
        oauth2_refresh_token=_DROPBOX_REFRESH_TOKEN)

r = dbxt.team_namespaces_list()
found = None
for namespace in r.namespaces:
    if namespace.name != 'ManagedIntake':
        continue

    found = namespace
    break

assert \
    found is not None, \
    "Could not find images namespace."

path = dropbox.common.PathRoot.namespace_id(found.namespace_id)
dbxtmp = dbx.with_path_root(path)

for entry in dbx.files_list_folder(""):
    print(entry)

 

However, I'm getting the [normal] "using team keys to access a single account" error:

  File "/home/dustin/.pyenv/versions/workflow_application/lib/python3.8/site-packages/dropbox/base.py", line 2145, in files_list_folder
    r = self.request(
  File "/home/dustin/.pyenv/versions/workflow_application/lib/python3.8/site-packages/dropbox/dropbox_client.py", line 326, in request
    res = self.request_json_string_with_retry(host,
  File "/home/dustin/.pyenv/versions/workflow_application/lib/python3.8/site-packages/dropbox/dropbox_client.py", line 476, in request_json_string_with_retry
    return self.request_json_string(host,
  File "/home/dustin/.pyenv/versions/workflow_application/lib/python3.8/site-packages/dropbox/dropbox_client.py", line 596, in request_json_string
    self.raise_dropbox_error_for_resp(r)
  File "/home/dustin/.pyenv/versions/workflow_application/lib/python3.8/site-packages/dropbox/dropbox_client.py", line 632, in raise_dropbox_error_for_resp
    raise BadInputError(request_id, res.text)
dropbox.exceptions.BadInputError: BadInputError('7dbb18c6c1fb4a09bb58483a877d182c', 'Error in call to API function "files/list_folder": This API function operates on a single Dropbox account, but the OAuth 2 access token you provided is for an entire Dropbox Business team.  Since your API app key has team member file access permissions, you can operate on a team member\'s Dropbox by providing the "Dropbox-API-Select-User" HTTP header or "select_user" URL parameter to specify the exact user <https://www.dropbox.com/developers/documentation/http/teams>.')

 

So, I switched to calling `as_user()` with the associated member ID on the Team object, setting the root on that, and then enumerating that, but I'm still getting the same error, which doesn't make sense to me:

# Get team-scoped resource
dbxt = dropbox.dropbox_client.DropboxTeam(
        app_key=_DROPBOX_KEY,
        app_secret=_DROPBOX_SECRET,
        oauth2_refresh_token=_DROPBOX_REFRESH_TOKEN)

r = dbxt.team_namespaces_list()
found = None
for namespace in r.namespaces:
    if namespace.name != 'ManagedIntake':
        continue

    found = namespace
    break

assert \
    found is not None, \
    "Could not find images namespace."

dbxtm = dbxt.as_user(found.team_member_id)

path = dropbox.common.PathRoot.namespace_id(found.namespace_id)
dbxtmp = dbxtm.with_path_root(path)

for entry in dbxtmp.files_list_folder(""):
    print(entry)

 

Suggestions on what I could be missing?

Greg-DB
Dropbox Staff

The NamespaceMetadata.team_member_id is only returned for team member folders or app folders, not shared/team folders, so it wouldn't be returned for your "ManagedIntake". That means that you're not actually setting a value in as_user, resulting in that error. If you have a team-linked client like this, you'd need to get a relevant team member ID from elsewhere.

 

Also, note that if you use team_namespaces_list, you should implement team_namespaces_list_continue as well.

 

Alternatively, if you just want to connect to a particular account and access the files/folders via that account, you can disable any team scopes and get a new access token/refresh token without them. The access token/refresh token without the team scopes will be specific to the particular account (Business or not) and so will not require the additional header. You can find more information on scopes in the OAuth Guide. If you don't need to call any team endpoints (e.g., if you just need to call individual endpoints, such as via files_list_folder/files_list_folder_continue), I recommend this solution instead for simplicity and security.

 

Here's a simplified version of the code that should work to list a folder named "ManagedIntake" in the team space, when using a refresh token for a specific account, without any team scopes granted:

dbx = dropbox.Dropbox(
        app_key=_DROPBOX_KEY,
        app_secret=_DROPBOX_SECRET,
        oauth2_refresh_token=_DROPBOX_REFRESH_TOKEN)

root_info = dbx.users_get_current_account().root_info

path = dropbox.common.PathRoot.root(root_info.root_namespace_id)
dbx = dbx.with_path_root(path)

for entry in dbx.files_list_folder("/ManagedIntake").entries:
    print(entry)
# be sure to implement files_list_folder_continue as well

 

dsoprea
Helpful | Level 6

Okay. Yes. That's what I was missing. I saw enough entries that has non-None team-member IDs that I missed that the one I was looking for had a None one.

 

Yes. That's fine. I'm not dealing with pagination given that this is a PoC that didn't require it.

 

Since this is a team-level folder, I've just enumerated the members and grabbed the first one:

# Get team-scoped resource
dbxt = dropbox.dropbox_client.DropboxTeam(
        app_key=_DROPBOX_KEY,
        app_secret=_DROPBOX_SECRET,
        oauth2_refresh_token=_DROPBOX_REFRESH_TOKEN)

# Get members

result = dbxt.team_members_list()

# Grab the first member returned

first_member_id = None
for member in result.members:
    first_member_id = member.profile.team_member_id
    break

assert \
    first_member_id is not None, \
    "No members found."

# Get the namespace whose root we want to use

r = dbxt.team_namespaces_list()
namespace_id = None
for namespace in r.namespaces:
    if namespace.name != 'ManagedIntake':
        continue

    namespace_id = namespace.namespace_id

assert \
    namespace_id is not None, \
    "Could not find images namespace."

# Get object scoped to the member and root-path above

dbxtm = dbxt.as_user(first_member_id)

path = dropbox.common.PathRoot.namespace_id(namespace_id)
dbxtmp = dbxtm.with_path_root(path)

result = dbxtmp.files_list_folder("")
for entry in result.entries:
    print(entry.name)

 

So, that works, and works as desired.

 

How would the account-level access work? Since all of our apps would have to be team-scoped, are you just saying that removing the team-scopes will magically allow the app to have account -specific access? I had originally created a separate app for this and hadn't given it any team-scopes, but I was unable to even see this folder, presumably because it lives in the team space. Is that accurate?

Greg-DB
Dropbox Staff

Yes, when an app doesn't use team scopes, it gets connected to a specific account, not the entire team. When an app uses team scopes, it gets connected to the entire team, not just a specific account.

 

So, when an app doesn't use team scopes and is connected to a specific account, there is only one relevant account and so you don't need to specify a member ID using as_user.

 

Either way, in order to access the team space, you do need to use with_path_root. You can use that whether or not that app uses team scopes, as long as the app is registered for "full Dropbox" access (and not "app folder" access).

dsoprea
Helpful | Level 6

Thanks for your help, @Greg-DB. Hopefully one day soon we'll reach the minimum density and distribution for all of these questions via previously-posted messages in the forum so that you (and whomever comes after you) will not necessarily having to be on-base constantly. These all seem like basic questions, but I'll open up every link and still seem to come-up short.

Need more support?
Who's talking

Top contributors to this post

  • User avatar
    dsoprea Helpful | Level 6
  • User avatar
    Greg-DB Dropbox Staff
What do Dropbox user levels mean?