pax_global_header00006660000000000000000000000064140413051510014504gustar00rootroot0000000000000052 comment=5fbaac03f808a99c7822b931f74ca402d21af1fc mwoauth-0.3.7/000077500000000000000000000000001404130515100131775ustar00rootroot00000000000000mwoauth-0.3.7/LICENSE000066400000000000000000000021141404130515100142020ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2014 Aaron Halfaker / Filippo Valsorda Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. mwoauth-0.3.7/MANIFEST.in000066400000000000000000000000541404130515100147340ustar00rootroot00000000000000include LICENSE README.rst requirements.txt mwoauth-0.3.7/PKG-INFO000066400000000000000000000045311404130515100142770ustar00rootroot00000000000000Metadata-Version: 2.1 Name: mwoauth Version: 0.3.7 Summary: A generic MediaWiki OAuth handshake helper. Home-page: https://github.com/mediawiki-utilities/python-mwoauth Author: Aaron Halfaker / Filippo Valsorda Author-email: aaron.halfaker@gmail.com License: MIT Description: MediaWiki OAuth Library ======================= ``mwoauth`` is an open licensed (MIT) library designed to provide a simple means to performing an OAuth handshake with a MediaWiki installation with the `OAuth Extension `_ installed. **Compatible with python 2.7 and 3.x** **Install with pip:** ``pip install mwoauth`` **Documentation:** http://pythonhosted.org/mwoauth Usage ===== .. code-block:: python from mwoauth import ConsumerToken, Handshaker from six.moves import input # For compatibility between python 2 and 3 # Consruct a "consumer" from the key/secret provided by MediaWiki import config consumer_token = ConsumerToken(config.consumer_key, config.consumer_secret) # Construct handshaker with wiki URI and consumer handshaker = Handshaker("https://en.wikipedia.org/w/index.php", consumer_token) # Step 1: Initialize -- ask MediaWiki for a temporary key/secret for user redirect, request_token = handshaker.initiate() # Step 2: Authorize -- send user to MediaWiki to confirm authorization print("Point your browser to: %s" % redirect) # response_qs = input("Response query string: ") # Step 3: Complete -- obtain authorized key/secret for "resource owner" access_token = handshaker.complete(request_token, response_qs) print(str(access_token)) # Step 4: Identify -- (optional) get identifying information about the user identity = handshaker.identify(access_token) print("Identified as {username}.".format(**identity)) Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Topic :: Security Classifier: License :: OSI Approved :: MIT License Classifier: Topic :: Software Development :: Libraries :: Python Modules Provides-Extra: flask mwoauth-0.3.7/README.rst000066400000000000000000000030071404130515100146660ustar00rootroot00000000000000MediaWiki OAuth Library ======================= ``mwoauth`` is an open licensed (MIT) library designed to provide a simple means to performing an OAuth handshake with a MediaWiki installation with the `OAuth Extension `_ installed. **Compatible with python 2.7 and 3.x** **Install with pip:** ``pip install mwoauth`` **Documentation:** http://pythonhosted.org/mwoauth Usage ===== .. code-block:: python from mwoauth import ConsumerToken, Handshaker from six.moves import input # For compatibility between python 2 and 3 # Consruct a "consumer" from the key/secret provided by MediaWiki import config consumer_token = ConsumerToken(config.consumer_key, config.consumer_secret) # Construct handshaker with wiki URI and consumer handshaker = Handshaker("https://en.wikipedia.org/w/index.php", consumer_token) # Step 1: Initialize -- ask MediaWiki for a temporary key/secret for user redirect, request_token = handshaker.initiate() # Step 2: Authorize -- send user to MediaWiki to confirm authorization print("Point your browser to: %s" % redirect) # response_qs = input("Response query string: ") # Step 3: Complete -- obtain authorized key/secret for "resource owner" access_token = handshaker.complete(request_token, response_qs) print(str(access_token)) # Step 4: Identify -- (optional) get identifying information about the user identity = handshaker.identify(access_token) print("Identified as {username}.".format(**identity)) mwoauth-0.3.7/mwoauth.egg-info/000077500000000000000000000000001404130515100163555ustar00rootroot00000000000000mwoauth-0.3.7/mwoauth.egg-info/PKG-INFO000066400000000000000000000045311404130515100174550ustar00rootroot00000000000000Metadata-Version: 2.1 Name: mwoauth Version: 0.3.7 Summary: A generic MediaWiki OAuth handshake helper. Home-page: https://github.com/mediawiki-utilities/python-mwoauth Author: Aaron Halfaker / Filippo Valsorda Author-email: aaron.halfaker@gmail.com License: MIT Description: MediaWiki OAuth Library ======================= ``mwoauth`` is an open licensed (MIT) library designed to provide a simple means to performing an OAuth handshake with a MediaWiki installation with the `OAuth Extension `_ installed. **Compatible with python 2.7 and 3.x** **Install with pip:** ``pip install mwoauth`` **Documentation:** http://pythonhosted.org/mwoauth Usage ===== .. code-block:: python from mwoauth import ConsumerToken, Handshaker from six.moves import input # For compatibility between python 2 and 3 # Consruct a "consumer" from the key/secret provided by MediaWiki import config consumer_token = ConsumerToken(config.consumer_key, config.consumer_secret) # Construct handshaker with wiki URI and consumer handshaker = Handshaker("https://en.wikipedia.org/w/index.php", consumer_token) # Step 1: Initialize -- ask MediaWiki for a temporary key/secret for user redirect, request_token = handshaker.initiate() # Step 2: Authorize -- send user to MediaWiki to confirm authorization print("Point your browser to: %s" % redirect) # response_qs = input("Response query string: ") # Step 3: Complete -- obtain authorized key/secret for "resource owner" access_token = handshaker.complete(request_token, response_qs) print(str(access_token)) # Step 4: Identify -- (optional) get identifying information about the user identity = handshaker.identify(access_token) print("Identified as {username}.".format(**identity)) Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Topic :: Security Classifier: License :: OSI Approved :: MIT License Classifier: Topic :: Software Development :: Libraries :: Python Modules Provides-Extra: flask mwoauth-0.3.7/mwoauth.egg-info/SOURCES.txt000066400000000000000000000006571404130515100202510ustar00rootroot00000000000000LICENSE MANIFEST.in README.rst requirements.txt setup.cfg setup.py mwoauth/__init__.py mwoauth/about.py mwoauth/defaults.py mwoauth/errors.py mwoauth/flask.py mwoauth/functions.py mwoauth/handshaker.py mwoauth/tokens.py mwoauth.egg-info/PKG-INFO mwoauth.egg-info/SOURCES.txt mwoauth.egg-info/dependency_links.txt mwoauth.egg-info/requires.txt mwoauth.egg-info/top_level.txt mwoauth/tests/__init__.py mwoauth/tests/test_functions.pymwoauth-0.3.7/mwoauth.egg-info/dependency_links.txt000066400000000000000000000000011404130515100224230ustar00rootroot00000000000000 mwoauth-0.3.7/mwoauth.egg-info/requires.txt000066400000000000000000000001131404130515100207500ustar00rootroot00000000000000PyJWT<2.0.0,>=1.0.1 oauthlib requests requests-oauthlib six [flask] flask mwoauth-0.3.7/mwoauth.egg-info/top_level.txt000066400000000000000000000000101404130515100210760ustar00rootroot00000000000000mwoauth mwoauth-0.3.7/mwoauth/000077500000000000000000000000001404130515100146635ustar00rootroot00000000000000mwoauth-0.3.7/mwoauth/__init__.py000066400000000000000000000012311404130515100167710ustar00rootroot00000000000000"""Provides a collection of utilities for easily working with MediaWiki's OAuth1.0a implementation.""" from .handshaker import Handshaker from .tokens import AccessToken, ConsumerToken, RequestToken from .functions import initiate, complete, identify from .errors import OAuthException from .about import (__name__, __version__, __author__, __author_email__, __description__, __license__, __url__) __all__ = [ ConsumerToken, RequestToken, AccessToken, initiate, complete, identify, Handshaker, OAuthException, __name__, __version__, __author__, __author_email__, __description__, __license__, __url__ ] mwoauth-0.3.7/mwoauth/about.py000066400000000000000000000006131404130515100163470ustar00rootroot00000000000000__name__ = "mwoauth" __version__ = "0.3.7" __author__ = "Aaron Halfaker / Filippo Valsorda" __author_email__ = "aaron.halfaker@gmail.com" __description__ = "A generic MediaWiki OAuth handshake helper." __license__ = "MIT" __url__ = "https://github.com/mediawiki-utilities/python-mwoauth" all = [__name__, __version__, __author__, __author_email__, __description__, __license__, __url__] mwoauth-0.3.7/mwoauth/defaults.py000066400000000000000000000000611404130515100170410ustar00rootroot00000000000000USER_AGENT = "python-mwoauth default user agent" mwoauth-0.3.7/mwoauth/errors.py000066400000000000000000000000521404130515100165460ustar00rootroot00000000000000class OAuthException(Exception): pass mwoauth-0.3.7/mwoauth/flask.py000066400000000000000000000233261404130515100163430ustar00rootroot00000000000000""" .. autoclass:: mwoauth.flask.MWOAuth :members: :member-order: bysource .. autofunction:: mwoauth.flask.authorized """ import logging from functools import wraps import flask from requests_oauthlib import OAuth1 from six.moves.urllib.parse import urljoin from .errors import OAuthException from .handshaker import Handshaker from .tokens import AccessToken, RequestToken logger = logging.getLogger(__name__) class MWOAuth: """ Implements a basic MediaWiki OAuth pattern with a set of routes - /mwoauth/initiate -- Starts an OAuth handshake - /mwoauth/callback -- Completes an OAuth handshake - /mwoauth/identify -- Gets identity information about an authorized user - /mwoauth/logout -- Discards OAuth tokens and user identity There's also a convenient decorator provided :func:`~mwoauth.flask.MWOAuth.authorized`. When applied to a routing function, this decorator will redirect non-authorized users to /mwoauth/initiate with a "?next=" that will return them to the originating route once authorization is completed. :Example: .. code-block:: python from flask import Flask import mwoauth import mwoauth.flask app = Flask(__name__) @app.route("/") def index(): return "Hello world" flask_mwoauth = mwoauth.flask.MWOAuth( "https://en.wikipedia.org", mwoauth.ConsumerToken("...", "...")) app.register_blueprint(flask_mwoauth.bp) @app.route("/my_settings/") @mwoauth.flask.authorized def my_settings(): return flask_mwoauth.identity() :Parameters: host : str The host name (including protocol) of the MediaWiki wiki to use for the OAuth handshake. consumer_token : :class:`mwoauth.ConsumerToken` The consumer token information user_agent : str A User-Agent header to include with requests. A warning will be logged is this is not set. default_next : str Where should the user be redirected after an OAuth handshake when no '?next=' param is provided render_logout : func A method that renders the logout page seen at /mwoauth/logout render_identify : func A method that renders the identify page seen at /mwoauth/identify. Takes one positional argument -- the `identity` dictionary returned by MediaWiki. render_error : func A method that renders an error. Takes two arguements: * message : str (The error message) * status : int (The https status number) **kwargs : dict Parameters to be passed to :class:`flask.Blueprint` during its construction. """ def __init__(self, host, consumer_token, user_agent=None, default_next="index", render_logout=None, render_indentify=None, render_error=None, **kwargs): self.bp = flask.Blueprint('mwoauth', __name__, **kwargs) self.host = host self.user_agent = user_agent self.consumer_token = consumer_token self.handshaker = None self.default_next = default_next self.render_logout = render_logout or generic_logout self.render_identify = render_indentify or generic_identify self.render_error = render_error or generic_error @self.bp.route("/mwoauth/initiate/") def mwoauth_initiate(): """Start an OAuth handshake.""" mw_authorizer_url, request_token = self._handshaker().initiate() rt_session_key = _str(request_token.key) + "_request_token" next_session_key = _str(request_token.key) + "_next" # Ensures that Flask's default session storage strategy will work flask.session[rt_session_key] = \ dict(zip(request_token._fields, request_token)) if 'next' in flask.request.args: flask.session[next_session_key] = \ flask.request.args.get('next') return flask.redirect(mw_authorizer_url) @self.bp.route("/mwoauth/callback/") def mwoauth_callback(): """Complete the oauth handshake.""" # Generate session keys request_token_key = _str( flask.request.args.get('oauth_token', 'None')) rt_session_key = request_token_key + "_request_token" next_session_key = request_token_key + "_next" # Make sure we're continuing an in-progress handshake if rt_session_key not in flask.session: flask.session.pop(rt_session_key, None) flask.session.pop(next_session_key, None) return self.render_error( "OAuth callback failed. " + "Couldn't find request_token in session. " + "Are cookies disabled?", 403) # Complete the handshake try: access_token = self._handshaker().complete( RequestToken(**flask.session[rt_session_key]), _str(flask.request.query_string)) except OAuthException as e: flask.session.pop(rt_session_key, None) flask.session.pop(next_session_key, None) return self.render_error( "OAuth callback failed. " + str(e), 403) # Store the access token flask.session['mwoauth_access_token'] = \ dict(zip(access_token._fields, access_token)) # Identify the user identity = self._handshaker().identify(access_token) flask.session['mwoauth_identity'] = identity # Redirect to wherever we're supposed to go if next_session_key in flask.session: return flask.redirect( flask.url_for(flask.session[next_session_key])) else: return flask.redirect( flask.url_for(self.default_next)) @self.bp.route("/mwoauth/identify/") @authorized def mwoauth_identify(): """Return user information if authenticated.""" return flask.jsonify(flask.session['mwoauth_identity']) @self.bp.route("/mwoauth/logout/") def mwoauth_logout(): """Delete the local session.""" flask.session.pop('mwoauth_access_token', None) flask.session.pop('mwoauth_identity', None) if 'next' in flask.request.args: return flask.redirect( flask.url_for(flask.request.args.get('next'))) else: return self.render_logout() def _handshaker(self): if not self.handshaker: full_callback = urljoin( flask.request.url_root, flask.url_for("mwoauth.mwoauth_callback")) print(full_callback) self.handshaker = Handshaker( self.host, self.consumer_token, user_agent=self.user_agent, callback=full_callback) return self.handshaker @staticmethod def identify(): return flask.session.get('mwoauth_identity') def mwapi_session(self, *args, **kwargs): """ Create :class:`mwapi.Session` that is authorized for the current user. `args` and `kwargs` are passed directly to :class:`mwapi.Session` """ import mwapi auth1 = self.generate_auth() return mwapi.Session(*args, user_agent=self.user_agent, auth=auth1, **kwargs) def requests_session(self, *args, **kwargs): """ Create :class:`requests.Session` that is authorized for the current user. `args` and `kwargs` are passed directly to :class:`requests.Session` """ import requests auth1 = self.generate_auth() return requests.Session(*args, auth=auth1, **kwargs) def generate_auth(self): if 'mwoauth_access_token' in flask.session: access_token = AccessToken( **flask.session['mwoauth_access_token']) auth1 = OAuth1(self.consumer_token.key, client_secret=self.consumer_token.secret, resource_owner_key=access_token.key, resource_owner_secret=access_token.secret) return auth1 else: raise OAuthException( "Cannot generate auth. User has not authorized.") def authorized(route): """ Wrap a flask route. Ensure that the user has authorized via OAuth or redirect the user to the authorization endpoint with a delayed redirect back to the originating endpoint. """ @wraps(route) def authorized_route(*args, **kwargs): if 'mwoauth_access_token' in flask.session: return route(*args, **kwargs) else: return flask.redirect( flask.url_for('mwoauth.mwoauth_initiate') + "?next=" + flask.request.endpoint) return authorized_route def generic_logout(): return "Logged out" def generic_identify(identity): return flask.jsonify(identity) def generic_error(message, status): return '' + message + '', status def encode_token(token): return dict(zip(token._fields, token)) def _str(val): """Ensure that the val is the default str() type for python2 or 3.""" if str == bytes: if isinstance(val, str): return val else: return str(val) else: if isinstance(val, str): return val else: return str(val, 'ascii') mwoauth-0.3.7/mwoauth/functions.py000066400000000000000000000245521404130515100172550ustar00rootroot00000000000000""" A set of stateless functions that can be used to complete various steps of an OAuth handshake or to identify a MediaWiki user. :Example: .. code-block:: python from mwoauth import ConsumerToken, initiate, complete, identify from six.moves import input # For compatibility between python 2 and 3 # Consruct a "consumer" from the key/secret provided by MediaWiki import config consumer_token = ConsumerToken( config.consumer_key, config.consumer_secret) mw_uri = "https://en.wikipedia.org/w/index.php" # Step 1: Initialize -- ask MediaWiki for a temporary key/secret for # user redirect, request_token = initiate(mw_uri, consumer_token) # Step 2: Authorize -- send user to MediaWiki to confirm authorization print("Point your browser to: %s" % redirect) # response_qs = input("Response query string: ") # Step 3: Complete -- obtain authorized key/secret for "resource owner" access_token = complete( mw_uri, consumer_token, request_token, response_qs) print(str(access_token)) # Step 4: Identify -- (optional) get identifying information about the # user identity = identify(mw_uri, consumer_token, access_token) print("Identified as {username}.".format(**identity)) """ import json import re import time import jwt import requests from requests_oauthlib import OAuth1 from six import PY3, text_type from six.moves.urllib.parse import parse_qs, urlencode, urlparse from . import defaults from .errors import OAuthException from .tokens import AccessToken, RequestToken def force_unicode(val, encoding="unicode-escape"): if type(val) == text_type: return val else: if PY3: return val.decode(encoding, errors="replace") else: return unicode(val, encoding, errors="replace") # noqa def initiate(mw_uri, consumer_token, callback='oob', user_agent=defaults.USER_AGENT): """ Initiate an oauth handshake with MediaWiki. :Parameters: mw_uri : `str` The base URI of the MediaWiki installation. Note that the URI should end in ``"index.php"``. consumer_token : :class:`~mwoauth.ConsumerToken` A token representing you, the consumer. Provided by MediaWiki via ``Special:OAuthConsumerRegistration``. callback : `str` Callback URL. Defaults to 'oob'. :Returns: A `tuple` of two values: * a MediaWiki URL to direct the user to * a :class:`~mwoauth.RequestToken` representing a request for access """ auth = OAuth1(consumer_token.key, client_secret=consumer_token.secret, callback_uri=callback) r = requests.post(url=mw_uri, params={'title': "Special:OAuth/initiate"}, auth=auth, headers={'User-Agent': user_agent}) request_token = process_request_token(r.text) params = {'title': "Special:OAuth/authenticate", 'oauth_token': request_token.key, 'oauth_consumer_key': consumer_token.key} return (mw_uri + "?" + urlencode(params), request_token) def process_request_token(content): if content.startswith("Error: "): raise OAuthException(content[len("Error: "):]) credentials = parse_qs(content) if credentials is None or credentials == {}: raise OAuthException( "Expected x-www-form-urlencoded response from " + "MediaWiki, but got something else: " + "{0}".format(repr(content))) elif 'oauth_token' not in credentials or \ 'oauth_token_secret' not in credentials: raise OAuthException( "MediaWiki response lacks token information: " "{0}".format(repr(credentials))) else: return RequestToken( credentials.get('oauth_token')[0], credentials.get('oauth_token_secret')[0] ) def complete(mw_uri, consumer_token, request_token, response_qs, user_agent=defaults.USER_AGENT): """ Complete an OAuth handshake with MediaWiki by exchanging an :Parameters: mw_uri : `str` The base URI of the MediaWiki installation. Note that the URI should end in ``"index.php"``. consumer_token : :class:`~mwoauth.ConsumerToken` A key/secret pair representing you, the consumer. request_token : :class:`~mwoauth.RequestToken` A temporary token representing the user. Returned by `initiate()`. response_qs : `bytes` The query string of the URL that MediaWiki forwards the user back after authorization. :Returns: An `AccessToken` containing an authorized key/secret pair that can be stored and used by you. """ callback_data = parse_qs(force_unicode(response_qs)) if callback_data is None or callback_data == {}: raise OAuthException( "Expected URL query string, but got " + "something else instead: {0}".format(str(response_qs))) elif 'oauth_token' not in callback_data or \ 'oauth_verifier' not in callback_data: raise OAuthException( "Query string lacks token information: " "{0}".format(repr(callback_data))) # Process the callback_data request_token_key = callback_data.get('oauth_token')[0] verifier = callback_data.get('oauth_verifier')[0] # Check if the query string references the right temp resource owner key if not request_token.key == request_token_key: raise OAuthException( "Unexpect request token key {0!r}, expected {1!r}.".format( request_token_key, request_token.key)) # Construct a new auth with the verifier auth = OAuth1(consumer_token.key, client_secret=consumer_token.secret, resource_owner_key=request_token.key, resource_owner_secret=request_token.secret, verifier=verifier) # Send the verifier and ask for an authorized resource owner key/secret r = requests.post(url=mw_uri, params={'title': "Special:OAuth/token"}, auth=auth, headers={'User-Agent': user_agent}) # Parse response and construct an authorized resource owner credentials = parse_qs(r.text) if credentials is None: raise OAuthException( "Expected x-www-form-urlencoded response, " + "but got some else instead: {0}".format(r.text)) access_token = AccessToken( credentials.get('oauth_token')[0], credentials.get('oauth_token_secret')[0] ) return access_token def identify(mw_uri, consumer_token, access_token, leeway=10.0, user_agent=defaults.USER_AGENT): """ Gather identifying information about a user via an authorized token. :Parameters: mw_uri : `str` The base URI of the MediaWiki installation. Note that the URI should end in ``"index.php"``. consumer_token : :class:`~mwoauth.ConsumerToken` A token representing you, the consumer. access_token : :class:`~mwoauth.AccessToken` A token representing an authorized user. Obtained from `complete()` leeway : `int` | `float` The number of seconds of leeway to account for when examining a tokens "issued at" timestamp. :Returns: A dictionary containing identity information. """ # Construct an OAuth auth auth = OAuth1(consumer_token.key, client_secret=consumer_token.secret, resource_owner_key=access_token.key, resource_owner_secret=access_token.secret) # Request the identity using auth r = requests.post(url=mw_uri, params={'title': "Special:OAuth/identify"}, auth=auth, headers={'User-Agent': user_agent}) # Special:OAuth/identify unhelpfully returns 200 status even when there is # an error in the API call. Check for error messages manually. try: identity = jwt.decode(r.content, consumer_token.secret, audience=consumer_token.key, algorithms=["HS256"], leeway=leeway) except jwt.InvalidTokenError as e: if r.text.startswith('{'): try: resp = json.loads(r.text) if 'error' in resp: raise OAuthException( "A MediaWiki API error occurred: {0}" .format(resp['message'])) else: raise OAuthException( "Unknown JSON response: {0!r}" .format(r.text[:100])) except ValueError as e: raise OAuthException( "An error occurred while trying to read json " + "content: {0}".format(e)) else: raise OAuthException( "Could not read response from 'Special:OAuth/identify'. " + "Maybe your MediaWiki is not configured correctly? " + "Expected JSON but instead got: {0!r}".format(r.text[:100])) # Verify the issuer is who we expect (server sends $wgCanonicalServer) issuer = urlparse(identity['iss']).netloc expected_domain = urlparse(mw_uri).netloc if not issuer == expected_domain: raise OAuthException( "Unexpected issuer " + "{0}, expected {1}".format(issuer, expected_domain)) # Check that the identity was issued in the past. now = time.time() issued_at = float(identity['iat']) if not now >= (issued_at - leeway): raise OAuthException( "Identity issued {0} ".format(issued_at - now) + "seconds in the future!") # Verify that the nonce matches our request nonce, # to avoid a replay attack authorization_header = force_unicode(r.request.headers['Authorization']) request_nonce = re.search(r'oauth_nonce="(.*?)"', authorization_header).group(1) if identity['nonce'] != request_nonce: raise OAuthException( 'Replay attack detected: {0} != {1}'.format( identity['nonce'], request_nonce)) return identity mwoauth-0.3.7/mwoauth/handshaker.py000066400000000000000000000106131404130515100173460ustar00rootroot00000000000000""" A client for managing an OAuth handshake with MediaWiki. :Example: .. code-block:: python from mwoauth import ConsumerToken, Handshaker from six.moves import input # For compatibility between python 2 and 3 # Consruct a "consumer" from the key/secret provided by MediaWiki import config consumer_token = ConsumerToken( config.consumer_key, config.consumer_secret) # Construct handshaker with wiki URI and consumer handshaker = Handshaker( "https://en.wikipedia.org/w/index.php", consumer_token) # Step 1: Initialize -- ask MediaWiki for a temporary key/secret for # user redirect, request_token = handshaker.initiate() # Step 2: Authorize -- send user to MediaWiki to confirm authorization print("Point your browser to: %s" % redirect) # response_qs = input("Response query string: ") # Step 3: Complete -- obtain authorized key/secret for "resource owner" access_token = handshaker.complete(request_token, response_qs) print(str(access_token)) # Step 4: Identify -- (optional) get identifying information about the # user identity = handshaker.identify(access_token) print("Identified as {username}.".format(**identity)) """ import logging from . import defaults from .functions import complete, identify, initiate logger = logging.getLogger(__name__) class Handshaker(object): """ :Parameters: mw_uri : `str` The base URI of the wiki (provider) to authenticate with. This uri should end in ``"index.php"``. consumer_token : :class:`~mwoauth.ConsumerToken` A token representing you, the consumer. Provided by MediaWiki via ``Special:OAuthConsumerRegistration``. callback : `str` Callback URL. Defaults to 'oob'. """ def __init__(self, mw_uri, consumer_token, callback='oob', user_agent=None): self.mw_uri = mw_uri self.consumer_token = consumer_token self.callback = callback if user_agent is None: logger.warning("Sending requests with default User-Agent. " + "Set 'user_agent' on mwoauth.flask.MWOAuth to " + "quiet this message.") self.user_agent = defaults.USER_AGENT else: self.user_agent = user_agent def initiate(self, callback=None): """ Initiate an OAuth handshake with MediaWiki. :Parameters: callback : `str` Callback URL. Defaults to 'oob'. :Returns: A `tuple` of two values: * a MediaWiki URL to direct the user to * a :class:`~mwoauth.RequestToken` representing an access request """ return initiate(self.mw_uri, self.consumer_token, callback=callback or self.callback, user_agent=self.user_agent) def complete(self, request_token, response_qs): """ Complete an OAuth handshake with MediaWiki by exchanging an :Parameters: request_token : `RequestToken` A temporary token representing the user. Returned by `initiate()`. response_qs : `bytes` The query string of the URL that MediaWiki forwards the user back after authorization. :Returns: An :class:`~mwoauth.AccessToken` containing an authorized key/secret pair that can be stored and used by you. """ return complete( self.mw_uri, self.consumer_token, request_token, response_qs, user_agent=self.user_agent) def identify(self, access_token, leeway=10.0): """ Gather identifying information about a user via an authorized token. :Parameters: access_token : `AccessToken` A token representing an authorized user. Obtained from `complete()`. leeway : `int` | `float` The number of seconds of leeway to account for when examining a tokens "issued at" timestamp. :Returns: A dictionary containing identity information. """ return identify(self.mw_uri, self.consumer_token, access_token, leeway=leeway, user_agent=self.user_agent) mwoauth-0.3.7/mwoauth/tests/000077500000000000000000000000001404130515100160255ustar00rootroot00000000000000mwoauth-0.3.7/mwoauth/tests/__init__.py000066400000000000000000000000001404130515100201240ustar00rootroot00000000000000mwoauth-0.3.7/mwoauth/tests/test_functions.py000066400000000000000000000023571404130515100214550ustar00rootroot00000000000000# -*- coding: UTF-8 -*- import pytest from six import PY3, b from ..errors import OAuthException from ..functions import process_request_token def test_process_request_token(): request_token = process_request_token( b("oauth_token=iamatoken&oauth_token_secret=iamasecret")) assert request_token.key == "iamatoken" assert request_token.secret == "iamasecret" def test_process_request_token_errors(): if PY3: text = "Error: Произошла ошибка в протоколе OAuth: " + \ "Invalid consumer key" content = bytes(text, "utf-8") else: content = "Error: Произошла ошибка в протоколе OAuth: " + \ "Invalid consumer key" text = unicode(content, "utf-8") with pytest.raises(OAuthException, match=text[len("Error: "):]): process_request_token(content) with pytest.raises(OAuthException, match="I am an error"): process_request_token("Error: I am an error") with pytest.raises(OAuthException, match=r"Expected x-www-form-.*"): process_request_token("TOTAL NONSENSE") with pytest.raises(OAuthException, match=r"MediaWiki response lacks.*"): process_request_token("foo=bar&baz=bum") mwoauth-0.3.7/mwoauth/tokens.py000066400000000000000000000026341404130515100165450ustar00rootroot00000000000000""" A set of tokens (key/secret pairs) used to identify actors during and after an OAuth handshake. """ from collections import namedtuple ConsumerToken = namedtuple("ConsumerToken", ['key', 'secret']) """ Represents a consumer (you). This key/secrets pair is provided by MediaWiki when you register an OAuth consumer (see ``Special:OAuthConsumerRegistration``). Note that Extension:OAuth must be installed in order in order for ``Special:OAuthConsumerRegistration`` to appear. :Parameters: key : `str` A hex string identifying the user secret : `str` A hex string used to sign communications """ RequestToken = namedtuple("RequestToken", ['key', 'secret']) """ Represents a request for access during authorization. This key/secret pair is provided by MediaWiki via ``Special:OAuth/initiate``. Once the user authorize you, this token can be traded for an `AccessToken` via `complete()`. :Parameters: key : `str` A hex string identifying the user secret : `str` A hex string used to sign communications """ AccessToken = namedtuple("AccessToken", ['key', 'secret']) """ Represents an authorized user. This key and secret is provided by MediaWiki via ``Special:OAuth/complete`` and later used to show MediaWiki evidence of authorization. :Parameters: key : `str` A hex string identifying the user secret : `str` A hex string used to sign communications """ mwoauth-0.3.7/requirements.txt000066400000000000000000000001131404130515100164560ustar00rootroot00000000000000PyJWT == 1.7.1 requests == 2.21.0 requests-oauthlib == 1.2.0 six == 1.12.0 mwoauth-0.3.7/setup.cfg000066400000000000000000000001501404130515100150140ustar00rootroot00000000000000[flake8] exclude = .tox,build,dist,doc,examples,*.egg,*.egg-info [egg_info] tag_build = tag_date = 0 mwoauth-0.3.7/setup.py000066400000000000000000000017621404130515100147170ustar00rootroot00000000000000import os from setuptools import find_packages, setup def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() about_path = os.path.join(os.path.dirname(__file__), "mwoauth/about.py") exec(compile(open(about_path).read(), about_path, "exec")) setup( name=__name__, # noqa version=__version__, # noqa author=__author__, # noqa author_email=__author_email__, # noqa description=__description__, # noqa url=__url__, # noqa license=__license__, # noqa packages=find_packages(), long_description=read('README.rst'), install_requires=[ 'PyJWT>=1.0.1,<2.0.0', 'oauthlib', 'requests', 'requests-oauthlib', 'six' ], extras_require={ 'flask': ['flask'], }, classifiers=[ "Development Status :: 4 - Beta", "Topic :: Security", "License :: OSI Approved :: MIT License", "Topic :: Software Development :: Libraries :: Python Modules" ] )