diff --git a/cps/helper.py b/cps/helper.py index 51e9456e..e21edc2e 100644 --- a/cps/helper.py +++ b/cps/helper.py @@ -445,7 +445,7 @@ def get_book_cover_with_uuid(book_uuid, def get_book_cover_internal(book, use_generic_cover_on_failure): - if book.has_cover: + if book and book.has_cover: if config.config_use_google_drive: try: if not gd.is_gdrive_ready(): diff --git a/cps/kobo.py b/cps/kobo.py index 52ff8f2c..57f23867 100644 --- a/cps/kobo.py +++ b/cps/kobo.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +import base64 import copy import uuid import os @@ -25,8 +26,6 @@ kobo_auth.disable_failed_auth_redirect_for_blueprint(kobo) log = logger.create() -import base64 - def b64encode(data): return base64.b64encode(data) @@ -92,10 +91,12 @@ class SyncToken: if "." in sync_token_header: return SyncToken(raw_kobo_store_token=sync_token_header) - sync_token_json = json.loads( - base64.b64decode(sync_token_header + "=" * (-len(sync_token_header) % 4)) - ) try: + sync_token_json = json.loads( + base64.b64decode( + sync_token_header + "=" * (-len(sync_token_header) % 4) + ) + ) validate(sync_token_json, SyncToken.token_schema) if sync_token_json["version"] < SyncToken.MIN_VERSION: raise ValueError @@ -217,8 +218,10 @@ def get_metadata__v1(book_uuid): def get_download_url_for_book(book): - return "{url_base}/download/{book_id}/kepub".format( - url_base=config.config_server_url, book_id=book.id + return "{url_base}/download/{book_id}/kepub?{auth_token_param}".format( + url_base=config.config_server_url, + book_id=book.id, + auth_token_param=kobo_auth.get_auth_url_param(request), ) diff --git a/cps/kobo_auth.py b/cps/kobo_auth.py index 2fbd230d..7752a0b1 100644 --- a/cps/kobo_auth.py +++ b/cps/kobo_auth.py @@ -50,6 +50,8 @@ from werkzeug.security import check_password_hash from . import logger, ub, lm USER_KEY_HEADER = "x-kobo-userkey" +USER_KEY_URL_PARAM = "kobo_userkey" + log = logger.create() @@ -59,7 +61,7 @@ def disable_failed_auth_redirect_for_blueprint(bp): @lm.request_loader def load_user_from_kobo_request(request): - user_key = request.headers.get(USER_KEY_HEADER) + user_key = get_auth_token_from_request(request) if user_key: for user in ( ub.session.query(ub.User).filter(ub.User.kobo_user_key_hash != "").all() @@ -68,3 +70,20 @@ def load_user_from_kobo_request(request): return user log.info("Received Kobo request without a recognizable UserKey.") return None + + +def get_auth_token_from_request(request): + user_key = request.headers.get(USER_KEY_HEADER) + if not user_key: + user_key = request.args.get(USER_KEY_URL_PARAM) + return user_key + + +def get_auth_url_param(request): + # Some of the API requests emitted by the Kobo device don't set any headers. To + # support those calls, authorization on those endpoints can only rely on URL params. + # Since the raw UserKey in already leaked in headers, it is probably not *that* much + # worse to also leak it over url params. + # Ideally however, we should be generating short-lived tokens that grant limited + # access instead.. + return USER_KEY_URL_PARAM + "=" + get_auth_token_from_request(request)