Skip to content

Commit fb19d70

Browse files
author
Karl Rieb
committed
Add support for app auth endpoints.
generator: Fix bug with import logic. generator: Fix bug with Javadoc sanitization. Fixes T84874.
1 parent e766819 commit fb19d70

11 files changed

Lines changed: 335 additions & 170 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@
66

77
# Output file when rendering ReadMe.md locally.
88
/ReadMe.html
9+
10+
# editor temp files
11+
*~

ChangeLog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
- Add support for Dropbox API app endpoints.
12
- Update upload-file example to include chunked upload example.
23
- Increase default socket read timeout to 2 minutes.
34
- Parse Retry-After header for 503 retry exceptions in API v1.

babel

Submodule babel updated from f03a2fc to df5cedd

generator/java.babelg.py

Lines changed: 42 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -93,15 +93,24 @@ def sanitize_pattern(pattern):
9393
return pattern.replace('\\', '\\\\').replace('"', '\\"')
9494

9595

96+
_JAVADOC_REPLACEMENT_CHARS = (
97+
('&', '&'),
98+
('<', '&lt;'),
99+
('>', '&gt;'),
100+
)
96101
def sanitize_javadoc(doc):
97102
# sanitize &, <, > characters
98-
for char, code in (('&', '&amp;'),
99-
('<', '&lt;'),
100-
('>', '&gt;')):
103+
for char, code in _JAVADOC_REPLACEMENT_CHARS:
101104
doc = doc.replace(char, code)
102105
return doc
103106

104107

108+
def unsanitize_javadoc(doc):
109+
for char, code in _JAVADOC_REPLACEMENT_CHARS:
110+
doc = doc.replace(code, char)
111+
return doc
112+
113+
105114
def oxford_comma_list(values, conjunction='and'):
106115
if not values:
107116
return None
@@ -333,7 +342,9 @@ def current_imports(self):
333342
local_package = self.current_java_package
334343
if local_package:
335344
local_prefix = local_package + '.'
336-
return frozenset(i for i in self._current_imports if not i.startswith(local_prefix))
345+
def is_local(import_):
346+
return import_.startswith(local_prefix) and '.' not in import_[len(local_prefix):]
347+
return frozenset(i for i in self._current_imports if not is_local(i))
337348
else:
338349
return frozenset(self._current_imports)
339350

