1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
|
#!/usr/bin/env python
from __future__ import absolute_import, division, print_function, unicode_literals
import datetime
import os
import random
import re
import string
import sys
import pytest
try:
from io import BytesIO
except ImportError:
from StringIO import StringIO as BytesIO
from dropbox import (
create_session,
Dropbox,
DropboxOAuth2Flow,
DropboxTeam,
session,
stone_serializers,
)
from dropbox.dropbox_client import PATH_ROOT_HEADER, SELECT_USER_HEADER
from dropbox.exceptions import (
ApiError,
AuthError,
# BadInputError,
PathRootError,
)
from dropbox.files import (
DeleteResult,
ListFolderError,
PathOrLink,
SharedLinkFileInfo,
)
from dropbox.common import (
PathRoot,
PathRoot_validator,
)
from dropbox.session import SSLError
# Key Types
REFRESH_TOKEN_KEY = "REFRESH_TOKEN"
ACCESS_TOKEN_KEY = "DROPBOX_TOKEN"
CLIENT_ID_KEY = "CLIENT_ID"
CLIENT_SECRET_KEY = "CLIENT_SECRET"
# App Types
SCOPED_KEY = "SCOPED"
LEGACY_KEY = "LEGACY"
# User Types
USER_KEY = "USER"
TEAM_KEY = "TEAM"
# Misc types
SHARED_LINK_KEY = "DROPBOX_SHARED_LINK"
def format_env_name(app_type=SCOPED_KEY, user_type=USER_KEY, key_type=ACCESS_TOKEN_KEY):
return '{}_{}_{}'.format(app_type, user_type, key_type)
def _value_from_env_or_die(env_name):
value = os.environ.get(env_name)
if value is None:
print('Set {} environment variable to a valid value.'.format(env_name),
file=sys.stderr)
sys.exit(1)
return value
_TRUSTED_CERTS_FILE = os.path.join(os.path.dirname(__file__), "trusted-certs.crt")
_EXPIRED_CERTS_FILE = os.path.join(os.path.dirname(__file__), "expired-certs.crt")
# enables testing both with and without a manually-provided CA bundle
@pytest.fixture(params=[None, _TRUSTED_CERTS_FILE], ids=["no-pinning", "pinning"])
def dbx_session(request):
return create_session(ca_certs=request.param)
@pytest.fixture()
def dbx_from_env(dbx_session):
oauth2_token = _value_from_env_or_die(format_env_name())
return Dropbox(oauth2_token, session=dbx_session)
@pytest.fixture()
def refresh_dbx_from_env(dbx_session):
refresh_token = _value_from_env_or_die(format_env_name(SCOPED_KEY, USER_KEY, REFRESH_TOKEN_KEY))
app_key = _value_from_env_or_die(format_env_name(SCOPED_KEY, USER_KEY, CLIENT_ID_KEY))
app_secret = _value_from_env_or_die(format_env_name(SCOPED_KEY, USER_KEY, CLIENT_SECRET_KEY))
return Dropbox(oauth2_refresh_token=refresh_token,
app_key=app_key, app_secret=app_secret,
session=dbx_session)
@pytest.fixture()
def dbx_team_from_env(dbx_session):
team_oauth2_token = _value_from_env_or_die(
format_env_name(SCOPED_KEY, TEAM_KEY, ACCESS_TOKEN_KEY))
return DropboxTeam(team_oauth2_token, session=dbx_session)
@pytest.fixture()
def dbx_app_auth_from_env(dbx_session):
app_key = _value_from_env_or_die(format_env_name(SCOPED_KEY, USER_KEY, CLIENT_ID_KEY))
app_secret = _value_from_env_or_die(format_env_name(SCOPED_KEY, USER_KEY, CLIENT_SECRET_KEY))
return Dropbox(app_key=app_key, app_secret=app_secret, session=dbx_session)
@pytest.fixture()
def dbx_share_url_from_env():
return _value_from_env_or_die(SHARED_LINK_KEY)
MALFORMED_TOKEN = 'asdf'
INVALID_TOKEN = 'z' * 62
# Need bytes type for Python3
DUMMY_PAYLOAD = string.ascii_letters.encode('ascii')
RANDOM_FOLDER = random.sample(string.ascii_letters, 15)
TIMESTAMP = str(datetime.datetime.utcnow())
STATIC_FILE = "/test.txt"
@pytest.fixture(scope='module')
def pytest_setup():
print("Setup")
dbx = Dropbox(_value_from_env_or_die(format_env_name()))
try:
dbx.files_delete(STATIC_FILE)
except Exception:
print("File not found")
try:
dbx.files_delete('/Test/%s' % TIMESTAMP)
except Exception:
print("File not found")
@pytest.mark.usefixtures(
"pytest_setup",
"dbx_from_env",
"refresh_dbx_from_env",
"dbx_app_auth_from_env",
"dbx_share_url_from_env",
)
class TestDropbox:
def test_multi_auth(self, dbx_from_env, dbx_app_auth_from_env, dbx_share_url_from_env):
# Test for user (with oauth token)
preview_result, resp = dbx_from_env.files_get_thumbnail_v2(
PathOrLink.link(SharedLinkFileInfo(url=dbx_share_url_from_env))
)
assert resp.status_code == 200
# Test for app (with app key and secret)
preview_result, resp = dbx_from_env.files_get_thumbnail_v2(
PathOrLink.link(SharedLinkFileInfo(url=dbx_share_url_from_env))
)
assert resp.status_code == 200
def test_refresh(self, refresh_dbx_from_env):
refresh_dbx_from_env.users_get_current_account()
def test_app_auth(self, dbx_app_auth_from_env):
dbx_app_auth_from_env.check_app(query="hello world")
def test_downscope(self, refresh_dbx_from_env):
refresh_dbx_from_env.users_get_current_account()
refresh_dbx_from_env.refresh_access_token(scope=['files.metadata.read'])
with pytest.raises(AuthError):
# Should fail because downscoped to not include needed scope
refresh_dbx_from_env.users_get_current_account()
def test_rpc(self, dbx_from_env):
dbx_from_env.files_list_folder('')
# Test API error
random_folder_path = '/' + \
''.join(RANDOM_FOLDER)
with pytest.raises(ApiError) as cm:
dbx_from_env.files_list_folder(random_folder_path)
assert isinstance(cm.value.error, ListFolderError)
def test_upload_download(self, dbx_from_env):
# Upload file
random_filename = ''.join(RANDOM_FOLDER)
random_path = '/Test/%s/%s' % (TIMESTAMP, random_filename)
test_contents = DUMMY_PAYLOAD
dbx_from_env.files_upload(test_contents, random_path)
# Download file
_, resp = dbx_from_env.files_download(random_path)
assert DUMMY_PAYLOAD == resp.content
# Cleanup folder
dbx_from_env.files_delete('/Test/%s' % TIMESTAMP)
def test_bad_upload_types(self, dbx_from_env):
with pytest.raises(TypeError):
dbx_from_env.files_upload(BytesIO(b'test'), '/Test')
def test_clone_when_user_linked(self, dbx_from_env):
new_dbx = dbx_from_env.clone()
assert dbx_from_env is not new_dbx
assert isinstance(new_dbx, dbx_from_env.__class__)
def test_with_path_root_constructor(self, dbx_from_env):
# Verify valid mode types
for path_root in (
PathRoot.home,
PathRoot.root("123"),
PathRoot.namespace_id("123"),
):
dbx_new = dbx_from_env.with_path_root(path_root)
assert dbx_new is not dbx_from_env
expected = stone_serializers.json_encode(PathRoot_validator, path_root)
assert dbx_new._headers.get(PATH_ROOT_HEADER) == expected
# verify invalid mode raises ValueError
with pytest.raises(ValueError):
dbx_from_env.with_path_root(None)
def test_path_root(self, dbx_from_env):
root_info = dbx_from_env.users_get_current_account().root_info
root_ns = root_info.root_namespace_id
home_ns = root_info.home_namespace_id
# verify home mode
dbxpr = dbx_from_env.with_path_root(PathRoot.home)
dbxpr.files_list_folder('')
# verify root mode
dbxpr = dbx_from_env.with_path_root(PathRoot.root(root_ns))
dbxpr.files_list_folder('')
# verify namespace_id mode
dbxpr = dbx_from_env.with_path_root(PathRoot.namespace_id(home_ns))
dbxpr.files_list_folder('')
def test_path_root_err(self, dbx_from_env):
# verify invalid namespace return is_no_permission error
dbxpr = dbx_from_env.with_path_root(PathRoot.namespace_id("1234567890"))
with pytest.raises(PathRootError) as cm:
dbxpr.files_list_folder('')
assert cm.value.error.is_no_permission()
dbxpr = dbx_from_env.with_path_root(PathRoot.root("1234567890"))
with pytest.raises(PathRootError) as cm:
dbxpr.files_list_folder('')
assert cm.value.error.is_invalid_root()
def test_versioned_route(self, dbx_from_env):
# Upload a test file
dbx_from_env.files_upload(DUMMY_PAYLOAD, STATIC_FILE)
# Delete the file with v2 route
resp = dbx_from_env.files_delete_v2(STATIC_FILE)
# Verify response type is of v2 route
assert isinstance(resp, DeleteResult)
@pytest.mark.usefixtures(
"pytest_setup",
"dbx_team_from_env",
)
class TestDropboxTeam:
def test_team(self, dbx_team_from_env):
dbx_team_from_env.team_groups_list()
r = dbx_team_from_env.team_members_list()
if r.members:
# Only test assuming a member if there is a member
team_member_id = r.members[0].profile.team_member_id
dbx_team_from_env.as_user(team_member_id).files_list_folder('')
def test_as_user(self, dbx_team_from_env):
dbx_as_user = dbx_team_from_env.as_user('1')
path_root = PathRoot.root("123")
dbx_new = dbx_as_user.with_path_root(path_root)
assert isinstance(dbx_new, Dropbox)
assert dbx_new._headers.get(SELECT_USER_HEADER) == '1'
expected = stone_serializers.json_encode(PathRoot_validator, path_root)
assert dbx_new._headers.get(PATH_ROOT_HEADER) == expected
def test_as_admin(self, dbx_team_from_env):
dbx_as_admin = dbx_team_from_env.as_admin('1')
assert isinstance(dbx_as_admin, Dropbox)
def test_clone_when_team_linked(self, dbx_team_from_env):
new_dbxt = dbx_team_from_env.clone()
assert dbx_team_from_env is not new_dbxt
assert isinstance(new_dbxt, dbx_team_from_env.__class__)
def test_default_oauth2_urls():
flow_obj = DropboxOAuth2Flow('dummy_app_key', 'dummy_app_secret',
'http://localhost/dummy', 'dummy_session', 'dbx-auth-csrf-token')
assert re.match(
r'^https://{}/oauth2/authorize\?'.format(re.escape(session.WEB_HOST)),
flow_obj._get_authorize_url('http://localhost/redirect', 'state', 'legacy'),
)
assert flow_obj.build_url(
'/oauth2/authorize'
) == 'https://{}/oauth2/authorize'.format(session.API_HOST)
assert flow_obj.build_url(
'/oauth2/authorize', host=session.WEB_HOST
) == 'https://{}/oauth2/authorize'.format(session.WEB_HOST)
def test_bad_auth(dbx_session):
# Test malformed token
malformed_token_dbx = Dropbox(MALFORMED_TOKEN, session=dbx_session)
# TODO: backend is no longer returning `BadInputError`
# with pytest.raises(BadInputError,) as cm:
# malformed_token_dbx.files_list_folder('')
# assert 'token is malformed' in cm.value.message
with pytest.raises(AuthError):
malformed_token_dbx.files_list_folder('')
# Test reasonable-looking invalid token
invalid_token_dbx = Dropbox(INVALID_TOKEN, session=dbx_session)
with pytest.raises(AuthError) as cm:
invalid_token_dbx.files_list_folder('')
assert cm.value.error.is_invalid_access_token()
def test_bad_pins():
# sanity-check: if we're pinning using expired pins, we should fail w/ an SSL error
_dbx = Dropbox("dummy_token", ca_certs=_EXPIRED_CERTS_FILE)
with pytest.raises(SSLError,):
_dbx.files_list_folder('')
def test_bad_pins_session():
_session = create_session(ca_certs=_EXPIRED_CERTS_FILE)
_dbx = Dropbox("dummy_token2", session=_session)
with pytest.raises(SSLError,):
_dbx.files_list_folder('')
|