Custom app with JWT authentication, cannot create file in folder
Hello,
I created a custom app with JWT authentication.
I first created a POC, writing a Python Library to create a Client based upon CLIENT_ID / CLIENT_SECRET / DEVELOPER_TOKEN.
I asked IT to get Service Account ID. I created a folder, and gave to this Service account ID co owner rights to the folder.
I could validate the upload of a file in the folder.
Now I'm trying to follow this documentation to use JWT auhtentication with Box Python SDK https://developer.box.com/guides/authentication/jwt/with-sdk/.
It appears that client creation with config.json file is OK (no error) but as soon as I try to upload file in folder I have the following error
{'errors': [{'reason': 'invalid_parameter', 'name': 'item', 'message': "Invalid value 'd_XXXXXXXXXXXX'. 'item' with value 'd_XXXXXXXXXXXX' not found"}]}
This folder is the same as first POC, so it exists, and the Service account ID has co owner rights. So why do I get this error? If anyone can help on this topic...
-
Hi Julien,
I've tried to replicate your use case and I was able to upload a file. I think the source of the issue might be how the service account was added to the folder as co-owner.
If I understood correctly there are 2 service accounts. A folder owned by service account A is shared with service account B and service account B uploads a file.
In my test this worked:
Service account A in my case is name JWT, has a folder named "JWT folder for UI Sample apps"
I used the sharing link feature:
and this python script works fine:
from boxsdk import JWTAuth, Client
from boxsdk.object.file import File
class CFG:
"""config class"""
JWT_CONFIG_FILE = ".jwt.config.json"
AS_USER = "18622116055"
PARENT_FOLDER_ID = "0" # folder id 0 is root folder
def get_box_client(as_user: bool = False):
"""get a box client"""
auth = JWTAuth.from_settings_file(CFG.JWT_CONFIG_FILE)
service_client = Client(auth)
if not as_user:
return service_client
user = service_client.user(CFG.AS_USER)
return service_client.as_user(user)
def print_items(items):
"""print items"""
print("\n")
print("Type\tID\tName")
print("----\t--\t----")
for item in list(items):
print(f"{item.type}\t{item.id}\t{item.name}\t")
def main():
"""main function"""
client = get_box_client(as_user=False)
# print current user info
user = client.user().get()
print(f"Current User: {user.name}\tid:{user.id}\temail:{user.login}")
# list files in parent folder
items = client.folder(CFG.PARENT_FOLDER_ID).get_items()
print_items(items)
# folder = 198775845609
client.folder("198775845609").upload("test_upload.txt", "test_upload.txt")
if __name__ == "__main__":
main()
print("\n")
print("-" * 80)
print("All Done!")However, when trying to replicate your use case, I also tried to invite the UI Elements service account as a co-owner collaborator to another folder, and the invite is pending acceptance from that service account, which in my case has no valid email.
Could this be the source of your issue?
-
Hello Rui and thanks for this very clear answer.
First to answer your question, yes I think you understood the issue
"If I understood correctly there are 2 service accounts. A folder owned by service account A is shared with service account B and service account B uploads a file." ==> if you consider my personal account is a "service account" which I'm not sure regarding Box terminology
I finally found a solution on my side :
- with Python API, create a new folder with Client authenticated with app config file (using create_subfolder)
- share this with folder with my personal account so that I can have access to that folder (.add_collaborator("mail@mail.com", CollaborationRole.CO_OWNER)
Since, I can now use this folder to CRUD file without any problem with authentication with config file.
But I still can't use the folder I created with my personal account, which I then shared with app Service Account.
The hypothesis you raised regarding the "Pending" sharing is interesting, it might be root cause of issue, as it seems to be some access right management issue. But you have to know I don't have this view when checking information regarding the sharing.
If it helps, here are the folder I created
The folder with which I have an issue (the one created with my account then shared)
https://harmonicinc.app.box.com/folder/196886012101
The folder which works fine (created with app service account then shared with my personal account)
https://harmonicinc.app.box.com/folder/198717991676
Strangely, the folder has been created with "Box Admin Service Account" while I was expecting it to be created with the app service account (Box_XOS_manufacturing). Since I'm using the config.json file from the app, I would have expected to always have a client which is using "Box_XOS_manufacturing" for any command.
But it looks like that once client is created with same config.json file :
- we send upload commands with "Box_XOS_manufacturing" account
- we send create_subfolder commands with "Box Admin Service Account"
-
Hi Julien,
That is odd indeed...
So looking at your screenshots I can see both folders are shared with different service users, which is puzzling, and leads me to think there are 2 service users.
Perhaps the person acting as the box administrator in your company did something manually or has some policies in place.
Once an app is using JWT authentication the service user should be consistent, these apps, depending on configuration, can impersonate another user but not another service app.
To be honest, what you're describing is somewhat unexpected, but there are so many variables in play, that I lost track. I'm glad you found a workaround.
However here are some of the more generic rules and my configurations, perhaps these can help you identify your situation.
You can check these under the developer console for your specific app (https://app.box.com/developers/).
So in my example I have an app configure to authenticate using JWT (server side):
Then on the app access level it is configured as app+enterprise access, giving it access to the entire enterprise:
On application scopes I have everything selected, and on the advanced features I have:
This will allow this service user to impersonate any other user in the enterprise.
There is an interesting detail here. If you set a developer token that usually only lasts for 60 minutes, that token is associated with your user rather than the service user.
Every time you change something here you need to ask you box administrator to reauthorize the app, you can check its status or submit via the authorization tab:
Another test you can do is check which user represents the service account associated with the JWT, for my previous example:
client = get_box_client(as_user=False)
# print current user info
user = client.user().get()
print(f"Current User: {user.name}\tid:{user.id}\temail:{user.login}")
# list files in parent folder
items = client.folder(0).get_items()
print_items(items)outputs, the service user details and the items of the root folder:
Current User:
UI-Elements-Sample
id:20344589936
email:AutomationUser_1841316_RbcnIM9B2l@boxdevedition.com
Type ID Name
---- -- ----
folder 177388203339 100k
folder 172599089223 Bookings
folder 163422716106 Box UI Elements Demo
folder 189803765719 ClassificationService
folder 198775845609 JWT Folder for UI Sample Apps
folder 172611202270 My Signed Documents
folder 170845975022 Waivers
folder 176837925976 WebhookYou can also list all the users visible to your service user:
users = client.users()
for user in users:
print(f"User: {user.name}\tid:{user.id}\temail:{user.login}")which outputs in my case:
User: Administrator id:18662105676 email:AppUser_1715931_Il2dcyHuqu@boxdevedition.com
User: Administrator id:18662356345 email:AppUser_1715931_vt8XOps1Ff@boxdevedition.com
User: Administrator id:18661971368 email:AppUser_1715931_xSifhdw6W7@boxdevedition.com
User: Investment User id:22240548078 email:barduinor+inv@gmail.com
User: Wealth User id:22240405099 email:barduinor+we@gmail.com
User: Wholesale User id:22240545678 email:barduinor+wh@gmail.com
User: Rui Barbosa id:18622116055 email:barduinor@gmail.comFrom here you can create a folder and share it with your personal user and verify how does it show up on your side.
# create a new folder
folder_new = client.folder(CFG.PARENT_FOLDER_ID).create_subfolder("Shared with RB")
# share it with RB as co-owner
folder_new.add_collaborator("barduinor@gmail.com", CollaborationRole.CO_OWNER)Now when I login as myself on the box app, I can see the folder owned by the service user which created it:
Now, lets upload a file, again using the app service user to this folder which in my case is:
Type ID Name
---- -- ----
folder 177388203339 100k
folder 198947288178 aaaa
folder 172599089223 Bookings
folder 163422716106 Box UI Elements Demo
folder 189803765719 ClassificationService
folder 198775845609 JWT Folder for UI Sample Apps
folder 172611202270 My Signed Documents
folder 198948099055 Shared with RB
folder 170845975022 Waivers
folder 176837925976 Webhookusing this:
client.folder("198948099055").upload("test_upload.txt", "test_upload.txt")
And the file is also owned by the service user:
Hope this helps you test your use case and find out why we can see 2 distinct service users.
Best regards
-
Hello Rui,
Regarding the suggested app parameters, I do have same authentication as you have.
I also tested :
- app access level (it was App acc only for me)
- advanced features (it was no ticked for both paremeters, I ticked the boxes)
but no effect, still have the issue.
Regarding the following test
client.user().get()
I have following error message
Context Info: {'errors': [{'reason': 'invalid_parameter', 'name': 'user', 'message': "Invalid value 'u_217074553'. 'user' with value 'u_217074553' not found"}]}
For the client.users() request it does not print anything.
It looks like that either i don't have rights to get users for client, or there are no users...
I only see this log trace
boxsdk.pagination.limit_offset_based_object_collection.LimitOffsetBasedObjectCollection
I tried several things, like managing exception and so on, but still hve nothing print
try:
user_iterator = self.client.users()
except BoxAPIException as e:
print(f"Error fetching user list: {e}")
exit()
# Loop through all users
for user in user_iterator:
if isinstance(user, User):
print(user.name, user.login)
else:
print(f"Skipping non-user object: {user}")The create_subfolder / add_collaborator proposed test is what I did as a workaround and I do confirm it works fine and this now what I'm using.
サインインしてコメントを残してください。
コメント
5件のコメント