@@ -349,13 +360,17 @@ def add_imports(self, *imports):
349360
class if necessary.
350361
351362
"""
352-
assert self.current_java_class, "No Java class scoped for generation."
363+
assert self._current_class, "No Java class scoped for generation."
353364

354365
get_name = lambda v: v.rsplit('.', 1)[-1]
355366

356-
current_class_prefix = self._current_class + '.' if self._current_class else None
367+
current_class_prefix = self._current_class + '.'
357368

358369
existing_names = set(get_name(import_) for import_ in self._current_imports)
370+
371+
# avoid issues where we import a class with the same name as us
372+
existing_names.add(get_name(self._current_class))
373+
359374
for import_ in imports:
360375
if not isinstance(import_, JavaClass):
361376
java_class = JavaClass(self, import_)
@@ -718,8 +733,9 @@ def babel_filenames(self):
718733
# fallback to first route
719734
for route in self.routes:
720735
filenames[route.babel_filename] = None
736+
# TODO: fallback to aliases. enable assert after doing this
737+
#assert filenames, "namespace not associated with any babel files: %s" % self.babel_name
721738

722-
assert filenames, "namespace not associated with any babel files"
723739
return filenames.keys()
724740

725741
@property
@@ -1561,6 +1577,8 @@ def javadoc_ref_handler(self, tag, val, context=None):
15611577
ref = '{@code %s}' % camelcase(val)
15621578
elif tag == 'link':
15631579
anchor, link = val.rsplit(' ', 1)
1580+
# unsanitize from previous sanitize calls
1581+
anchor = unsanitize_javadoc(anchor)
15641582
# do not sanitize this HTML
15651583
return '<a href="%s">%s</a>' % (link, anchor)
15661584
elif tag == 'val':
@@ -1574,7 +1592,7 @@ def javadoc_ref_handler(self, tag, val, context=None):
15741592
def translate_babel_doc(self, doc, context=None):
15751593
if doc:
15761594
handler = lambda tag, val: self.javadoc_ref_handler(tag, val, context=context)
1577-
return self._ctx.g.process_doc(doc, handler)
1595+
return self._ctx.g.process_doc(sanitize_javadoc(doc), handler)
15781596
else:
15791597
return doc
15801598

@@ -2241,71 +2259,11 @@ def create_package_path(self, package_name):
22412259
def generate_dbx_clients(self):
22422260
out = self.g.emit
22432261

2244-
user_client_class_name = 'DbxClientV2'
2245-
with self.dbx_client(
2246-
self.ctx.base_package, user_client_class_name, 'user',
2247-
2248-
"""
2249-
Use this class to make remote calls to the Dropbox API user endpoints. User endpoints
2250-
expose actions you can perform as a Dropbox user. You'll need an access token first,
2251-
normally acquired by directing a Dropbox user through the auth flow using {@link
2252-
com.dropbox.core.DbxWebAuth}.
2253-
"""
2254-
):
2255-
pass
2256-
2257-
with self.dbx_client(
2258-
self.ctx.base_package, 'DbxTeamClientV2', 'team',
2259-
"""
2260-
Use this class to make remote calls to the Dropbox API team endpoints. Team endpoints
2261-
expose actions you can perform on or for a Dropbox team. You'll need a team access
2262-
token first, normally acquired by directing a Dropbox Business team administrator
2263-
through the auth flow using {@link com.dropbox.core.DbxWebAuth}.
2264-
2265-
Team clients can access user endpoints by using the {@link #asMember} method. This
2266-
allows team clients to perform actions as a particular team member.
2267-
"""
2268-
):
2269-
self.doc.generate_javadoc(
2270-
"""
2271-
Returns a {@link %s} that performs requests against Dropbox API user endpoints as the
2272-
given team member.
2273-
2274-
This method performs no validation of the team member ID.
2275-
""" % (user_client_class_name,),
2276-
params=OrderedDict(memberId="Team member ID of member in this client's team, never {@code null}."),
2277-
returns="Dropbox client that issues requests to user endpoints as the given team member",
2278-
throws=OrderedDict(IllegalArgumentException="If {@code memberId} is {@code null}")
2279-
)
2280-
2281-
out('')
2282-
with self.g.block('public %s asMember(String memberId)' % (user_client_class_name,)):
2283-
out('if (memberId == null) throw new IllegalArgumentException("\'memberId\' should not be null");')
2284-
out('return new %s(new DbxTeamRawClientV2(_client, memberId));' % (user_client_class_name,))
2285-
2286-
self.doc.generate_javadoc(
2287-
"""
2288-
{@link DbxRawClientV2} raw client that adds select-user header to all requests.
2289-
Used to perform requests as a particular team member.
2290-
"""
2291-
)
2292-
with self.g.block('private static final class DbxTeamRawClientV2 extends DbxRawClientV2'):
2293-
out('private final String memberId;')
2294-
out('')
2295-
with self.g.block('private DbxTeamRawClientV2(DbxRawClientV2 underlying, String memberId)'):
2296-
out('super(underlying);')
2297-
out('this.memberId = memberId;')
2298-
out('')
2299-
out('@Override')
2300-
with self.g.block('protected void addAuthHeaders(java.util.List<HttpRequestor.Header> headers)'):
2301-
out('super.addAuthHeaders(headers);')
2302-
out('com.dropbox.core.DbxRequestUtil.addSelectUserHeader(headers, memberId);')
2303-
2304-
2305-
@contextmanager
2306-
def dbx_client(self, package_name, class_name, auth, class_doc):
2307-
assert class_doc
2262+
self.generate_dbx_client(self.ctx.base_package, 'DbxClientV2Base', 'user')
2263+
self.generate_dbx_client(self.ctx.base_package, 'DbxTeamClientV2Base', 'team')
2264+
self.generate_dbx_client(self.ctx.base_package, 'DbxAppClientV2Base', 'app')
23082265

2266+
def generate_dbx_client(self, package_name, class_name, auth):
23092267
out = self.g.emit
23102268

23112269
if auth == 'user':
@@ -2320,76 +2278,36 @@ def dbx_client(self, package_name, class_name, auth, class_doc):
23202278

23212279
package_relpath = self.create_package_path(package_name)
23222280
file_name = os.path.join(package_relpath, class_name + '.java')
2323-
with self.g.output_to_relative_path(file_name):
2281+
fq_class_name = package_name + '.' + class_name
2282+
with self.g.output_to_relative_path(file_name), self.ctx.scoped(fq_class_name):
23242283
self.generate_file_header()
23252284
out('package %s;' % package_name)
2326-
out('')
2327-
out('import com.dropbox.core.DbxHost;')
2328-
out('import com.dropbox.core.DbxRequestConfig;')
2329-
out('import com.dropbox.core.http.HttpRequestor;')
23302285

23312286
for namespace in namespaces:
2332-
out('import %s;' % namespace.java_class(auth))
2287+
self.ctx.add_imports(namespace.java_class(auth))
2288+
2289+
self.importer.generate_imports()
23332290

23342291
out('')
23352292
self.doc.generate_javadoc(
23362293
"""
2337-
%s
2338-
2339-
This class has no mutable state, so it's thread safe as long as you pass in a thread
2340-
safe {@link HttpRequestor} implementation.
2341-
""" % class_doc
2294+
Base class for %s auth clients.
2295+
""" % auth
23422296
)
2343-
with self.g.block('public final class %s' % class_name):
2344-
out('private final DbxRawClientV2 _client;')
2297+
with self.g.block('public class %s' % class_name):
2298+
out('protected final DbxRawClientV2 _client;')
2299+
out('')
23452300
for namespace in namespaces:
23462301
out('private final %s %s;' % (namespace.java_class(auth), namespace.java_field))
2347-
out('')
2348-
2349-
param_docs = OrderedDict((
2350-
('requestConfig', 'Default attributes to use for each request'),
2351-
('accessToken', 'OAuth 2 access token (that you got from Dropbox) '
2352-
'that gives your app the ability to make Dropbox API calls. Typically '
2353-
'acquired through {@link com.dropbox.core.DbxWebAuth}'),
2354-
('host', 'Dropbox hosts to send requests to (used for mocking and testing)'),
2355-
))
2356-
2357-
self.doc.generate_javadoc(
2358-
"""
2359-
Creates a client that uses the given OAuth 2 access token as authorization when
2360-
performing requests against the default Dropbox hosts.
2361-
""",
2362-
params=OrderedDict(
2363-
(k, v) for k, v in param_docs.items()
2364-
if k in ('requestConfig', 'accessToken')
2365-
)
2366-
)
2367-
with self.g.block('public %s(DbxRequestConfig requestConfig, String accessToken)' % class_name):
2368-
out('this(requestConfig, accessToken, DbxHost.DEFAULT);')
2369-
2370-
out('')
2371-
2372-
2373-
self.doc.generate_javadoc(
2374-
"""
2375-
Same as {@link #%s(DbxRequestConfig, String)} except you can also set the
2376-
hostnames of the Dropbox API servers. This is used in testing. You don't
2377-
normally need to call this.
2378-
""" % (class_name,),
2379-
params=param_docs
2380-
)
2381-
with self.g.block('public %s(DbxRequestConfig requestConfig, String accessToken, DbxHost host)' % class_name):
2382-
out('this(new DbxRawClientV2(requestConfig, accessToken, host));')
23832302

