write OAuth token to cache by default (#616)

This commit is contained in:
Mike Fährmann 2020-05-25 22:19:58 +02:00
parent ddc253cf9a
commit dfcf2a2c91
No known key found for this signature in database
GPG Key ID: 5680CA389D365A88
7 changed files with 70 additions and 24 deletions

View File

@ -910,6 +910,16 @@ Description Controls how a user is directed to an OAuth authorization site.
=========== ===== =========== =====
extractor.oauth.cache
---------------------
=========== =====
Type ``bool``
Default ``true``
Description Store tokens received during OAuth authorizations
in `cache <cache.file_>`__.
=========== =====
extractor.oauth.port extractor.oauth.port
-------------------- --------------------
=========== ===== =========== =====

View File

@ -30,7 +30,6 @@
}, },
"deviantart": "deviantart":
{ {
"refresh-token": null,
"extra": false, "extra": false,
"flat": true, "flat": true,
"folders": false, "folders": false,
@ -51,8 +50,6 @@
}, },
"flickr": "flickr":
{ {
"access-token": null,
"access-token-secret": null,
"videos": true, "videos": true,
"size-max": null "size-max": null
}, },
@ -96,6 +93,7 @@
"oauth": "oauth":
{ {
"browser": true, "browser": true,
"cache": true,
"port": 6414 "port": 6414
}, },
"pixiv": "pixiv":
@ -120,7 +118,6 @@
}, },
"reddit": "reddit":
{ {
"refresh-token": null,
"comments": 0, "comments": 0,
"morecomments": false, "morecomments": false,
"date-min": 0, "date-min": 0,

View File

@ -850,9 +850,12 @@ class DeviantartOAuthAPI():
self.client_secret = extractor.config( self.client_secret = extractor.config(
"client-secret", self.CLIENT_SECRET) "client-secret", self.CLIENT_SECRET)
self.refresh_token = extractor.config("refresh-token") token = extractor.config("refresh-token")
if self.refresh_token == "cache": if token is None or token == "cache":
self.refresh_token = "#" + str(self.client_id) token = "#" + str(self.client_id)
if not _refresh_token_cache(token):
token = None
self.refresh_token_key = token
self.log.debug( self.log.debug(
"Using %s API credentials (client-id %s)", "Using %s API credentials (client-id %s)",
@ -952,18 +955,19 @@ class DeviantartOAuthAPI():
endpoint = "user/profile/" + username endpoint = "user/profile/" + username
return self._call(endpoint, fatal=False) return self._call(endpoint, fatal=False)
def authenticate(self, refresh_token): def authenticate(self, refresh_token_key):
"""Authenticate the application by requesting an access token""" """Authenticate the application by requesting an access token"""
self.headers["Authorization"] = self._authenticate_impl(refresh_token) self.headers["Authorization"] = \
self._authenticate_impl(refresh_token_key)
@cache(maxage=3600, keyarg=1) @cache(maxage=3600, keyarg=1)
def _authenticate_impl(self, refresh_token): def _authenticate_impl(self, refresh_token_key):
"""Actual authenticate implementation""" """Actual authenticate implementation"""
url = "https://www.deviantart.com/oauth2/token" url = "https://www.deviantart.com/oauth2/token"
if refresh_token: if refresh_token_key:
self.log.info("Refreshing private access token") self.log.info("Refreshing private access token")
data = {"grant_type": "refresh_token", data = {"grant_type": "refresh_token",
"refresh_token": _refresh_token_cache(refresh_token)} "refresh_token": _refresh_token_cache(refresh_token_key)}
else: else:
self.log.info("Requesting public access token") self.log.info("Requesting public access token")
data = {"grant_type": "client_credentials"} data = {"grant_type": "client_credentials"}
@ -977,8 +981,9 @@ class DeviantartOAuthAPI():
self.log.debug("Server response: %s", data) self.log.debug("Server response: %s", data)
raise exception.AuthenticationError('"{}" ({})'.format( raise exception.AuthenticationError('"{}" ({})'.format(
data.get("error_description"), data.get("error"))) data.get("error_description"), data.get("error")))
if refresh_token: if refresh_token_key:
_refresh_token_cache.update(refresh_token, data["refresh_token"]) _refresh_token_cache.update(
refresh_token_key, data["refresh_token"])
return "Bearer " + data["access_token"] return "Bearer " + data["access_token"]
def _call(self, endpoint, params=None, fatal=True, public=True): def _call(self, endpoint, params=None, fatal=True, public=True):
@ -988,7 +993,7 @@ class DeviantartOAuthAPI():
if self.delay >= 0: if self.delay >= 0:
time.sleep(2 ** self.delay) time.sleep(2 ** self.delay)
self.authenticate(None if public else self.refresh_token) self.authenticate(None if public else self.refresh_token_key)
response = self.extractor.request( response = self.extractor.request(
url, headers=self.headers, params=params, fatal=None) url, headers=self.headers, params=params, fatal=None)
data = response.json() data = response.json()
@ -1024,7 +1029,7 @@ class DeviantartOAuthAPI():
if extend: if extend:
if public and len(data["results"]) < params["limit"]: if public and len(data["results"]) < params["limit"]:
if self.refresh_token: if self.refresh_token_key:
self.log.debug("Switching to private access token") self.log.debug("Switching to private access token")
public = False public = False
continue continue
@ -1155,9 +1160,11 @@ class DeviantartEclipseAPI():
return text.rextract(page, '\\"id\\":', ',', pos)[0].strip('" ') return text.rextract(page, '\\"id\\":', ',', pos)[0].strip('" ')
@cache(maxage=10*365*24*3600, keyarg=0) @cache(maxage=100*365*24*3600, keyarg=0)
def _refresh_token_cache(original_token, new_token=None): def _refresh_token_cache(token):
return new_token or original_token if token and token[0] == "#":
return None
return token
############################################################################### ###############################################################################

View File

@ -26,6 +26,7 @@ class OAuthBase(Extractor):
def __init__(self, match): def __init__(self, match):
Extractor.__init__(self, match) Extractor.__init__(self, match)
self.client = None self.client = None
self.cache = config.get(("extractor", self.category), "cache", True)
def oauth_config(self, key, default=None): def oauth_config(self, key, default=None):
return config.interpolate( return config.interpolate(
@ -94,6 +95,13 @@ class OAuthBase(Extractor):
token_secret=data["oauth_token_secret"], token_secret=data["oauth_token_secret"],
)) ))
# write to cache
if self.cache:
key = (self.subcategory, self.session.auth.consumer_key)
tokens = (data["oauth_token"], data["oauth_token_secret"])
oauth._token_cache.update(key, tokens)
self.log.info("Writing tokens to cache")
def _oauth2_authorization_code_grant( def _oauth2_authorization_code_grant(
self, client_id, client_secret, auth_url, token_url, self, client_id, client_secret, auth_url, token_url,
scope="read", key="refresh_token", auth=True, scope="read", key="refresh_token", auth=True,
@ -162,7 +170,7 @@ class OAuthBase(Extractor):
)) ))
# write to cache # write to cache
if cache and config.get(("extractor", self.category), "cache"): if self.cache and cache:
cache.update("#" + str(client_id), data[key]) cache.update("#" + str(client_id), data[key])
self.log.info("Writing 'refresh-token' to cache") self.log.info("Writing 'refresh-token' to cache")
@ -223,6 +231,7 @@ class OAuthReddit(OAuthBase):
"https://www.reddit.com/api/v1/authorize", "https://www.reddit.com/api/v1/authorize",
"https://www.reddit.com/api/v1/access_token", "https://www.reddit.com/api/v1/access_token",
scope="read history", scope="read history",
cache=reddit._refresh_token_cache,
) )

