Merge remote-tracking branch 'fix/robust_proxy' into Develop

# Conflicts:
#	cps/kobo.py
pull/1228/head
Ozzieisaacs 5 years ago
commit 27a18d60a7

@ -75,24 +75,34 @@ CONNECTION_SPECIFIC_HEADERS = [
def get_kobo_activated(): def get_kobo_activated():
return config.config_kobo_sync return config.config_kobo_sync
def redirect_or_proxy_request(proxy=False):
if config.config_kobo_proxy or proxy == True: def make_request_to_kobo_store(sync_token=None):
if request.method == "GET":
return redirect(get_store_url_for_current_request(), 307)
if request.method == "DELETE":
log.info('Delete Book')
return make_response(jsonify({}))
else:
# The Kobo device turns other request types into GET requests on redirects, so we instead proxy to the Kobo store ourselves.
outgoing_headers = Headers(request.headers) outgoing_headers = Headers(request.headers)
outgoing_headers.remove("Host") outgoing_headers.remove("Host")
if sync_token:
sync_token.set_kobo_store_header(outgoing_headers)
store_response = requests.request( store_response = requests.request(
method=request.method, method=request.method,
url=get_store_url_for_current_request(), url=get_store_url_for_current_request(),
headers=outgoing_headers, headers=outgoing_headers,
data=request.get_data(), data=request.get_data(),
allow_redirects=False, allow_redirects=False,
timeout=(2, 10)
) )
return store_response
def redirect_or_proxy_request():
if config.config_kobo_proxy:
if request.method == "GET":
return redirect(get_store_url_for_current_request(), 307)
if request.method == "DELETE":
log.info('Delete Book')
return make_response(jsonify({}))
else:
# The Kobo device turns other request types into GET requests on redirects, so we instead proxy to the Kobo store ourselves.
store_response = make_request_to_kobo_store()
response_headers = store_response.headers response_headers = store_response.headers
for header_key in CONNECTION_SPECIFIC_HEADERS: for header_key in CONNECTION_SPECIFIC_HEADERS:
@ -104,6 +114,7 @@ def redirect_or_proxy_request(proxy=False):
else: else:
return make_response(jsonify({})) return make_response(jsonify({}))
@kobo.route("/v1/library/sync") @kobo.route("/v1/library/sync")
@requires_kobo_auth @requires_kobo_auth
@download_required @download_required
@ -162,35 +173,24 @@ def HandleSyncRequest():
def generate_sync_response(request, sync_token, entitlements): def generate_sync_response(request, sync_token, entitlements):
# We first merge in sync results from the official Kobo store. extra_headers = {}
outgoing_headers = Headers(request.headers) if config.config_kobo_proxy:
outgoing_headers.remove("Host") # Merge in sync results from the official Kobo store.
sync_token.set_kobo_store_header(outgoing_headers) try:
store_response = requests.request( store_response = make_request_to_kobo_store(sync_token)
method=request.method,
url=get_store_url_for_current_request(),
headers=outgoing_headers,
data=request.get_data(),
)
store_entitlements = store_response.json() store_entitlements = store_response.json()
entitlements += store_entitlements entitlements += store_entitlements
sync_token.merge_from_store_response(store_response) sync_token.merge_from_store_response(store_response)
extra_headers["x-kobo-sync"] = store_response.headers.get("x-kobo-sync")
extra_headers["x-kobo-sync-mode"] = store_response.headers.get("x-kobo-sync-mode")
extra_headers["x-kobo-recent-reads"] = store_response.headers.get("x-kobo-recent-reads")
response = make_response(jsonify(entitlements)) except Exception as e:
log.error("Failed to receive or parse response from Kobo's sync endpoint: " + str(e))
sync_token.to_headers(extra_headers)
sync_token.to_headers(response.headers) response = make_response(jsonify(entitlements), extra_headers)
try:
# These headers could probably use some more investigation.
response.headers["x-kobo-sync"] = store_response.headers["x-kobo-sync"]
response.headers["x-kobo-sync-mode"] = store_response.headers[
"x-kobo-sync-mode"
]
response.headers["x-kobo-recent-reads"] = store_response.headers[
"x-kobo-recent-reads"
]
except KeyError:
pass
return response return response
@ -379,6 +379,7 @@ def HandleUnimplementedRequest(dummy=None, book_uuid=None, shelf_name=None, tag_
log.debug("Alternative Request received:") log.debug("Alternative Request received:")
return redirect_or_proxy_request() return redirect_or_proxy_request()
# TODO: Implement the following routes # TODO: Implement the following routes
@kobo.route("/v1/user/loyalty/<dummy>", methods=["GET", "POST"]) @kobo.route("/v1/user/loyalty/<dummy>", methods=["GET", "POST"])
@kobo.route("/v1/user/profile", methods=["GET", "POST"]) @kobo.route("/v1/user/profile", methods=["GET", "POST"])
@ -386,9 +387,21 @@ def HandleUnimplementedRequest(dummy=None, book_uuid=None, shelf_name=None, tag_
@kobo.route("/v1/user/recommendations", methods=["GET", "POST"]) @kobo.route("/v1/user/recommendations", methods=["GET", "POST"])
@kobo.route("/v1/analytics/<dummy>", methods=["GET", "POST"]) @kobo.route("/v1/analytics/<dummy>", methods=["GET", "POST"])
def HandleUserRequest(dummy=None): def HandleUserRequest(dummy=None):
log.debug("Unimplemented Request received: %s", request.base_url) log.debug("Unimplemented User Request received: %s", request.base_url)
return redirect_or_proxy_request()
@kobo.route("/v1/products/<dummy>/prices", methods=["GET", "POST"])
@kobo.route("/v1/products/<dummy>/recommendations", methods=["GET", "POST"])
@kobo.route("/v1/products/<dummy>/nextread", methods=["GET", "POST"])
@kobo.route("/v1/products/<dummy>/reviews", methods=["GET", "POST"])
@kobo.route("/v1/products/books/<dummy>", methods=["GET", "POST"])
@kobo.route("/v1/products/dailydeal", methods=["GET", "POST"])
def HandleProductsRequest(dummy=None):
log.debug("Unimplemented Products Request received: %s", request.base_url)
return redirect_or_proxy_request() return redirect_or_proxy_request()
@kobo.app_errorhandler(404) @kobo.app_errorhandler(404)
def handle_404(err): def handle_404(err):
# This handler acts as a catch-all for endpoints that we don't have an interest in # This handler acts as a catch-all for endpoints that we don't have an interest in
@ -397,11 +410,9 @@ def handle_404(err):
return redirect_or_proxy_request() return redirect_or_proxy_request()
@kobo.route("/v1/auth/device", methods=["POST"]) def make_calibre_web_auth_response():
@requires_kobo_auth # As described in kobo_auth.py, CalibreWeb doesn't make use practical use of this auth/device API call for
def HandleAuthRequest(): # authentation (nor for authorization). We return a dummy response just to keep the device happy.
# Missing feature: Authentication :)
log.debug('Kobo Auth request')
content = request.get_json() content = request.get_json()
AccessToken = base64.b64encode(os.urandom(24)).decode('utf-8') AccessToken = base64.b64encode(os.urandom(24)).decode('utf-8')
RefreshToken = base64.b64encode(os.urandom(24)).decode('utf-8') RefreshToken = base64.b64encode(os.urandom(24)).decode('utf-8')
@ -419,12 +430,32 @@ def HandleAuthRequest():
} }
) )
) )
@kobo.route("/v1/auth/device", methods=["POST"])
@requires_kobo_auth
def HandleAuthRequest():
log.debug('Kobo Auth request')
if config.config_kobo_proxy:
try:
return redirect_or_proxy_request()
except:
log.error("Failed to receive or parse response from Kobo's auth endpoint. Falling back to un-proxied mode.")
return make_calibre_web_auth_response()
def make_calibre_web_init_response(calibre_web_url):
resources = NATIVE_KOBO_RESOURCES(calibre_web_url)
response = make_response(jsonify({"Resources": resources}))
response.headers["x-kobo-apitoken"] = "e30="
return response return response
@kobo.route("/v1/initialization") @kobo.route("/v1/initialization")
@requires_kobo_auth @requires_kobo_auth
def HandleInitRequest(): def HandleInitRequest():
log.info('Init')
if not current_app.wsgi_app.is_proxied: if not current_app.wsgi_app.is_proxied:
log.debug('Kobo: Received unproxied request, changed request port to server port') log.debug('Kobo: Received unproxied request, changed request port to server port')
calibre_web_url = "{url_scheme}://{url_base}:{url_port}".format( calibre_web_url = "{url_scheme}://{url_base}:{url_port}".format(
@ -434,15 +465,10 @@ def HandleInitRequest():
) )
else: else:
calibre_web_url = url_for("web.index", _external=True).strip("/") calibre_web_url = url_for("web.index", _external=True).strip("/")
if config.config_kobo_proxy: if config.config_kobo_proxy:
outgoing_headers = Headers(request.headers) try:
outgoing_headers.remove("Host") store_response = make_request_to_kobo_store()
store_response = requests.request(
method=request.method,
url=get_store_url_for_current_request(),
headers=outgoing_headers,
data=request.get_data(),
)
store_response_json = store_response.json() store_response_json = store_response.json()
if "Resources" in store_response_json: if "Resources" in store_response_json:
@ -457,11 +483,11 @@ def HandleInitRequest():
book_uuid="{ImageId}")) book_uuid="{ImageId}"))
return make_response(store_response_json, store_response.status_code) return make_response(store_response_json, store_response.status_code)
else: except:
resources = NATIVE_KOBO_RESOURCES(calibre_web_url) log.error("Failed to receive or parse response from Kobo's init endpoint. Falling back to un-proxied mode.")
response = make_response(jsonify({"Resources": resources}))
response.headers["x-kobo-apitoken"] = "e30=" return make_calibre_web_init_response(calibre_web_url)
return response
def NATIVE_KOBO_RESOURCES(calibre_web_url): def NATIVE_KOBO_RESOURCES(calibre_web_url):
return { return {

Loading…
Cancel
Save