23842303
out('')
23852304
self.doc.generate_javadoc(
23862305
"""
23872306
For internal use only.
23882307
""",
2389-
params=param_docs
2308+
params=(('_client', 'Raw v2 client to use for issuing requests'),)
23902309
)
2391-
# package-private
2392-
with self.g.block('%s(DbxRawClientV2 _client)' % class_name):
2310+
with self.g.block('protected %s(DbxRawClientV2 _client)' % class_name):
23932311
out('this._client = _client;')
23942312
for namespace in namespaces:
23952313
out('this.%s = new %s(_client);' % (
@@ -2407,11 +2325,6 @@ def dbx_client(self, package_name, class_name, auth, class_doc):
24072325
with self.g.block("public %s %s()" % (namespace.java_class(auth), namespace.java_getter)):
24082326
out('return %s;' % namespace.java_field)
24092327

2410-
out('')
2411-
# allow caller to add custom methods to the client
2412-
yield
2413-
2414-
24152328
def generate_namespace(self, namespace):
24162329
# create class files for all namespace data types in this package
24172330
for data_type in namespace.data_types:

run-babel-codegen

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ echo "Generating..."
7272
PYTHONPATH="$babel_dir" python3 -m babelapi.cli \
7373
"$generator" \
7474
"$gen_dir"\
75-
"${spec_dir}"/*.babel \
75+
$(find "${spec_dir}" -name "*.babel") \
7676
--\
7777
--package com.dropbox.core.v2
7878

src/main/java/com/dropbox/core/DbxRequestUtil.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,18 +93,32 @@ private static String encodeUrlParams(/*@Nullable*/String userLocale,
9393
}
9494

9595
public static List<HttpRequestor.Header> addAuthHeader(/*@Nullable*/List<HttpRequestor.Header> headers, String accessToken) {
96+
if (accessToken == null) throw new NullPointerException("accessToken");
9697
if (headers == null) headers = new ArrayList<HttpRequestor.Header>();
98+
9799
headers.add(new HttpRequestor.Header("Authorization", "Bearer " + accessToken));
98100
return headers;
99101
}
100102

101103
public static List<HttpRequestor.Header> addSelectUserHeader(/*@Nullable*/List<HttpRequestor.Header> headers, String memberId) {
102-
if (memberId == null) throw new IllegalArgumentException("'memberId' is null");
104+
if (memberId == null) throw new NullPointerException("memberId");
103105
if (headers == null) headers = new ArrayList<HttpRequestor.Header>();
106+
104107
headers.add(new HttpRequestor.Header("Dropbox-API-Select-User", memberId));
105108
return headers;
106109
}
107110

111+
public static List<HttpRequestor.Header> addBasicAuthHeader(/*@Nullable*/List<HttpRequestor.Header> headers, String username, String password) {
112+
if (username == null) throw new NullPointerException("username");
113+
if (password == null) throw new NullPointerException("password");
114+
if (headers == null) headers = new ArrayList<HttpRequestor.Header>();
115+
116+
String credentials = username + ":" + password;
117+
String base64Credentials = StringUtil.base64Encode(StringUtil.stringToUtf8(credentials));
118+
headers.add(new HttpRequestor.Header("Authorization", "Basic " + base64Credentials));
119+
return headers;
120+
}
121+
108122
public static List<HttpRequestor.Header> addUserAgentHeader(
109123
/*@Nullable*/List<HttpRequestor.Header> headers,
110124
DbxRequestConfig requestConfig,

src/main/java/com/dropbox/core/DbxWebAuthHelper.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,7 @@ public static DbxAuthFinish finish(DbxAppInfo appInfo,
4141
};
4242

4343
ArrayList<HttpRequestor.Header> headers = new ArrayList<HttpRequestor.Header>();
44-
String credentials = appInfo.getKey() + ":" + appInfo.getSecret();
45-
String base64Credentials = StringUtil.base64Encode(StringUtil.stringToUtf8(credentials));
46-
headers.add(new HttpRequestor.Header("Authorization", "Basic " + base64Credentials));
44+
DbxRequestUtil.addBasicAuthHeader(headers, appInfo.getKey(), appInfo.getSecret());
4745

4846
return DbxRequestUtil.doPostNoAuth(
4947
requestConfig,

0 commit comments

Comments
 (0)