Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@
.htmlcov
.DS_Store
.idea
.vscode
setup.cfg
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
PLUGIN_VERSION=1.0.0
PLUGIN_VERSION=1.0.1
PLUGIN_ID=box-com

plugin:
Expand Down
8 changes: 8 additions & 0 deletions parameter-sets/box-set-id/parameter-set.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@
"type": "PASSWORD",
"description": "Generated on Box.com for use in DSS",
"mandatory": true
},
{
"name": "enterprise_id",
"label": "Company name",
"type": "STRING",
"description": "Only for private API",
"visibilityCondition": false,
"defaultValue": null
}
]
}
33 changes: 33 additions & 0 deletions parameter-sets/oauth-login/parameter-set.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"meta": {
"label": "Box.com Single Sign On",
"description": "",
"icon": "icon-cloud"
},
"defaultDefinableInline": true,
"defaultDefinableAtProjectLevel": true,
"pluginParams": [],
"params": [
{
"name": "boxcom_oauth",
"type": "CREDENTIAL_REQUEST",
"label": "Box.com Single Sign On",
"credentialRequestSettings": {
"type": "OAUTH2",
"oauth2Flow": "authorization_code",
"oauth2Provider": "AZURE",
"authorizationEndpoint": "https://account.box.com/api/oauth2/authorize",
"tokenEndpoint": "https://api.box.com/oauth2/token",
"scope": "root_readwrite"
}
},
{
"name": "enterprise_id",
"label": "Company name",
"type": "STRING",
"description": "Only for private API",
"visibilityCondition": false,
"defaultValue": null
}
]
}
2 changes: 1 addition & 1 deletion plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "box-com",
"version": "1.0.0",
"version": "1.0.1",
"meta": {
"label": "Box.com",
"description": "Read and write data from/to your Box.com account",
Expand Down
26 changes: 25 additions & 1 deletion python-fs-providers/box-com_box-com/fs-provider.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,35 @@
"icon": "icon-cloud"
},
"params": [
{
"name": "auth_type",
"label": "Type of authentication",
"type": "SELECT",
"defaultValue": "token",
"selectChoices": [
{
"value": "token",
"label": "Access token"
},
{
"value": "oauth",
"label": "Box.com Single Sign On"
}
]
},
{
"name": "box_com_connection",
"label": "Box.com connection",
"type": "PRESET",
"parameterSetId": "box-set-id"
"parameterSetId": "box-set-id",
"visibilityCondition": "model.auth_type == 'token'"
},
{
"name": "oauth_login",
"label": "Box.com Single Sign On",
"type": "PRESET",
"parameterSetId": "oauth-login",
"visibilityCondition": "model.auth_type == 'oauth'"
},
{
"name": "cache_enabled",
Expand Down
72 changes: 54 additions & 18 deletions python-fs-providers/box-com_box-com/fs-provider.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
from dataiku.fsprovider import FSProvider
from boxsdk import OAuth2, Client

import os, shutil, json, hashlib, logging
import os
import shutil
import hashlib
from safe_logger import SafeLogger

from box_item import BoxItem
from utils import get_full_path, get_rel_path, get_normalized_path

logger = SafeLogger("box-com plugin", forbiden_keys=["access_token", "boxcom_oauth"])


class BoxComFSProvider(FSProvider):
def __init__(self, root, config, client):
Expand All @@ -17,8 +21,15 @@ def __init__(self, root, config, client):
if len(root) > 0 and root[0] == '/':
root = root[1:]
self.root = root
logger.warning("config={}".format(logger.filter_secrets(config)))
logger.warning("client={}".format(logger.filter_secrets(client)))
self.connection = client.get("box_com_connection")
self.access_token = self.connection['access_token']
self.enterprise_id = self.connection.get("enterprise_id", None)
self.auth_type = config.get("auth_type", "token")
if self.auth_type == "oauth":
self.access_token = config.get("oauth_login")["boxcom_oauth"]
else:
self.access_token = self.connection['access_token']
self.cache_enabled = config.get("cache_enabled")
if self.cache_enabled:
cache_file_name = hashlib.sha1(self.access_token.encode('utf-8')).hexdigest()
Expand All @@ -30,7 +41,14 @@ def __init__(self, root, config, client):
access_token=self.access_token
)
self.client = Client(auth)
self.user = self.client.user().get()
if self.enterprise_id is not None and self.enterprise_id != "":
session = self.client._session
api = API()
api.BASE_API_URL = 'https://{enterprise_id}.ent.box.com/2.0'.format(enterprise_id=self.enterprise_id)
session._api_config = api
self.client = Client(auth, session=session)
logger.warning("base api url updated to: {}".format(self.client.session._api_config.BASE_API_URL))

self.box_item = BoxItem(cache_file_name, root, self.client)
self.box_item.check_path_format(get_normalized_path(root))

Expand Down Expand Up @@ -65,16 +83,25 @@ def browse(self, path):
full_path = get_full_path(self.root, path)
item = self.box_item.get_by_path(get_rel_path(full_path))
if item.not_exists():
return {'fullPath' : normalized_path, 'exists' : False}
return {
'fullPath': normalized_path,
'exists': False
}
if item.is_folder():
return {'fullPath' : normalized_path, 'exists' : True, 'directory' : True, 'children' : item.get_children(normalized_path), 'lastModified' : item.get_last_modified()}
return {
'fullPath': normalized_path,
'exists': True,
'directory': True,
'children': item.get_children(normalized_path),
'lastModified': item.get_last_modified()
}
else:
return item.get_as_browse()

def enumerate(self, path, first_non_empty):
"""
Enumerate files recursively from prefix. If first_non_empty, stop at the first non-empty file.

If the prefix doesn't denote a file or folder, return None
"""
full_path = get_full_path(self.root, path)
Expand All @@ -88,18 +115,18 @@ def enumerate(self, path, first_non_empty):
if item.is_folder():
paths = self.list_recursive(normalized_path, item.id, first_non_empty)
else:
paths.append({'path':normalized_path.split("/")[-1], 'size':item.size, 'lastModified':int(0) * 1000})
paths.append({'path': normalized_path.split("/")[-1], 'size': item.size, 'lastModified': int(0) * 1000})
return paths

def list_recursive(self, path, folder_id, first_non_empty):
paths = []
if path == "/":
path = ""
for child in self.client.folder(folder_id).get_items(fields = ['modified_at','name','type','size']):
for child in self.client.folder(folder_id).get_items(fields=['modified_at', 'name', 'type', 'size']):
if child.type == self.box_item.BOX_FOLDER:
paths.extend(self.list_recursive(path + '/' + child.name, child.id, first_non_empty))
else:
paths.append({'path':path + '/' + child.name, 'size':child.size})
paths.append({'path': path + '/' + child.name, 'size': child.size})
if first_non_empty:
return paths
return paths
Expand All @@ -109,7 +136,7 @@ def delete_recursive(self, path):
Delete recursively from path. Return the number of deleted files (optional)
"""
full_path = get_full_path(self.root, path)
item = self.box_item.get_by_path(full_path, force_no_cache = True)
item = self.box_item.get_by_path(full_path, force_no_cache=True)
if item.not_exists():
return 0
else:
Expand All @@ -126,17 +153,17 @@ def move(self, from_path, to_path):
from_base, from_item_name = os.path.split(full_from_path)
to_base, to_item_name = os.path.split(full_to_path)

from_item = self.box_item.get_by_path(full_from_path, force_no_cache = True)
from_item = self.box_item.get_by_path(full_from_path, force_no_cache=True)

if from_item.not_exists():
return False

from_item_id = from_item.get_id()
from_item_is_folder = from_item.is_folder()

to_item = self.box_item.get_by_path(full_to_path, force_no_cache = True)
to_item = self.box_item.get_by_path(full_to_path, force_no_cache=True)
if to_item.not_exists():
to_item = self.box_item.get_by_path(to_base, force_no_cache = True)
to_item = self.box_item.get_by_path(to_base, force_no_cache=True)

destination_folder = self.client.folder(to_item.get_id())

Expand All @@ -156,7 +183,7 @@ def read(self, path, stream, limit):
full_path = get_full_path(self.root, path)
byte_range = None

if limit is not None and limit is not "-1":
if limit is not None and limit != "-1":
int_limit = int(limit)
if int_limit > 0:
byte_range = (0, int(limit) - 1)
Expand All @@ -171,8 +198,17 @@ def write(self, path, stream):
Write the stream to the object denoted by path into the stream
"""
full_path = get_full_path(self.root, path)
item = self.box_item.create_path(full_path, force_no_cache = True)
item = self.box_item.create_path(full_path, force_no_cache=True)
if item.is_folder():
item.write_stream(stream)
else:
raise Exception('Not a file name')
raise Exception('Not a file name')


class API(object):
"""Configuration object containing the URLs for the Box API."""
BASE_API_URL = 'https://api.box.com/2.0'
UPLOAD_URL = 'https://upload.box.com/api/2.0'
OAUTH2_API_URL = 'https://api.box.com/oauth2' # <https://developers.box.co$
OAUTH2_AUTHORIZE_URL = 'https://account.box.com/api/oauth2/authorize' # <h$
MAX_RETRY_ATTEMPTS = 5
Loading