View File

@ -222,7 +222,6 @@ class RedditAPI():
self.extractor = extractor self.extractor = extractor
self.comments = text.parse_int(extractor.config("comments", 0)) self.comments = text.parse_int(extractor.config("comments", 0))
self.morecomments = extractor.config("morecomments", False) self.morecomments = extractor.config("morecomments", False)
self.refresh_token = extractor.config("refresh-token")
self.log = extractor.log self.log = extractor.log
client_id = extractor.config("client-id", self.CLIENT_ID) client_id = extractor.config("client-id", self.CLIENT_ID)
@ -236,6 +235,13 @@ class RedditAPI():
self.client_id = client_id self.client_id = client_id
self.headers = {"User-Agent": user_agent} self.headers = {"User-Agent": user_agent}
token = extractor.config("refresh-token")
if token is None or token == "cache":
key = "#" + self.client_id
self.refresh_token = _refresh_token_cache(key)
else:
self.refresh_token = token
def submission(self, submission_id): def submission(self, submission_id):
"""Fetch the (submission, comments)=-tuple for a submission id""" """Fetch the (submission, comments)=-tuple for a submission id"""
endpoint = "/comments/" + submission_id + "/.json" endpoint = "/comments/" + submission_id + "/.json"
@ -382,3 +388,10 @@ class RedditAPI():
@staticmethod @staticmethod
def _decode(sid): def _decode(sid):
return util.bdecode(sid, "0123456789abcdefghijklmnopqrstuvwxyz") return util.bdecode(sid, "0123456789abcdefghijklmnopqrstuvwxyz")
@cache(maxage=100*365*24*3600, keyarg=0)
def _refresh_token_cache(token):
if token and token[0] == "#":
return None
return token

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2018-2019 Mike Fährmann # Copyright 2018-2020 Mike Fährmann
# #
# This program is free software; you can redistribute it and/or modify # This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as # it under the terms of the GNU General Public License version 2 as
@ -20,6 +20,7 @@ import requests
import requests.auth import requests.auth
from . import text from . import text
from .cache import cache
def nonce(size, alphabet=string.ascii_letters): def nonce(size, alphabet=string.ascii_letters):
@ -117,6 +118,10 @@ class OAuth1API():
token_secret = extractor.config("access-token-secret") token_secret = extractor.config("access-token-secret")
key_type = "default" if api_key == self.API_KEY else "custom" key_type = "default" if api_key == self.API_KEY else "custom"
if token is None or token == "cache":
key = (extractor.category, api_key)
token, token_secret = _token_cache(key)
if api_key and api_secret and token and token_secret: if api_key and api_secret and token and token_secret:
self.log.debug("Using %s OAuth1.0 authentication", key_type) self.log.debug("Using %s OAuth1.0 authentication", key_type)
self.session = OAuth1Session( self.session = OAuth1Session(
@ -131,3 +136,8 @@ class OAuth1API():
kwargs["fatal"] = None kwargs["fatal"] = None
kwargs["session"] = self.session kwargs["session"] = self.session
return self.extractor.request(url, **kwargs) return self.extractor.request(url, **kwargs)
@cache(maxage=100*365*24*3600, keyarg=0)
def _token_cache(key):
return None, None

View File

@ -22,8 +22,8 @@ config.set(("cache",), "file", dbpath)
from gallery_dl import cache # noqa E402 from gallery_dl import cache # noqa E402
def tearDownModule(): # def tearDownModule():
util.remove_file(dbpath) # util.remove_file(dbpath)
class TestCache(unittest.TestCase): class TestCache(unittest.TestCase):