Mpaa has uploaded a new change for review.
https://gerrit.wikimedia.org/r/260230
Change subject: OAuth: support Site login with (username, token)
......................................................................
OAuth: support Site login with (username, token)
OAuth token can now be provided when creating Site instance, in order to
use OAuth without necessarily store tokens in config files.
This is useful when using pywikibot as a library to login a user at run
time, once oauth token data have been fetched.
Change-Id: If192745e05bf77920e0fe6a5f3b4361077322734
---
M pywikibot/__init__.py
M pywikibot/comms/http.py
M pywikibot/comms/threadedhttp.py
M pywikibot/site.py
M tests/utils.py
5 files changed, 65 insertions(+), 42 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/pywikibot/core
refs/changes/30/260230/1
diff --git a/pywikibot/__init__.py b/pywikibot/__init__.py
index 343f1bb..c3f67f0 100644
--- a/pywikibot/__init__.py
+++ b/pywikibot/__init__.py
@@ -15,6 +15,7 @@
import datetime
import math
import re
+import requests
import sys
import threading
@@ -540,11 +541,33 @@
return cls(amount, wb['unit'], error)
+def get_authentication(uri):
+ """
+ Retrieve authentication token from config file.
+
+ @param uri: the URI to access
+ @type uri: str
+ @return: authentication token
+ @rtype: None or tuple of two str
+ """
+ parsed_uri = requests.utils.urlparse(uri)
+ netloc_parts = parsed_uri.netloc.split('.')
+ netlocs = [parsed_uri.netloc] + ['.'.join(['*'] + netloc_parts[i + 1:])
+ for i in range(len(netloc_parts))]
+ for path in netlocs:
+ if path in config.authenticate:
+ if len(config.authenticate[path]) in [2, 4]:
+ return config.authenticate[path]
+ else:
+ warn('Invalid authentication tokens for %s '
+ 'set in `config.authenticate`' % path)
+ return None
+
_sites = {}
_url_cache = {} # The code/fam pair for each URL
-def Site(code=None, fam=None, user=None, sysop=None, interface=None, url=None):
+def Site(code=None, fam=None, user=None, sysop=None, interface=None, url=None,
oauth=None):
"""A factory method to obtain a Site object.
Site objects are cached and reused by this method.
@@ -556,7 +579,8 @@
@type code: string
@param fam: family name or object (override config.family)
@type fam: string or Family
- @param user: bot user name to use on this site (override config.usernames)
+ @param user: bot user name to use on this site (override config.usernames);
+ if oauth is used, it shall contain the expected username
@type user: unicode
@param sysop: sysop user to use on this site (override config.sysopnames)
@type sysop: unicode
@@ -566,6 +590,10 @@
@param url: Instead of code and fam, does try to get a Site based on the
URL. Still requires that the family supporting that URL exists.
@type url: string
+ @param oauth: a tuple or list of the format:
+ (consumer_key, consumer_secret, access_key, acess_secret)
+ it overrides config.authenticate
+ @type oauth: tuple or list
"""
# Either code and fam or only url
if url and (code or fam):
@@ -619,6 +647,13 @@
sysop = sysop or config.sysopnames[family_name].get(code) \
or config.sysopnames[family_name].get('*')
+ if oauth is None:
+ # try to get auth token from config file, if not explicitly passed.
+ try:
+ oauth = get_authentication(fam.base_url(code, ''))
+ except KeyError: # code not in family.langs
+ pass
+
if not isinstance(interface, type):
# If it isnt a class, assume it is a string
try:
@@ -631,9 +666,9 @@
warning('Site called with interface=%s' % interface.__name__)
user = normalize_username(user)
- key = '%s:%s:%s:%s' % (interface.__name__, fam, code, user)
+ key = '%s:%s:%s:%s:%s' % (interface.__name__, fam, code, user, oauth)
if key not in _sites or not isinstance(_sites[key], interface):
- _sites[key] = interface(code=code, fam=fam, user=user, sysop=sysop)
+ _sites[key] = interface(code=code, fam=fam, user=user, sysop=sysop,
oauth=oauth)
debug(u"Instantiated %s object '%s'"
% (interface.__name__, _sites[key]), _logger)
diff --git a/pywikibot/comms/http.py b/pywikibot/comms/http.py
index 8db7213..4e3bc4d 100644
--- a/pywikibot/comms/http.py
+++ b/pywikibot/comms/http.py
@@ -244,31 +244,8 @@
headers['user-agent'] = user_agent(site, format_string)
- r = fetch(baseuri, method, body, headers, **kwargs)
+ r = fetch(baseuri, method, body, headers, auth=site._oauth, **kwargs)
return r.content
-
-
-def get_authentication(uri):
- """
- Retrieve authentication token.
-
- @param uri: the URI to access
- @type uri: str
- @return: authentication token
- @rtype: None or tuple of two str
- """
- parsed_uri = requests.utils.urlparse(uri)
- netloc_parts = parsed_uri.netloc.split('.')
- netlocs = [parsed_uri.netloc] + ['.'.join(['*'] + netloc_parts[i + 1:])
- for i in range(len(netloc_parts))]
- for path in netlocs:
- if path in config.authenticate:
- if len(config.authenticate[path]) in [2, 4]:
- return config.authenticate[path]
- else:
- warn('Invalid authentication tokens for %s '
- 'set in `config.authenticate`' % path)
- return None
def _http_process(session, http_request):
@@ -276,9 +253,9 @@
uri = http_request.uri
body = http_request.body
headers = http_request.headers
+ auth = http_request.auth
if PY2 and headers:
headers = dict((key, str(value)) for key, value in headers.items())
- auth = get_authentication(uri)
if auth is not None and len(auth) == 4:
if isinstance(requests_oauthlib, ImportError):
warn('%s' % requests_oauthlib, ImportWarning)
@@ -294,6 +271,8 @@
# Note that the connections are pooled which mean that a future
# HTTPS request can succeed even if the certificate is invalid and
# verify=True, when a request with verify=False happened before
+ if auth is not None:
+ session.cookies = requests.cookies.RequestsCookieJar(policy=None)
response = session.request(method, uri, data=body, headers=headers,
auth=auth, timeout=timeout,
verify=not ignore_validation)
diff --git a/pywikibot/comms/threadedhttp.py b/pywikibot/comms/threadedhttp.py
index 5075663..891c7ed 100644
--- a/pywikibot/comms/threadedhttp.py
+++ b/pywikibot/comms/threadedhttp.py
@@ -52,6 +52,7 @@
self.charset = None
self.callbacks = callbacks
+ self.auth = kwargs.pop('auth', None)
self.args = [uri, method, body, headers]
self.kwargs = kwargs
diff --git a/pywikibot/site.py b/pywikibot/site.py
index 0bbf596..1077c5f 100644
--- a/pywikibot/site.py
+++ b/pywikibot/site.py
@@ -34,7 +34,6 @@
import pywikibot
import pywikibot.family
-from pywikibot.comms.http import get_authentication
from pywikibot.data import api
from pywikibot.echo import Notification
from pywikibot.exceptions import (
@@ -705,7 +704,7 @@
"""Site methods that are independent of the communication interface."""
- def __init__(self, code, fam=None, user=None, sysop=None):
+ def __init__(self, code, fam=None, user=None, sysop=None, oauth=None):
"""
Constructor.
@@ -713,10 +712,14 @@
@type code: str
@param fam: wiki family name (optional)
@type fam: str or Family
- @param user: bot user name (optional)
+ @param user: bot user name (optional); if oauth is used, it shall
+ contain the expected username
@type user: str
@param sysop: sysop account user name (optional)
@type sysop: str
+ @param oauth: a tuple or list of the format:
+ (consumer_key, consumer_secret, access_key, acess_secret)
+ @type oauth: tuple or list
"""
if code.lower() != code:
@@ -1784,9 +1787,9 @@
"""Site removed from a family."""
- def __init__(self, code, fam, user=None, sysop=None):
+ def __init__(self, code, fam, user=None, sysop=None, oauth=None):
"""Constructor."""
- super(RemovedSite, self).__init__(code, fam, user, sysop)
+ super(RemovedSite, self).__init__(code, fam, user, sysop, oauth)
class NonMWAPISite(BaseSite):
@@ -1816,15 +1819,21 @@
"""
- def __init__(self, code, fam=None, user=None, sysop=None):
+ def __init__(self, code, fam=None, user=None, sysop=None, oauth=None):
"""Constructor."""
- BaseSite.__init__(self, code, fam, user, sysop)
+ super(APISite, self).__init__(code, fam, user, sysop, oauth=oauth)
self._msgcache = {}
self._loginstatus = LoginStatus.NOT_ATTEMPTED
self._siteinfo = Siteinfo(self)
self._paraminfo = api.ParamInfo(self)
self._interwikimap = _InterwikiMap(self)
self.tokens = TokenWallet(self)
+ self._oauth = oauth
+ if oauth is not None:
+ if len(oauth) != 4:
+ raise TypeError('Site %s: oauth token has incorrect format' %
self)
+ if user is None:
+ raise ValueError('Site %s: user expected when using OAuth' %
self)
def __getstate__(self):
"""Remove TokenWallet before pickling, for security reasons."""
@@ -1996,8 +2005,7 @@
@rtype: bool
"""
- auth_token = get_authentication(self.base_url(''))
- return auth_token is not None and len(auth_token) == 4
+ return self._oauth is not None
def login(self, sysop=False):
"""Log the user in if not already logged in."""
@@ -2026,8 +2034,8 @@
self._loginstatus = LoginStatus.IN_PROGRESS
try:
self.getuserinfo(force=True)
- if self.userinfo['name'] == self._username[sysop] and \
- self.logged_in(sysop):
+ if (self.userinfo['name'] == self._username[sysop] and
+ self.logged_in(sysop)):
return
except api.APIError: # May occur if you are not logged in (no API
read permissions).
pass
diff --git a/tests/utils.py b/tests/utils.py
index ab1f946..add2776 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -397,9 +397,9 @@
_loginstatus = pywikibot.site.LoginStatus.NOT_ATTEMPTED
- def __init__(self, code, fam, user, sysop):
+ def __init__(self, code, fam, user, sysop, oauth=None):
"""Constructor."""
- super(DrySite, self).__init__(code, fam, user, sysop)
+ super(DrySite, self).__init__(code, fam, user, sysop, oauth=None)
self._userinfo = pywikibot.tools.EMPTY_DEFAULT
self._paraminfo = DryParamInfo()
self._siteinfo = DummySiteinfo({})
--
To view, visit https://gerrit.wikimedia.org/r/260230
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: If192745e05bf77920e0fe6a5f3b4361077322734
Gerrit-PatchSet: 1
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: Mpaa <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits