We are aware of the issue with the badge emails resending to everyone, we apologise for the inconvenience - learn more here.

Forum Discussion

dsoprea's avatar
dsoprea
Helpful | Level 6
11 months ago

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

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:

 

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?

  • 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?

  • 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's avatar
      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's avatar
        Greg-DB
        Icon for Dropbox Staff rankDropbox 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