pax_global_header00006660000000000000000000000064132767125100014516gustar00rootroot0000000000000052 comment=50f0924153859e6a76eec120ec3700ee69bde226 flask-oauthlib-0.9.5/000077500000000000000000000000001327671251000144365ustar00rootroot00000000000000flask-oauthlib-0.9.5/.coveragerc000066400000000000000000000000601327671251000165530ustar00rootroot00000000000000[report] omit = flask_oauthlib/contrib/client/* flask-oauthlib-0.9.5/.gitignore000066400000000000000000000002271327671251000164270ustar00rootroot00000000000000*.pyc *.pyo *.egg-info *.swp __pycache__ build develop-eggs dist eggs parts .DS_Store .installed.cfg docs/_build cover/ venv/ .tox *.egg .ropeproject/ flask-oauthlib-0.9.5/.gitmodules000066400000000000000000000001501327671251000166070ustar00rootroot00000000000000[submodule "docs/_themes"] path = docs/_themes url = git://github.com/lepture/flask-sphinx-themes.git flask-oauthlib-0.9.5/.travis.yml000066400000000000000000000004611327671251000165500ustar00rootroot00000000000000language: python python: - "2.7" - "3.4" - "3.5" - "3.6" - "pypy" script: - nosetests -s after_success: - pip install coveralls - coverage run --source=flask_oauthlib setup.py -q nosetests - coveralls branches: only: - master notifications: email: false flask-oauthlib-0.9.5/AUTHORS000066400000000000000000000004231327671251000155050ustar00rootroot00000000000000- Hsiaoming Yang http://github.com/lepture - Randy Topliffe https://github.com/Taar - Mackenzie Blake Thompson https://github.com/flippmoke - Ib Lundgren https://github.com/ib-lundgren - Jiangge Zhang https://github.com/tonyseek - Stian Prestholdt https://github.com/stianpr flask-oauthlib-0.9.5/CHANGES.rst000066400000000000000000000166501327671251000162500ustar00rootroot00000000000000Changelog ========= Here you can see the full list of changes between each Flask-OAuthlib release. Version 0.9.5 ------------- Released on May 16, 2018 - Fix error handlers - Update supported OAuthlib - Add support for string type token Version 0.9.4 ------------- Released on Jun 9, 2017 - Handle HTTP Basic Auth for client's access to token endpoint (#301) - Allow having access tokens without expiration date (#311) - Log exception traceback. (#281) Version 0.9.3 ------------- Released on Jun 2, 2016 - Revert the wrong implement of non credential oauth2 require auth - Catch all exceptions in OAuth2 providers - Bugfix for examples, docs and other things Version 0.9.2 ------------- Released on Nov 3, 2015 - Bugfix in client parse_response when body is none. - Update contrib client by @tonyseek - Typo fix for OAuth1 provider - Fix OAuth2 provider on non credential clients by @Fleurer Version 0.9.1 ------------- Released on Mar 9, 2015 - Improve on security. - Fix on contrib client. Version 0.9.0 ------------- Released on Feb 3, 2015 - New feature for contrib client, which will become the official client in the future via `#136`_ and `#176`_. - Add appropriate headers when making POST request for access toke via `#169`_. - Use a local copy of instance 'request_token_params' attribute to avoid side effects via `#177`_. - Some minor fixes of contrib by Hsiaoming Yang. .. _`#177`: https://github.com/lepture/flask-oauthlib/pull/177 .. _`#169`: https://github.com/lepture/flask-oauthlib/pull/169 .. _`#136`: https://github.com/lepture/flask-oauthlib/pull/136 .. _`#176`: https://github.com/lepture/flask-oauthlib/pull/176 Version 0.8.0 ------------- Released on Dec 3, 2014 .. module:: flask_oauthlib.provider.oauth2 - New feature for generating refresh tokens - Add new function :meth:`OAuth2Provider.verify_request` for non vanilla Flask projects - Some small bugfixes Version 0.7.0 ------------- Released on Aug 20, 2014 .. module:: flask_oauthlib.client - Deprecated :meth:`OAuthRemoteApp.authorized_handler` in favor of :meth:`OAuthRemoteApp.authorized_response`. - Add revocation endpoint via `#131`_. - Handle unknown exceptions in providers. - Add PATCH method for client via `#134`_. .. _`#131`: https://github.com/lepture/flask-oauthlib/pull/131 .. _`#134`: https://github.com/lepture/flask-oauthlib/pull/134 Version 0.6.0 ------------- Released on Jul 29, 2014 - Compatible with OAuthLib 0.6.2 and 0.6.3 - Add invalid_response decorator to handle invalid request - Add error_message for OAuthLib Request. Version 0.5.0 ------------- Released on May 13, 2014 - Add ``contrib.apps`` module, thanks for tonyseek via `#94`_. - Status code changed to 401 for invalid access token via `#93`_. - **Security bug** for access token via `#92`_. - Fix for client part, request token params for OAuth1 via `#91`_. - **API change** for ``oauth.require_oauth`` via `#89`_. - Fix for OAuth2 provider, support client authentication for authorization-code grant type via `#86`_. - Fix client_credentials logic in validate_grant_type via `#85`_. - Fix for client part, pass access token method via `#83`_. - Fix for OAuth2 provider related to confidential client via `#82`_. Upgrade From 0.4.x to 0.5.0 ~~~~~~~~~~~~~~~~~~~~~~~~~~~ API for OAuth providers ``oauth.require_oauth`` has changed. Before the change, you would write code like:: @app.route('/api/user') @oauth.require_oauth('email') def user(req): return jsonify(req.user) After the change, you would write code like:: from flask import request @app.route('/api/user') @oauth.require_oauth('email') def user(): return jsonify(request.oauth.user) .. _`#94`: https://github.com/lepture/flask-oauthlib/pull/94 .. _`#93`: https://github.com/lepture/flask-oauthlib/issues/93 .. _`#92`: https://github.com/lepture/flask-oauthlib/issues/92 .. _`#91`: https://github.com/lepture/flask-oauthlib/issues/91 .. _`#89`: https://github.com/lepture/flask-oauthlib/issues/89 .. _`#86`: https://github.com/lepture/flask-oauthlib/pull/86 .. _`#85`: https://github.com/lepture/flask-oauthlib/pull/85 .. _`#83`: https://github.com/lepture/flask-oauthlib/pull/83 .. _`#82`: https://github.com/lepture/flask-oauthlib/issues/82 Thanks Stian Prestholdt and Jiangge Zhang. Version 0.4.3 ------------- Released on Feb 18, 2014 - OAuthlib released 0.6.1, which caused a bug in oauth2 provider. - Validation for scopes on oauth2 right via `#72`_. - Handle empty response for application/json via `#69`_. .. _`#69`: https://github.com/lepture/flask-oauthlib/issues/69 .. _`#72`: https://github.com/lepture/flask-oauthlib/issues/72 Version 0.4.2 ------------- Released on Jan 3, 2014 Happy New Year! - Add param ``state`` in authorize method via `#63`_. - Bugfix for encoding error in Python 3 via `#65`_. .. _`#63`: https://github.com/lepture/flask-oauthlib/issues/63 .. _`#65`: https://github.com/lepture/flask-oauthlib/issues/65 Version 0.4.1 ------------- Released on Nov 25, 2013 - Add access_token on request object via `#53`_. - Bugfix for lazy loading configuration via `#55`_. .. _`#53`: https://github.com/lepture/flask-oauthlib/issues/53 .. _`#55`: https://github.com/lepture/flask-oauthlib/issues/55 Version 0.4.0 ------------- Released on Nov 12, 2013 - Redesign contrib library. - A new way for lazy loading configuration via `#51`_. - Some bugfixes. .. _`#51`: https://github.com/lepture/flask-oauthlib/issues/51 Version 0.3.4 ------------- Released on Oct 31, 2013 - Bugfix for client missing a string placeholder via `#49`_. - Bugfix for client property getter via `#48`_. .. _`#49`: https://github.com/lepture/flask-oauthlib/issues/49 .. _`#48`: https://github.com/lepture/flask-oauthlib/issues/48 Version 0.3.3 ------------- Released on Oct 4, 2013 - Support for token generator in OAuth2 Provider via `#42`_. - Improve client part, improve test cases. - Fix scope via `#44`_. .. _`#42`: https://github.com/lepture/flask-oauthlib/issues/42 .. _`#44`: https://github.com/lepture/flask-oauthlib/issues/44 Version 0.3.2 ------------- Released on Sep 13, 2013 - Upgrade oauthlib to 0.6 - A quick bugfix for request token params via `#40`_. .. _`#40`: https://github.com/lepture/flask-oauthlib/issues/40 Version 0.3.1 ------------- Released on Aug 22, 2013 - Add contrib module via `#15`_. We are still working on it, take your own risk. - Add example of linkedin via `#35`_. - Compatible with new proposals of oauthlib. - Bugfix for client part. - Backward compatible for lower version of Flask via `#37`_. .. _`#15`: https://github.com/lepture/flask-oauthlib/issues/15 .. _`#35`: https://github.com/lepture/flask-oauthlib/issues/35 .. _`#37`: https://github.com/lepture/flask-oauthlib/issues/37 Version 0.3.0 ------------- Released on July 10, 2013. - OAuth1 Provider available. Documentation at :doc:`oauth1`. :) - Add ``before_request`` and ``after_request`` via `#22`_. - Lazy load configuration for client via `#23`_. Documentation at :ref:`lazy-configuration`. - Python 3 compatible now. .. _`#22`: https://github.com/lepture/flask-oauthlib/issues/22 .. _`#23`: https://github.com/lepture/flask-oauthlib/issues/23 Version 0.2.0 ------------- Released on June 19, 2013. - OAuth2 Provider available. Documentation at :doc:`oauth2`. :) - Make client part testable. - Change extension name of client from ``oauth-client`` to ``oauthlib.client``. Version 0.1.1 ------------- Released on May 23, 2013. - Fix setup.py Version 0.1.0 ------------- First public preview release on May 18, 2013. flask-oauthlib-0.9.5/CONTRIBUTING.rst000066400000000000000000000043751327671251000171100ustar00rootroot00000000000000Contributing ============= First, please do contribute! There are more than one way to contribute, and I will appreciate any way you choose. * introduce Flask-OAuthlib to your friends, let Flask-OAuthlib to be known * discuss Flask-OAuthlib , and submit bugs with github issues * improve documentation for Flask-OAuthlib * send patch with github pull request English and Chinese issues are acceptable, talk in your favorite language. Pull request and git commit message **must be in English**, if your commit message is in other language, it will be rejected. Issues ------ When you submit an issue, please format your content, a readable content helps a lot. You should have a little knowledge on Markdown_. .. _Markdown: http://github.github.com/github-flavored-markdown/ Code talks. If you can't make yourself understood, show me the code. Please make your case as simple as possible. Codebase -------- The codebase of Flask-OAuthlib is highly tested and :pep:`8` compatible, as a way to guarantee functionality and keep all code written in a good style. You should follow the code style. Here are some tips to make things simple: * When you cloned this repo, run ``pip install -r requirements.txt`` * Check the code style with ``make lint`` * Check the test with ``make test`` * Check the test coverage with ``make coverage`` Git Help -------- Something you should know about git. * don't add any code on the master branch, create a new one * don't add too many code in one pull request * all featured branches should be based on the master branch * don't merge any code yourself Take an example, if you want to add feature A and feature B, you should have two branches:: $ git branch feature-A $ git checkout feature-A Now code on feature-A branch, and when you finish feature A:: $ git checkout master $ git branch feature-B $ git checkout feature-B All branches must be based on the master branch. If your feature-B needs feature-A, you should send feature-A first, and wait for its merging. We may reject feature-A, and you should stop feature-B. Keep your master branch the same with upstream:: $ git remote add upstream git@github.com:lepture/flask-oauthlib.git $ git pull upstream master **And don't change any code on master branch.** flask-oauthlib-0.9.5/LICENSE000066400000000000000000000027641327671251000154540ustar00rootroot00000000000000Copyright (c) 2013 - 2014, Hsiaoming Yang. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of flask-oauthlib nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. flask-oauthlib-0.9.5/MANIFEST.in000066400000000000000000000000671327671251000161770ustar00rootroot00000000000000include README.rst include CHANGES.rst include LICENSE flask-oauthlib-0.9.5/Makefile000066400000000000000000000013551327671251000161020ustar00rootroot00000000000000.PHONY: lint test coverage clean clean-pyc clean-build docs lint: @which flake8 || pip install flake8 @flake8 flask_oauthlib tests test: @which nosetests || pip install nose @nosetests -s --nologcapture coverage: @which nosetests || pip install nose @rm -f .coverage @nosetests --with-coverage --cover-package=flask_oauthlib --cover-html clean: clean-build clean-pyc clean-docs clean-tox clean-build: @rm -fr build/ @rm -fr dist/ @rm -fr *.egg @rm -fr *.egg-info clean-pyc: @find . -name '*.pyc' -exec rm -f {} + @find . -name '*.pyo' -exec rm -f {} + @find . -name '*~' -exec rm -f {} + @find . -name '__pycache__' -exec rm -fr {} + clean-docs: @rm -fr docs/_build clean-tox: @rm -rf .tox/ docs: @$(MAKE) -C docs html flask-oauthlib-0.9.5/README.rst000066400000000000000000000060121327671251000161240ustar00rootroot00000000000000Flask-OAuthlib ============== .. image:: https://img.shields.io/badge/donate-lepture-green.svg :target: https://typlog.com/donate?amount=10&reason=lepture%2Fflask-oauthlib :alt: Donate lepture .. image:: https://img.shields.io/pypi/wheel/flask-oauthlib.svg :target: https://pypi.python.org/pypi/flask-OAuthlib/ :alt: Wheel Status .. image:: https://img.shields.io/pypi/v/flask-oauthlib.svg :target: https://pypi.python.org/pypi/flask-oauthlib/ :alt: Latest Version .. image:: https://travis-ci.org/lepture/flask-oauthlib.svg?branch=master :target: https://travis-ci.org/lepture/flask-oauthlib :alt: Travis CI Status .. image:: https://coveralls.io/repos/lepture/flask-oauthlib/badge.svg?branch=master :target: https://coveralls.io/r/lepture/flask-oauthlib :alt: Coverage Status Notice ------ **Please use https://github.com/lepture/authlib instead**. ===== Flask-OAuthlib is an extension to Flask that allows you to interact with remote OAuth enabled applications. On the client site, it is a replacement for Flask-OAuth. But it does more than that, it also helps you to create OAuth providers. Flask-OAuthlib relies on oauthlib_. .. _oauthlib: https://github.com/idan/oauthlib Sponsored by ------------ If you want to quickly add secure authentication to Flask, feel free to check out Auth0's Python API SDK and free plan at `auth0.com/overview`_ |auth0 image| .. _`auth0.com/overview`: https://auth0.com/overview?utm_source=GHsponsor&utm_medium=GHsponsor&utm_campaign=flask-oauthlib&utm_content=auth .. |auth0 image| image:: https://user-images.githubusercontent.com/290496/31718461-031a6710-b44b-11e7-80f8-7c5920c73b8f.png :target: https://auth0.com/overview?utm_source=GHsponsor&utm_medium=GHsponsor&utm_campaign=flask-oauthlib&utm_content=auth :alt: Coverage Status :width: 18px :height: 18px Features -------- - Support for OAuth 1.0a, 1.0, 1.1, OAuth2 client - Friendly API (same as Flask-OAuth) - Direct integration with Flask - Basic support for remote method invocation of RESTful APIs - Support OAuth1 provider with HMAC and RSA signature - Support OAuth2 provider with Bearer token And request more features at `github issues`_. .. _`github issues`: https://github.com/lepture/flask-oauthlib/issues Security Reporting ------------------ If you found security bugs which can not be public, send me email at `me@lepture.com`. Attachment with patch is welcome. Installation ------------ Installing flask-oauthlib is simple with pip_:: $ pip install Flask-OAuthlib If you don't have pip installed, try with easy_install:: $ easy_install Flask-OAuthlib .. _pip: http://www.pip-installer.org/ Additional Notes ---------------- We keep documentation at `flask-oauthlib@readthedocs`_. .. _`flask-oauthlib@readthedocs`: https://flask-oauthlib.readthedocs.io If you are only interested in the client part, you can find some examples in the ``example`` directory. There is also a `development version `_ on GitHub. flask-oauthlib-0.9.5/artwork.svg000066400000000000000000001025501327671251000166530ustar00rootroot00000000000000 flask-oauthlib Created with Sketch (http://www.bohemiancoding.com/sketch) flask-oauthlib-0.9.5/docs/000077500000000000000000000000001327671251000153665ustar00rootroot00000000000000flask-oauthlib-0.9.5/docs/Makefile000066400000000000000000000127341327671251000170350ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Flask-OAuthlib.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Flask-OAuthlib.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/Flask-OAuthlib" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Flask-OAuthlib" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." flask-oauthlib-0.9.5/docs/_static/000077500000000000000000000000001327671251000170145ustar00rootroot00000000000000flask-oauthlib-0.9.5/docs/_static/flask-oauthlib.png000066400000000000000000000565451327671251000224460ustar00rootroot00000000000000‰PNG  IHDRÜܱS ],IDATxìÛ p\åaèñßÑjWË–e½,Y–_X~`Œ1Æ$„G\óŒIK. $Ü–N“’[Ò’¦3·—Þ^I $¤á^B3¡…&…b&ò‘|ä#ÿEEþ‹x.WnSn^Ô—ÓõDÅz"F¡„d r3#SåýR>ô¢ç|håÖ—åŽî‘ŸÏo™Ñ;µwb_UjTfDª Ï$B,ÄDÙ2Q¶(•“ÌÖoËÛ‘¿¾¸¾è­¾TH'út&›ìó!ýćMSQéôä„ÜÂÞSÚNmÙ=.¥õèÒž­O¿”t¶T¶L¶\19²gÙCB‰1Fʉ‰wŽÜRöjÉË=Ý9­¶ÿE½àC# >,n›]œŸSš»cÊöŠ}ÚìÓ®GŸ#“+a¤£ŒPœ·«ú­t[lsô¸ßûˆnõA–Ì™™×Qв°íšLmºh{ÙÖ­Úuë’v,å)È6Ú8ÓG6Ƕæ?^½¸¼ëÒá£WÜ{öÿ&FeÍŸný|Om{|mî62‡1]9"ÙClÿ¿d²…ýç´C‹Ä«Q—.OÅ÷TßUüL¬Á@ѽ>xz'õÏΜ²ã궺FõvIbšòÊ—+¡@¡âlÙòe i½úôêѧÕ^ûtIeëÖ©WËԘ«TÅóUFëZ^Ñë%ºÙÉÚØ©×æœÓþ&çç¥Ûþ:½póÔ7 ·k‘r°\å&«Sb’:5ÊÄ%²Åäx¯R’ÙÒ:l²ÚÖÚªIÊÁF(Ugζ¢W ¿{Æ*©öq€¦‚¦‰ÍßnùÌú‘+¢½ú(W¡r§›£R)ÊcªˆŽ´ŒëmÔbµ—4ë–4T¤Ð)æt^;ö{ñ5|´“:»ëK[¯Ý”ó²®aßËÌr SLT`8IýzôI#¥O¯>ÝRr³%dÏòˆäJÈË–0›m°U‹>I½RÒ‚w“«@ÂcL2ÃE²©ÜÁÒÚìôœÕ^Q/c°H­SÍY¿oÚÿ•ô>ˆò~hœÐò?Z¯y±xµƒEF˜íBsœ©Tܽzlôª;­³EûÀT©H®¸2ÓÌ0Æg©–È6TJ‡§Õ{Ö‚rU;»¿ö­²Ãt9Á¢p¢åÕ¤.ÛzóæÒt,Rm†ËLw–të°ÞÓ¶Ûi­=‚c/n¼Y*L÷q“+4T§×­³Ø*†ª³À¸_Œ¾E½*ºË‰ÕwMó·V¼L³Á"3á Ó“Ðc£]–{ÞV»t;þŠU˜à<óT™ªÐ`i[½f±µÙ‹›åœ†ª;nÑ䄉¾ãÄ)œnßqÁ³#7 ;É'\àTˆØaMž·Â^­N¬ÑJÍöI“Í2AÄ€¤Ïûµm7ØXóÌ_¿Ó/ Ñwy=ßèøÆ‹Õoèb@d¬.´ÐX¹€”íž±Ò 6ë•v¤¦:Ù£‚#“#_­³Õ™î,Årôó=i‰gµ3 ×ItWßZñÚÑ=N„ôìÆo¯¿j‰=+1ß•Î0@—Ý~ëw^ñ¶£Sî%~h“£ShºÓÌ·P™b+<ïßÔëb@¡Ef?_x£7wÑo¹ù—·Ý¶|Âä›ìjç™'è´Í³ÙÖØìè]äÿí/=êX˜b¦3,2A)€Ë=â762 rŽÓöŒ»iâ¿JÈ·ë§æÞõÚÅ/Úm°“]è2§èö’—=b‹6áËáËwƒ¿“ã‡nÕáÐbæ› PŸMÞЇáTm‘sÌW `¯¥ð;Pî\§ÿ*÷«G±3´·®ž¸¨sñoçþnÈtŒò9æZÓ$@Ú ù‰Å¶ëu°˜ —[ A«Ã5Éå¨UäYM%î 7ºH¹ùæš$ØnxI{½a™F½*ä‹PhŠ9Ê´ Ú¦tÛ$wzÉ¢Ä ¡18^Å Ç«âW¿¾ñŸž,Y) f¦ë|Ùù DÈØíQw{ÀFýVl²Ïø²Z[¼ åðÄœ¯ÊgJXc4†—ããn“ï^÷Y©ÇgÍ×i‹´á¥ìµÌz›)‘‡¥æª´aI{KgyégÇl‰­‹9>ùÁqê–ÊÛþõK¡$TiX {Ã;Ã3áOì`ø²Ïü¯ð³°=$Ã?‡QÁaWîŸyeH‡_„ÊCÝ[áÎÐ~ ‚l‰p]X~j‚w)?|2ÜÞ }á+Âm¡.ÔÄpý¾\sîÍŽG±³$Cá¢î^rÁSºp¦?÷­OER–ù‘û=^¹B åjí¶L£ÃYàLxÑL§ËØlƒ`x®1Ã2/éFÚÛfÙé·úZÊV¯gK*UÆšcš¤úmÖ'Š?U6»ó‰dÒ±.6ï˜yK´ôꆟþfÒ«2 œïFÿM ÉC~æa‡þ«ÉZëlŽb–:<…®Qä!M¦˜m’f/H^±KÔ©ÐíM½ˆ;ÛëÞƒ¶.MúÐçmËí“2^ SLU`£.@Ÿ §×œ‘÷h¢/áØ»ð8'öëomù‡‡‹·0 ÆW\ãù iµ{ü“—¥ïd݆Êè2Æ µvY¥Õá8Íù¶+1GB…É:ÕÛaxiÎ0Ö,IôHÚb»&›è*Õöè4Ø>ËmÒi¼bä¨6ÓH šÐoƒ¼)•çV>ZØSèX»ü˜WßsãÆ[ç7ˆœê/\e¶tXì^j0X‘Oû¶Bô*è0Ï4%Þ´Æ»Ëñ9‹ŒRg–)ª”*·Ù+‚áôÛ£Î%fè÷¶ÍöNj—9S“6I;¬µ×X•r0Ê,eZìÁf±Ú²ñÒiÇ®ØùÒǬäˆÎûë¿ùPN;€bì;.S Xî_ÜîeIƒÍôE—‹©P©É^Cõ*ô cí´Fr›áó–ûž_{*Û:g«Ðj&Š”èÕj­BU: u^‘‹|Ñ\£Tj?`´Nk¬V¬TòlªŒ·ô¶Ê™Vu~Îs™½ǪØÉ£^MŒ~dÕŸ<® @•k|ÅYâ å×~ꆚëëª,u‡õ¦*²ÒP©lªPl•·Ôøv9Ðù>å1KìÍ֢˔ZaåRÍZ4Z¯H­ 5{Mp°Wù²*«¬·Ë6ùR ùüòMAÑÈQcª¤Mz;tTVŸ)³'ãØä–cÔcþþ¹Ï„X0дp[ØÞÑ~Î ¹Á Ã¥á¾pWXâûǃƒný¡3ümìïÔðP¸|˜ï¼3Üjƒý%·CGè ßË>ã€.φO ,ÝïÎŽÂ/ÃŒ`˜f‡!4‡[ØPâÙóa^È †T¾^ýáÿÛn åCÆøÎÊŸòcǦœQŽE£‹ZZ÷É'¤ýeû¦ëTvû…ïû½”ÁÆ»Þ_›êçžÕB§(v°6ËìVd‘*eJ–À˜:[¤_"DJtKI¸Ò…ªD+T©°Õ­C­ÑVêó.‘¶Ä¿hÑ£_‰2×»ÈHƒ5ºÏ-~£ LöM¦À*OÏÞ}_¢ê­*/;êGu>üâÓ&á}Íç^ñSwÛl°\sÝ`‘3äÙm¥~”¹Ü$k¥hŸJó$¼m…ŒÈÙ®ô 6j0Æ_©C§Y:mG©LÒn«¥¦[¦@Ü|—ÙìMû@Ïo‰§´*æÓ®We«[,WªÎé&™ïs¦©²[@¿uê%MSˆ‘æÈ³[#`—öª²y¿íoëw´Åõ͹þ­þS¿‘ä»ÂuÊÁ³þÑÚ Uíjm:MV¦D‹µè1×í68P‡Iæ)ÕámÓ\êr#­ò’×%¤mô;{Ñ;t!m§—üÊb[nFT(5OJK¶" }VÚxRÆPSÝäcZýBÊ.q•óUk³Þ:ù*,•1XƒMâ¦fí4Ê©yR÷“ûúö9ºbsŽr€1EíÔæ1)@ÜŸûš‰ƒ”?ø;Oëq n+ýÞkûwi[ìõ%3J»e ÕnšÆ*s¾rË-v¿z¶wÿ½jÖÒšíÕa_¶ÍCÆ ¶Ú¬À¹fšï\—˜ì—îÑb¨‘þ»/(ð‚^_Wkƒ-–xÒ[b©åº¨Õ=¦‰ÓelÖØnTÝ„“K+ì?Ê}ÜeGuyH„{–~áI)@+ÜäHZâûž“q°ŒnIív«2ÝIRÊ}A½‡åšb.Cå:S6oyÊbz[ŸŒ£‘±Ór;EêL²Åƒî·ÕøŠ:=h–Ó¼à·ÊRÒz³ §ÅFݦ+F“tÛ©ÛTN«¬(~*/“çÈsÏQ$ºó‹×v•ûË W…åáተ0ä‡,/\Þ !´„ÅáÆ0>5ÙPy¸>Ü›íÓá¤àW&†YaܰëÙÉáþк©á±Â„“ƒÃ¬"üÕÀªzsøŸƒ~«’ð•}·_y»£é¨.¾ó’oì=hi]XÞÑ~æ‡Q~¸)4†6„‹C [evìO„1Á kt¸"\þ&ì !<æe?BXÎ~O·Ä7¦®)üï0qÐè74ÿèâ9òb‹ÄްäÉ;?Q¾Pè*ß4ÐæiwxÃáHÙ!ß\å ¬·ÇpºÔ۬ǻË“+.O\´ÿë˜HðÞT¸Ø8_Rc»zFÜ9NÓe©7®” zMTŠBÓtÚdèÕT8vaâÑþ–#^Užw„î«ízü™‰ëqê³­teW»•¦š(_½Ve2R†J9”\EÊT¯Æ“M5Ó….užÓŒQ¡JY¶Q $Dú½»v/KˆTZãn­âN7_ÆRo8|½VI™®E¦ë²B ´ #'}<öˆG$¶Ð‘È)ÎÜÿê™/r\â/*íõS¯x/vÉ5ÕxeztøµÖÉ8q¦:I‰Æ‰tëÐh›Õªü­sÕûw-RŠU«U¦B¥Z%‚¤ŒCIYŸmѼ-i†òüÁ+Þ‹~Dæ*@±©¬— ÊÆÖ¾ötChðÞ‹9‚‹îŠ–}uéWŸŠà<_wŽÐiñÀ›äáËX¯Ç|5&›'e¥-2­ÀL \è‹z¬°ÍZ«m±[³v]Jdo%­Æ{^½fÛ¬·Ö& Ú¥UºÒ')Óc^‹ÕZµÛãã cŒç¼ä½éµVä?Y;è(ª.ŽßôÐ¥7AŠô"`Aªt ‚ô"   (vPé¤EÄ‚ ‚ " (‚ʇ HQé½Bêfç9÷ììÎBØõýÏù>Ü·IfæÍ{·ýï½UŒ]—Oî‘TÙgî 9š¯ú}{K,!wŽ€h 3NʃX¸‡wHRYM $ f>)e$UAü"œh*ÒŸ×éFIšrŽ?)çCIøC¨ÏqÎs¿&;ë¼LU>æP‡‚D"Î0×Ò”¼Ë `Èã&’@6[餺vq^º0§Æ¹sð#SJ¼y¤ºÇãÇ5°.jÍ‘ŸQ|ÇSä½åw*ñƒYÍt³n¿ÙJ›Fø) 5¼G8σˆ"7ï’Í<³÷ògx•.$P“¢„ùå¦4a2'ÈfñH¨Êl’p±Šfúj6dÌOKb—ÈBîüGÆÐÊC•™Xƒ_iK šò!]‰ô»ÏJQ“V$ÐŒoHä DÑŠ$~¥bÏܼ¨WÒ„7QE¯x0‡hÄà^r…Väå:PÒ~-ÏbôáVÓˆp$ÔçÒHåµGCéʘÇÈâŽ`ìÝ\a÷¬ÁÿèH0JSËÏɃô6»¬EùƒdZ#6´å¨kÌÞo€—l¿©{9ÂCfQg£l¯W%vLcw-e=y€XÄ¡Ta*£(†„‡Øˆ€t¦QD_´çS&¶™(w†;üúÛ÷ ½r—ÇöÿÒºø‹~Ä ÿ9âhHoî§=¹ÎöqÞˆZÄŸ´`ð¢Ï t'9K3æ’ÁŸÙŠì!™¶Œ')E}zÑÀÏâ• ‘H@ãQ§yƒ8ÔÛ2-všÜ îèË;"Æ¿S×Ã¥3S¥Ûu¦RùQ€ºôæ>â¬ý3€¡~ÚóÆ=5„ÇÙÃdÊ<Çü"ÛH#‹Ìr…ÃÃô¢¹••€‘—7¸¢LÌ~Ö+N;÷ä!wÆÀ¼ƒ¯2«ë“D«ÌéÍ߀›Õ܃ü§ˆ¦&iN¢Øj.“ÁFÊøy(ß'¨æ8Ïjà ~Áqt°ý¾´§Èˆš,Õù74ÔWôå³ókΗœCîàËE^:]±ÐˆM`4'ùQ‘=`ƒGÝØ œc 1–ö$“ºN’œ"Ç_×fpr.}Cç8Ì, !ÿî¦3Õx4Öª„)ΤÐMwuWÒùÅækÿŽ4^´ùæ\ãîÕÙXÞ%‹áÖ£¯ÅQÎó€ÇÂÌ"›O­¿U‰]\ö80Cx†T>å1:¨.<ò3šD`ƒ^K)^=<%nŠä 9üÚÄB#~×”ª°NMîoô` QÔ£¥Ì{‹ >0Æn Ör…ˆ†Êkéò°„ë˜eS ó Û¨lÍÎfyì˜Æã ¬Wð `©‡Y™ƒ$ó(b0ØDQ³¤=¨üŸ „²|… €ThjLBöä“%g¸Í´b\BV˜ecõ}9A— …·]hJ´¾ëOãæ{šó'Wiå@®Kaw“ïÉBw£ªÿg9HmbYºÛTA`7—y˜0æo{™1•ù)FÖ½,ÓG[Z…¶i(5èö-Ýê—Ÿâ B­=×'mNƒœxŒCoÏ™ý¨ð™±{´Œ`céh1¸.Ë ùUþ‹QAž‘ßä ØG Y,ye¥T•%ïXä¹.™ò°”v˜—–&G»TrŒâ=,qrMî‘:â4’|’ts¶¾Oøiì–G$?~“…¾ˆ´•â qtrïÄðD¹ rb€{¹b¡ï’ €›•TG¥x‘óL ÆKÁÿ†K›„X£®ädò=wÛü›s1„!´!‘¿¨k;î¦ˉ5JÌ^N ÆÁ[d²Ú¼ •ø“$úÛf{p…õ4¦^Oà¥-GÈf%åUþß0\n‡Û~a܂戅’,à @ó)ðŸ¼sOšßÉx2™jíº üN ==vÈ[d±–‚ª®%‹ W5%‹­”ñˆ·e2‚p÷ó_*AÃxŸé®Ëq.¨IÂ@2øJe[%v“B{íÆ ~£8B5ºü®ËÏ,R8Æ`ëŠÃ”:©iø„¢oü]H/» {€ôVƒAzQTm¸‘d3›hг“«tClxƒtÖg–íc¬0©b(©ül–.ž…¸™cÓOá,£ˆíDødË$ïMÛ)§Á¢VD"A¢%¿ŸêëR±ëÇÊ­q›é)£²ÂõíXL†î·‚Á+Äô´imѼÌãk.:aŒ6²®áæ%ÿys`Öe:0ÏÇך$~¡:ÃÈæ Ÿ{SNpœZ<Û2·=Q…¹B[Z’Ä.yF[´õ:Š4vÓIÏŒçÏLm8Un…[NÎ*<|‡:–Œ°Æú­ QŒ®và‚@–éÊÓdr” G»MèÆ5.ÅHÇuÎqŸ;ÆÙê°‹Ód°Â18U‘ßHá"k(îÁhCåàÍ6Í4òª±2õã nv«Ž®Hµßf @#ƒ–o1$h}»+9“ &ù±® °ØïÇ®ËÍÀ9*ùYö5@""Ž@?³¯Ðßáov¡xЬ‰$°ú´Ÿ;5³òLñ[L-‰ó©>\t¿mz¿…Б·|§;¸J£ˆÌrüùXLë¹Á:離$¿ÈVÊš…²ÏÎ%‹Ÿ8¯îg;ž!‹ÍáŒ#żÉüÅéàpr Ú oÀFrÇêñ=ã‰Ñ¡RçUy:Û’dd28$(Ô2¶ÙdÛ‘Vœu\¦7B4ïÓ¼–.ŸÍ‹D1‚L>$ÞK;›Ì&‚gÉ`=Ì53(`Jøœ8ZrŠ_}|4O›(ÀC†VTÕG}Hf'¥¨ÈA®ÑÖk¶9èAhôŒY¤Ù|O=Õà_Û½´àRñ¿„ŽyO©v4c7nk¿µC‚Bu:QœùÀD"Õ,0aR5ŸG«]§aRµÛBy7ßQÀcQW#³ŒƒßÉË0v°×àP™­]Z‹Z‘âM²YAZQCÛn»Ä7ÖqXÉ•ú"ŠÎ\f'¨ä ô ÿp'³>ëÍŒö3ÄüNÌÏýbb¿=Çe²YBÁ -—6FFÅ0‹ Æ›Ë,g¤½mjʲ™@.kaÖØ(@aŒÂÅ×ä·ŽÁd{P€B(O®£Ãì¢PÛÚK}Ä ?é¬ñšõ8i(´ªðð%l´¢ë´ÑÙó1Ì”RÃ^NƇ궯Í›½c'óø·:ºÔýÄ·¸8M‚z·šPK…òRÜŒáó{ÛmQæ8AyyŸT/»-Œ7È`17±,^µ]U,¯`o#ßx„Dp/! Âå£I¶àˆe’÷ •mhRC­WH0ßKäµ×ªÓ&¨ã2”žœ`äÑ<Ÿ<§Ð2™GGØURöC Û2rGi[ü.ŽO¸ÎINÑÝ1¬ú.0›Èd¨Ïc aé¼ËBÏ0©î‹°-v’¹‘u?ó.6:0JsœÃ¼fv›»E)´/s’¿¨é¡nUA‚@=¾Ã œ¡»ÞM{טñwHˆ?¸ç0Uݧ“ÀU£áîáE¨A&°œ?ŠòtFûÙ½ÏY³¡^ŸßOöq:^ßz ÀF?¦E-N›ý¨ú÷ð7ð/¥lÊS· „H/’¸X¬ZjQFþÙ9¬³8!ô/qBXÓ}1Ù^© QVµ’ý’!zrÚV èn“,ÿƒ´–Qæš©hº,Þ#ÑŽUò*šÿ¯!q^E€óI¸ØGq)&n¯Z ¹ÍçÅýÔ`Î+˜ä-§QU RP*ØòéÊð0……šâ%¥Œ>]¦vÇÚâGåŒjƒÏhd›AœÀÅ2WÇ<`ŒÇ»Æ8ܬ%ŸùK%ø ß1ËzÜO “*Zs•}–IÞDÖP¦ííá¤e×u悯ÓPkó+}‚0 òñ —<žÍýÌ~Æ©ê‚ÃGÓûtöøU£È´NÞç€Q†nfA6Ê0¶pî>7˜ËòGSÄ,ðP¼´®ñd0¢¼C&ozI‘ÆÃ¹†bÌ >‰øŽ/h@ž&•Uv»MC­•éL*ÿ£˜Oäàç©G3.sØ'3¡{L¨µ– È6¢5÷""‚aœ2Fœ>¹á?¾s×;â Ÿ¾¶E/Žúüˆ€¨麵¥bࢋ³ÏÎȵ\äyiÅ›Üà/£ª„ù&æ,û¹Ÿ |ÇvàW2ùÎ1àŽ"ìOŽG]Sö‘ÄIöPÑ|ñ3—¹ÂaC(HÏ \÷³ pó•¾ Ñ<åú°Ò‡â Ÿ6G?›¨l :rK‚`PU£²(_iôô+ •Dû9Jþö“Ï%ô ð5õ.‘‡Hä ˆ#Fø{¼à÷ª;0ÓRÂZQ9Mà+ÜÀaÚzÈÕé¹­ËkBÕªQ2NórŒåN”V P2½ùšL^u,¢ôIl%™qÚY<³Éb#Y,¶K[å’$s€É´! §Ñœ¹Îo”E|Ѓ³låRÇaöq5ø“C<à0[—¿8Ì!.ÐO½CÊ¹Ü ãp•Áú¼«1ìço£¾;Äûƒ #Ú¸•8Çϸø…úH€(Hg"”ÔÃ,ÐF2xËó5R Ðóäg™>KkdÛHò3L¦ã%㦛S¡(µyŠlœF‚9ÆR4ç@Yº“Âo” ¹Éôñú4㇩B]Ç µ9Î%[îçöˆÉ*°/·ç –)‡:†gSÇ-;Äûƒ7ÐNMàfUqÞæÔB(a(@]UÝÿ7OÛŽÁH᳘ñ|LðXº8CšMB”‰ x†Z#˜|jÌðüŒÄÓèK^„ކV¤†²Eúå,5å[mêEKNsÐÒ$›Zу6ÅågèdɺßI"Á쑆ï¹`åQÍ>sb£Ûä€KÈçÓ¤îœào°!ð8)bè­½m§ù:²Ô$çc[¥„x¾žµ–.sm•L¨UÍ“0†Ÿ“ÇÚ™ã<^·¬³!d°I•V$³Ëã!µ1´¢Šª–œâ4µì´"u'×3¾•6ZQ"-‰ !`rGiÖZRîtÏ»Ç̸“yÒ}CÎ…ë[¾@y¶-ö™Tâ1ª°Ó¡RB>¾#‹× ñ xQ€bÌÑ8P☠Œ´éjÑŒ>0K9 ˜F¬6»p ÕpîN&;ÍÒõä2k½N“v\f¿‘u¹Ä?Ôô’h{9Êæ_ç9Ac¯»ÝI=h°‚ͤWB¤rq†m&vxýçÄç;¥…ªïlp’ÚŸÙØÆ)®:jdEØH*ãX›¡DøØu«Hc óHg”Ïl$$œM&Ÿé^ #Š£8(í×Ne#/pÍ¢·Ú`ˆx¿1”S&Lêëß8Áiúqø&š:0Âör‰ñ<°QÐÀÅ"ât ½ñÏèÐ[rNÆÏˆ=¿7¿Q1`-i(W€9~ûNmܼàç§Wá&›ENûݰŸ–íaâÎã¼Í#Ò…tÜìñãnF*nŽú1¦qšlå?{£âbnÀ~¦Îœ`EUh=siRs{ 4J<á*”$hࣄUEûÇJSqËaé-ü3N›˜d?Å /Jˆ„Ê5§ vâ9c«V™_¿ë= K¸ÇÜuI—[ô¡Mº9{ÞO¹dI•P¹!×ü];'aRF*H`㌜´Šæ—mdÏÝø– #&}£qoÆ @ªÅöMXK;Êó)Զ†ñ«q1Ì>§ 1Ìó¤)™Le¼VMs.àoÔ$N5ÉK|Í`n°Ó7ÆmdÛnúsžÔq8*Ïq”þìá09Èô½$òk‚„â¼gÙ΃ Q[nÜò[„u¦Ý5|W![¢`ñšAC0i²fé<“â•ÄPB3×=g•f"Œ#̶lo“mÉp&àf¼Ö.èÇ5üfð ·*8ü¨vR€ŒOÒÈ:öRÏËsy„cF¶Õãçhà%ávpƒ®f G2äb,™&)SCU›–‡ù-—1·n¿ qê¦Z¬aÿóâŸ"TcU;ÉðøMùXD*—Ä,‹§<¢€ï3ÜÚGËÌâDÛê’|b=˜(ÆZQ B#ÉÄßèK~+Y)@t%Ÿ)í¡˜\åª$pÕj­Í Îñ°~÷ˆÍ$/e´çŽú³íÑ€Ùºôxåïù%ýÆãæ´èåSYaµ®-ÐÒÆãøƒTkéòò .[ ß’Å@kQ3 õØŸk¨5‚7Ï<,Ëh3̹ÿcx„~·¹’‡ÆOÖÒ=Âþ¦†—þ«µtu9Ì9:xÑŠNÑÈZ¶ï¸Hg«î$ <ÇySLïÿ‰Ë³êÏ…ˆçLíÔ‚0½à=À‡” ˜¹TÃ˸ü‘œRNb ^€6žK®²î»M¼-Ò!Šð=×ÙF6£œÌ1Ì%Í„f–ã`¸N"…N·\¸?xŒ¿)áXÁá;M,¼:âXÁáÿr”¦Ž¯çŸœä_®yqCãiOn$Ôe ÑÖƒ:µ»ßl9=ï÷Ø®©À¢6%ã"N× ñSWè‚™ö#3ÿö’ßÏì{tãG‡2š[ß2p㯾í³ôö3›€åcg>D‰EÌ Îy°t0§­êB´($4*SÛˆ…K„e-¹$]¹$SR}*%Ž•IuùG*ËËî0ßK’ wË~Å…¤‘äw˜i!Ϙ^§o9Vp¨#Cå’œ–RÙa¶ôÍÏ/ÊYyRú4,Œ‘@Æuë¹å“X ÑÎ Y%Cµê‚˜ZO)—®©¢ô—¤I`#i¿é9ŠÊ é&¤¥ü&ËdˆOßßÅ2H†KsÙ,ïʯÙh™-eº4“yò†Œ“\^­ÂFÉ\ùÙ´Âõ?"%CÊÉ*),öÑ]–É5i!}ä>ùJÊ{ÍÞ'kE$A:ÈÝ7gëûp,×Üü¬›´•+7g‰óÊÇI #U°î*J×!Eëç/_,xRÐççýa=vÆàà$OìçžK ›¹ý5X9åØB V ‘mc†V¤c:XÙ¤1,0´¢0[ü:Ӥܷä þÇUšó„ÒŠ4LÊ Üô$“ ”µÙm‡8aɶ6J+RN³©ÑÃ’u{9Áý¶Íòšá\Œ$^ƒ/ì]RÖ±˜öûùßZQNÝ¿sÉàoÚ"á^Vsš6j·­#“S5eƒY:¥·*Héyô%ÄZ¶¸ø„8­hJoÍ`w!ôÀÿqƒAÄòY¬QºxsCªê Ïd+¥=ì¶‹4A+8T5lµÇU^›P«šä¥y—–.Ül‹Ž>UéytfQåEbAd‘bVáaë­ ¦ŒÆø™û6º˜yÀª”pµ‰àß-“<ÞJÝðœ-Êwd˜PkÃOlšdœj CäQ±àin5²­¿ñ¦I~Œ³±ÿxi’/j-]Žs†Ž^‘ƒóübìºÒ†sÙÉ‹Vt”³4´4äK´%< æ®Ù¼GqÕ©û¥Í¬éÈ«\\쵟,-ˆŠÒùŽjG¾ï¡>ø‹6|Z—DA-v“Hwfs}4ÉRüAnÊ€æõñd®žc .«RB¼À­Ç׿{áÆ×òqBäö¥Ëd­ù› ºl Ú^L Ösžîw}’£´â ²™Ìƒá¼ÊÀÍ—â 7sëj¡.‘¹Š…¥^ü£ˆžÎßjÝà@Ó‡:RÖ¼¸N¦#áN¸›M¸ÉÖlR;JXW±Ì1™?†ydŸ[ôÖP 0‚[-DŽf Ñdª—ÄÝH&‹Ã4rœmÍyR¸L?|ä?É ^ä¦ y¢3â4›¨£–\OÞ©§4J  îÊei%ÑRpI`#B0AŠ+’[Òä¨àØ é‚ Üü#ÙŽŽóVíTGÝëDŠhóX$V Þöšâ%TL D²$B®É1qÿŠ[Âåºuœ=~s&VnÈiÇÙÓrÍ\×!qI˜„J #ËÊ-ŠÓÁHˆd‡d‹ ÕJHXv”K稠.L2%Ì•j2K.Ë{IHq2[ºÈç²M>‘å}žô‘Õ²A^”§Åw –™ò…¬•çd¼e×å–¢rë%…Ì ÔJÞ—ãòžÔ’e’ÛÁn[%—e¡”•Ϥ´ƒÝ¶TòÊd‰’÷å^ŸÙb²XêË9&«¤¡¸$Dî| Y‚eEz|áY¨øðžÁ‡,5™‡•¿‚²H@èM>6•„ºä/OUY)@³ ¡»HòŠA„ó&°ŒXJðÒŠì•6R˜hV(­¨Û¸õø×(öM”4šLS@Ø~Ô ÒŠì÷_’ï¸@¥UðŠ¥­ÁÅëæoœ¼ (±ø»=Õ=YÐ`XY XXî¹ý–W’†ú>8ç«ÛpkîvEqÆC=Ž·Q€ÊzÙuáJ²2å0´"Å[dó%…½hEȭǺљ‹ì§¦JøÖãñÖå'T¶u#ÃöLÞ¿¦Ž±öJ+2 ?Oè °‹Eä ˆ©Ó™#ì±-Ü¢6s@Qvèž»´ ̯ÀgWÿîI’-v^Ÿ£œ±LòÜæaö˜½‡Í$ZKaU¸‹ö06’Î0ÝméšqcÑŠÈà5šp›a,ɯµ©„Vp`õ€ksŒ#6e/ÒØL-—‘¢‹ÜÇ^X»í+²m ” IÁ€î1þuX¸…õõ|ô<*ß/=tgaÍÞ¡5ÝŠ"¡/óRÿqŽúäa!.Ÿf)åØA:š(€›%¨&©¡Öl‹A¦Za/ó2VÓžÛ™ŒõŠi‡»îâ¨É Ë óDgC¡-I)vqŽŽù©•L¨*>^zo뮫çŽS­r^ýybAdžâƒ’/o/¦¾è½A︾Üå°8À†b>Ë!P‹\4±ïÏ쟬ã†qwmpȸ‰§;½¹ýøˆhB}dÊ(Ó]àoN;&… !…­¬å,ƒãüdÈ^÷±5›d„Ò쥅ž ½˜Sϱ\Æ;Å_ÙRBáÿ€/¹;УÒQ:>@ ð™ßΆ—̬3µ­{}~*%æen?¾ ¯#=|4àö›­óÊ-Ã:á&9d5!> ;îYNëÂ9à¡!bAb³"R¬p‰¤i‚\7ØÈ’ »„K†4æŽt¼þ+×äaû¬ŽvRêæl)éå'Q\n?â%ÎÁ¢¬%$I2¥ŸäßQQ:ˆKÒ¤‡”q˜-.ÝL§¹Ôõ6ˆŒ™uç#D¢-'S2•xêqëjy.\tjÔÅHåž,Û(ZE}X–oËÓò–4d‰OD.BfÈë²ìæìEY!|nf°,’ ò°üO¦Ê“^/E¨ô–BRZn?òIa‰û¨-k¤€´“ÒA>‘X¯ÙR²TªKgé#oΖö±ÛI‚<%m¥¼,±ÇëÌ“Ë há¢,ýˆ„‰.\–çÂÉ|Åò°Éª¨=b ÀÎCÂc|kãÝçµ(@aF{;lëp£ bÍìҼ캧ÁªTܽbí©4a·‡iÈ³ÄØäîÎò¨U­(ÓVÁA(m(@ì´"EQ“ö<ÌÒ0Ïð •lDF„DkÇUŽÑXéBO][XÍ®U*¦ ü6ŠC=r6:oº¡NáASOÁ;a*8¬¤¿sÝ!¬Óƒ ~¢k¹A?‡ºCÉìã>>%•Ù’…òó1.Æ“Oã(o®^XÐÏQùv÷&„xTHäÅ€9ð æ`J&)Ž›šì#“të¸g5AŸõŽÕ ‹ò5é¸- yÚ“³1€<¦‚C*顽£<ŒdÒ9Åcˆ‘ÈuͯõÆ#!•¦Ð„BÏZj-Üp}.U˜"m‘øqÀDPÎÔÂkæCÜÁ´¨(ÆÜ&õäaî¤ 1| E½Küºçcð®j¥…x}ïÜbsś奘ð¶eÂp.0—9ó—øËIåò801ÜÄ;¤²Ò!p?p…Ù\e;Å}Òükr‘œŽVä÷©àÎû¦ÀŒöõ±…ZÓø‘5dÐß‘¢qøŽt¦yXªÍ.xP•¸™I„à“¾z‹ª “º÷N S u€4^#*@×Mâ|R¯² ~ˆ¢µH¾ó~,Ë1à0e^‘v\%§ã)û*œÙtGñ6ƒüFÙ»/ñH@hÁn.3Œ´7ÊR[EXHù¥H¢Ú‘í-BªØÔ%g¥´Wআ¸Ä-ù¥²8š*©RÈj_ArKŠÄK5‡ %”•ƒ Ä'Žq·±$«8ÆBŠ™»ÈZïhuÕ”lÞ)ãa2œ•T l”bæÿÏË?‚¸+;üx¸xB&xaؾBzJ¯öLŠ­Ìc>ïõ$Ú›ôøˆF˜„ü&â´ë¾·©ÔØTpxÔç8›N69+=ãÒJjÏhà}O•B)@.ú3XïÃÚÊËJàz“Î :'üÄBÈ ÖQɹΉsÇÆá?VB´£½€34 ¸ReG=ÃÍÃý”\V¯cÔ²[}êJ6´"¯ êî$ñ;Ŭû‰+¶¥ %ÔfŹÉ$ÃÛ²þ£ê¿É$\Œ#Æf·­ÂÍ“VëA‹V¤ˆc9nfŽ˜¥û2æÓ„€{Ä3• Vh&p,o˜u›Ncƶr[ŠW¹À5˜•CsK͈ð¢5ç ÿèÒ…0—R€ ­ˆ³4°U¸Û¦@y¯ íhÊzt˜¶F•©BUªQ•*ÜK 6£ƒKPÕ¨4»¸¦çB4ãL¿®\6»­V¤ÅòÉÏ—d1‘0 [)Ne°jò n “¹äÖ—çõ]¯‹âýÁ´ê2-½’¦ ‹÷)p?ÂzJoµS€s…¿¨¡å•¤¥©÷Y&ykRøå¾X¡Ö–IÞŠ=´b:Ø…fÓ…OÐaº ƒ‹œWYgG?®³ÃœåGCõ4ûØO2mxšd¬A݈¡6G8Í6Òˆ¡=²oZþŠ - ìEJ1®·‰v,ƘÅ&ÒYä°°­¨D™ÿžà"=õ„kϤ7G‡æ GꢲÏ/èÑb~¡qÍ5GrŽß¹Ç¡ðgÜÅCœá˜ÒŠ<)@_S˜ lå”O&zwÓ,¥¥ø… ŒFשEÎrˆúä5´¢á„íQky˜Ä0˜H´×ÕþL]‰dð‘WÚF!¾2‡d”Õð³—\îDµø<¼K{iîáƒy§Ö­F(FGNžR]}dïàà*Âh˜ÜÅ(ô¿SʶŸ2YeÝx39LMÄKX­½Ip]Xë$ø×Ú¥Uyšèà*5YÃiëø-n¥‹D±üF.Â5«Õ³âz ~új£glÅòãøxÙÊjoÑŠt/ò\ }>´=€‹÷T‡.À î^ Þ˜Õ©¡Xdhê îB}ð¤©ú_Zipiš(eÜφVT[¥±ÍC“¬Ë1ÎÐF¿ko–ǧ6±!Ûiæ!ð7‘ʃlD;ÈO„­‚C¤öFN¥Ÿ­Wkº†Zó°“UgŒ´…ZcÊKÙ»ÊtI …gõoTeœ幖‹7d¹æ×p!¯:oö;iD»ŸÎä¦/×ÙN!+LºÉcÙ4ÔJu„¾¦ |Y;qÈdúÜodÛYþñ‰ad¥ÍJ»—.^Ù¶ÑßfÉí£¢Y*m‚ÆxB(l|üݽN‹QÀ "ˆ5Ѹ „Ú~Z¡Öp¦òU‚ê?ô¾>é6ˆÚÒó¿]2Åèè ß•Ó_÷±Dw´ºÔ7±Ýë|ÁËd°É¡Nx/²ÄUvrƒqp€= â§l1ùPJò­m7Å;øL1—GfjÝ//_™º}Ë5Lê‰\¼Lc0pÖq& ™LSɇŒŽéYO¿kÓêO_È4GLœÙ”Ç&àâ#*%çJ5øÚ/¯1W5Ω]&«-'£ GYÛ1ø¡£þÇ@R±GiN„mqæ;çãhe>€× ëP†÷xHm¶Ç*!òuBTÙ™°FIy6„Æ‹BÖU¶@Üò«ì³8YJ9 t¤Ë©!Ñ’×ä°”t „ê\¦”ñS=¡˜ä¿9ïU‹ ‰´ý¶ãâÈœ:/™ &1^™>¥MÂ|9 sÌ"ª`*st,þ*oÎ%Éoò§ ŽjÒÂJFù[¶i8§|¶üy9ë²øB&øÁ[¿•Ô<‚OqpáäBF[†ò:WùÍÁ‰ö„Iªèg¼!µ,¤ÓüMOŽxÑŠB¸—_ÑÁ@IS‰‰´ôˆ‘ŸáqÂmUŒRh¢!¾í˜bYIS‰‹>ÊG$SÈfãx ѼH¢¶Èϯùõ#“îž$NI~0v\{—uŠó—pók€5´;–±Ì„A¤°ÝK9iM²U“¼'‰ìQ­Q›¥ÜD-OZ‘.\›‹ùq&ÑÙ‹û²‰íTãÖà͈P`5.^Cˆc%0X—N)@L5WiEŠÆS•"‘€q·)tpžú÷«2bÃq†ø›˜Xà…¤Ü*[þTí© .°Šö=íÂE~§Œ‡br™¯)êA+:¤š£w³”ÊÑ2B(w± <ÀbS@Ø&mJ„ÇÂýͽjlÁ­›c×EÍc…Iô>¹g¨5–À4 Ñ‘ÂHÀˆä.€­^t¿ô‰í&Š3Äß!o®n ºÍLRÀJü)‚ŒÖ½ÒÉtÿ-‰1·¡PÌF+ºÂNjy4«f3UéÒÕ¤zø!ËSŽÍ\µ‚>÷Zî„hæOÊmvÛ\6 P +É`,aJoõ¤E2™,&˜¥‹`.fÃC܇¢–k®3‘XQC/|ý¡8Ãùcƒ9 û©v“ OöÐ 1tVéöÉFÖõ0µ–ËùD’ØO-ZrŠƒ¶e³(´&ÔZ›“Ìb*Ö`3å¬bùé´7„»D+LZ€Q $ßô§Œ “öö!#,FS‚åf‘B½¤ÑX²˜Na&’ÁL¢¨K³Ì£‹ÚÊGhçž0Ñ¿š*øÅ;ÅŸ?q— ÷qÉLb‘ PŽÎjï ä28ÏÿKô4Ç“¥¦c±³ýìáGiGK ÃxkßÖæ7ù•S*ï ÐZÌ“¬ÊçɼêHøœkfv¡vÝ»¸øl¦’‹’tÕ» TòÏ" PQ_ðÁ©êêr€øŸ>}ÐÃ*Gºq\ ÜöHP¨@7]üy€›GÌà?³ÝÌ£¿‹í¸iA„j¡.`„‡"QØŠ$S‘¦$ëü&:]6ûÑ¡ ²ø›8ŠÒM­Æ@ÑžÀ)zê‹R— “mÁç°Ž3fUx1ŸzPÞã”-„…ºt$Òȶ œâ¿PÒ±¨ÔIÎr‘Ž-+êpÃ\ä- lQþÛL5׳¯YŒe*J<‹¸aJοæh¬ÎU’ćC0‚ùÜ0‰!ã鸖­êÏ›¤P^¯´‹knã¹âr«ÉÑ¡—©ØåiŽ*]/ ¡Õ[3˜AÆñ‘>èÀÑX+ÊœfˆÇ™4öÇ›)8âþ§ &w{òZ´r>"€Tf‘ á<Ä/üd¸îjmÁ9öRÃN+RÔã¨î¥ºÖÌkÙ”Dv*­Hˆ°Â?Žh‡§l»m50‰óï/Àj`"ÙÍãTE‚DÏqMÙžåõµî˜1aĹüM(ÆnºWUùvü®ÚOK‚^ºVtÙÇ]g]:ÝL˜´´Ç{yŽ#4÷¨n`UJÐt‘=ªÀÜË^[¥„²üäa×EКoL¼M+8x4A+`¤Ú<†æˆÂü—Å‹4ËV w³…l뉪 ŸŒØ=±€šfwdÇ)Æwî—ª7ÉÛ¤x!$HDÒ„6„yôPü’fÙ.ò¯—Ð\C­u9jÕÿPð Ç9Js­”ÐÕ+]d+ÍÒg gèôÙhUpÈÍ—öÚÑjK¼F§ÎÅãÔG‚F8 œ0Ù ¨Ò•9îÅqrkø›P,¶£ªî¹ü­&¬î– –îÚ¡R4™­ôà’}ÙÔ$?Ï^:sTÙeÞñ›x’m$;\Ù=ìæO˜lÒ' qèÕšÁ>3ôÖP?âçd2ž¤›ü™ºP‡$häã²LYî(Ý…¯Ј€_øŸRÌzD½gäe"©¸ù” ÿÉ[×Vjô&ØE?äÚKÀY?Ýšõ˜x£»^÷C‹[ ¸í¸ ±,àm"ˆ£#Õ‘ÿ­ÔˆY£ªY(}™Ñm†Ü¡7ävÝZ|I3®É7²Å Á´•/Á—ü,¥³‚?˜ÿM§‘f3.?³Y’ff³üÌfYu œF¦d˜{Jf‘TëZ«J+9({å¿¥¤«U¶í¤|!'4#©Ä·±_ÇÊí s€zdåÖÃí5uˆn§Õï¯B#¯CØ¥eMÑÝÝ<Ï ~·çO«öx„g¹Èa_2Ÿ‘m—y…8C3ƒúgÓ±î'’í™>Ú.>›a¬%—®y0‡ÙüLK{ !ôçnæéÝFщymæÉíq«IÅèðñ_×ñpY}Œ €4ÞË9ŽöŒa.ïñ6¯Q‹HŸ¼žÍ 2 C+*åm·YU€Z8Tp¨nT’Ç¡Ž±ëì/Tq¾!~„pI!Á§‰®‹É&ʰ‰,úb“£›/ŽGè©Î­àQƒåêýíâqc~œ;Mn[O+&u~&)^ß•Žü¡­Ý;šÃžhãØE¦2±¾a×ÒEӉΖ}hav]]Ó“#ÁF+Ò]Göp‰ÇÇ E ¨âQÁáQ½5›ñ˜Å2&y:lÑ7S(DZ泋Q•{XÎh^á W™D¼^Ë€Ô }Ô ÊŽSŒ]Ô<[ýçŒ#«©‰Ü器mž]ÖòPMJÁýt´HDÏ’Â* [{ü€)(o§í¢šVJ8GWÛo9¨Ý„K² ·M%©cì¼ëQÍõ¢•6•¡Ÿ°l¾QÀ'ÜGS8œ ø˜ÙÄ[wÈò£){pó-z–Ñ${ü’ñ’3HN¿8±Ððã*w¨`ZÑ$3¢·­¾0…«¤²™Ìc9'C $‡—ߊöfßu!ÃØuuø—cöRiJ¡­JYþ$ÉÓªS íQ„­À>ñ‰Ÿ¹N{+âý2a^òp™¼D8ówéJ3›@P#™“dpŽWÉuGf÷Çdpš.ú×K2ìäÔ"S%gð7á„N}‰D‰Ñ¡;ÍKÄä¨SÌ fӈ؛(ÉDßPÏÁ¶«FojÅ`s$ãœ#ý½ØÃ¿\qŒÖ3}¶)½ÕŽ l4Y@iLqÌØÈUs¤~Ì“T&ÒÁ.œÈEÀuË)xnåñÖé“ÎûúJDЗI]'IN!9ÿê’ØQk›xø&*Çù[Jº*¬°äÚTÊ{È¥/p“Ê™6$P’¹€›Wn™­ó’?+I³uœP›«ÀZ¿§³$1‹VävüÙÙ$áf;XÍ `)U¸›²·¦uh‹ùÝJ„oƒ“Œüf~îù’Sø›pÄÜj/œ(àaK6™|z‹Ìœ²øÄÆ«Ïm f3“"~åb>æùÕñ;¥Xgïª9]\IÇØE}?ÁËLNqG_›Á|Ï §<£·~@Ù|Owñë5|Áz¾¡'Q·­¶´V£oÏè›çÏÌ®:[r¹£/‡Ìx¢—+åe)-î“üݹœãqÛ®,À\25T¼Å![š6LeÛ)‡øTJ¸J‹RÝGm%›^Tã0§yćœ° x‰*FÖy/]žãIÊî¸l ùŠ à4ý AH`–Â5è–KWÎXŒ×˜£ºAÝ™5À7•*°°Ž3¢G­¿ßÃ;HÓñšݪ+W½(çÕØhåÝ•¾Mࣳˆ÷)ía_U6ÌÇ-Y—ÈjÙ–m Ù¨Ǽ*8ij·EªÀO$zDŠÓ†®TºÅÃoÊJÒYO5Z²˜t\œäY€›•·xK0…$mA¨WL=F}ïYÈãî~T£Õ”d i*éú;Þp[G©g³êæsÈâ âl{,Þñ=/@'ј¢æp)o+É&4'ÙƒB[Œïl Ú­HY\6 Pyþ ŠP›Nt¡¢ãN óx‘[çLKYLªI„îN/“îûü¦Æb¤f¤#z¨?6¥ò¹3øð©O •t¤· ´"ÒA-y“"ºl 8À~·é[­™Mm?">ž¦t¡ñ1G½¼½HdÕ-/I¦è>YÚ8æøTJhÆï¬¢ÍüªòÒÜã0ަ';HÁE*ï[¯äÎ[üðgâxF 3^b¢¢s1€I'ÉB&€±“T‹$ŒÇTÒÁ÷ŽIþmø8Ã8ÚÑ’Þ¬$ € Lð8^K3‚½¤±!÷Ky(F=ž`8m¨jóy6á"ÿ0€M^•4Ôj¨8Æn³®0–Š´¢+©I©[ä‘Ve{ù‚š‹ÐW«<,µŽáÌ$ƒlæçhôV ^ ³<ÄCãÞ'w ä‡Ç[V×CC{¿Ô+ò5•¸”ϲ¸Îqþâ2™\f1•=tµYÊí?ÇDòÜÆU}ïÿÛ»è,Ë;oüŸgKžìÖ€  ›Š‚¸!:¸U±U§îŽí¼µÓVg¦ó¶z¦¶ÎÑ:Î[G[m[_µµ‹Në´u©ã¸¢UÁ]QA4²# d!$äyò<Ïõç<““I TeSß?Ÿï 9œûÈùÝ\÷}Ý×õ»Ž go-ÅŒ0ª»IñäÐ:Bºßy›pdXZC&ÜŠÃà0-œ.3ÃäP÷'ÿ¦Êp^øy¾[i~¸ Dzâ›áíî•ë¿#CÕÖß/Ëï×<³ßö¬žBgïÂä^'ÅwŸ¸½öv=± ”~ä„tvшÏ4TmiKU˜ QÕ{ÇF½uY"g:U)4zÔuêA™³\â|Ò²bJ½ë!;’ÓaƒÕ[%¦Î‡)¥ÈKýBV±¤"%[SªL™J9µ{ÄcUi³ÐëVhÐ*¥1“|Õ—œ iŽÛ= ¥›”ÅJ¨T•¤Lñ×FyϯýNʶŽrµé"žt›y€>×TõåÎw:}ôDn´sÒ¯½ýî‚fÀ0×9G1hö{7¨×W©œmº"ïù­¹Þƒ\â ÓÐâIh›ý@ƇS©J­JIÅbºt b݉JéÐjV­Ú}ÅNw¾YJÁ½®÷º¾†¸Úy*°B‡ñÞö+·kÔWľíqð¶ýZ'(v~fä7Ýbç\»ó¹ä ¡¼×¼ê'aKÏ(~O˜Úïù)á„pJ8"Œ ‰îAoføQØØ½Òð­pyþ¦åÅ>-‚Éz[f´{£ÞΦ2\6…Õá™ !¿i¡®Ÿ›zöh=. Õýü+N ‡L϶ƿ E=Ãý9៮ü';;ÿ¥!ò½kÏíuó<-ÿ뿵…{v8Ÿ‰ôº:^ mù‘ÿ­E¾‘ŸÜ>F÷ÙÄúõ­…¬ör&„Ÿ‡ï†ÙÝ·^‹ÃùýœcÃmùÓõñpf(îç;=)¿¤ôßV„o…ª^äþϽù¦üŒ{w!·_ÿÓrÑ^g×oCº§tw}@+ûÐpEX²Ý†—‡²Pþ5äò»YôúŸ|è sÃ?„ÁÁLAHÛµ(—„²ðÍüˆ Oöûýî/書ú{Ê2'tõ¬\öþy©“ÂUoÝ0æ;ŸØ4-;TWì•áS˜e Xa£bŠ¢À85kl/îX_ñ%û‰Ø,&!%ªÄùFhw¯yºg:WÞò¸TÏõ¬ÓîTe”óhœ‚]Ò6˜è•6zCZ_ë¬ôºÇeõ7Ó·ü™8Xég~¤ 0ÖiÍÕgyÛ®¸aó½QÿøÀçz†¿X~ÀLõ¼œéápj(è÷üþbøCèÈ·NÜ~6äÏÉóŸßï5¬Úz4—Òð…žóø”ð0;Œ‘`·d|ø×ðfXÚA@$|¶»-ã­|da(> EáŒðBÈöô¿~-”÷Zûƺkg\k×›!³KɵD玘iÈ2¬Ó¬Ì8QÄ6B³•ºô•Uo‰œ6÷ø‰—D%h?%"Öû©õ€£\¤sÝ®$]⯜`¨¨wP,+ø(â*UIÉ‚bGø¬iõÞloƒQ¨FÊZÇ:Ê2ív¬Èi.7],w‡ÛµšÝ9ôâèãQ»–؉¢»˜È¦Â§‡Ù>tM¯3n¼¢F,¡^J_]VyÍ÷iÑä«Äå¼êwZAÌ þB¡-ñ.0ÖŸ›ªJ“ÿ²Ÿqž™Žr¨ýÕªP$˜¸, *®9q¡Ï;ÁñVi­êLW¢R“92¶·ÅÃŒ3R¡‹LÒh‘¬þ q®¿u¸Xì?ÖÚS¶³·Œ¾4ú›¨]MK徯ٙI³`°ßX©?›Íq˜c,é9^åXeh´P D4Þ°ð"¢ÒÎ@Ê ~®È 5V‚-ÚUH-¡ ´XgˆBCÅuó’% ‡zͲZ¤eýÒ=æéПþÒŽ›üÞm^Õ¨vzfâw*n®è²›Ä‡ØÖ\6eeøÎc‘M Á­Þw©CÅ7Ñ7ŒðË~fCd=%e–2+ CE‹¬ 4K =j³‚¬B•Hkð®4"h×h¨ÃÕjS¬Ì$QD•‰ë¦Ékf)w€ýDå@‰ZI‹Á“†ùƒû,ÕŸ„‰.öçê~áÿzW7ÎluÝ-²v›x•ÝjóÓ×’L^þ¤µ ÙhôǨƒý/cüÂ5ØÞ<9=Eaš„œ¥Vz‹ˆK¢ÓZY$j‚+ÕH*Tcˆ+×èð¤/*—4Óïm0Ìp3ͰØu6 ÓʵèOã]l†JÀ»îw«•t«uJ×øoÕÝ&kwºwäús®hžÞgâ5áÕð?Þw„Ï„Ò|ŸÜùç€o†/äצ‡›òSö¶ðõí&õÉpYèè~ÕSd›k·æ'¹­éÖýê™>›)†ä{QCX¾¾ Ëò‹½‹ÂYØßwS¯y·„ûÃìPÝgëÑß´~ÿ¬ïÛ݉M׺Û]X=oĤ԰µhò†µj å&§ÄzMv,%f¬!jUe¢S\¬ ý¦ŸE£c'n½»­Ñ×3E±ÒóÌ5Ï»&‹JyÉ;tëTa†"%¦›m¢*Ìó¼…–Ø‘1>ïoœn`¹ßø¡yÚév´YëGœy4bwÇM{*îyèô^ËøÑprøIhÿ£1ü<œjþäJ÷áñÐÖ½«*ݽÝáòPÕÏ‚Ñ-ùã‹¶[“ˆ†¯çÔ‡ï…éaT¨ ±ð¿óûÎÖmýéÓñúFÏfÃ%ágáá¸P±Ã¥¡d˜î «B· óÂ_…ÚmZ6¿ûÊõ“¯·'OÛCVûü´ ¾äÉ‚¥ çQ+¼á &+5.r —ÜëM­ú“ñV;ÚluÊjË_öÿS³m•,†.úÊIÙ"¡Lƒ—åÀz-*U(Ðc¡§íg“w=â= ,•Ó¿£œìL3Ä žr·'uÒ­Ô G?á’Ì {D¬*$$mVo[ Wm±EžÐˆ‰Èø0êŒwŒ“Ô¬[þϺßóÛ<4¯t¼ UÿÈÃö‚Èì=ÉC¿_?ë ëèQa¢saªÝ26z=6¿i¹`W •Ñ!-í£H˜¼5;Ùpåtkð¢Ç~×ÍŽŸ·ìÔ…U ´Ú^TqªŒ3ÕhŪ•‰è_—]2²2rˆäSORD:µÚ¤É"¯yßrKlÒŸûÓ5ùñðÛ+ïàÿç…ƒ[Ë¿äê†Ò—­Ò¿"•Ê s¨IjT¬Bµ˜•Ñl£UZ¬òŠw5Ù¤M—þÄs u?‰Íy~ž¬]älŸÇn9«éÒ–!/&—Ú"§…ŠT¤Î(ƒ(5ÌpUJ%Ez]×ô=Z4iÒ E‡õ[ê}i[dõ¯@™ƒLl/YT{sùÜ÷WÉùDˆ\ã“evâñWvŽ_·Òz-‚‹ X…F©Q$.&©PB¤9Ù|:u蔑Óe“õ[ÓªY› رµ†ßj cñú-çm9<7,’l°²¢%¶Y›´´”.¹Qʘ˜¸‚üG‰2å·ÜTÒª­pQáÓ.Ü< #åS!ò°OŸû#NNšÞ/WšLthÜXÝVÞKë’‘–òÉ!Ò}½‹Š(P(™-Ù\±©´¥b]tSzKbMì½ô|/ûŠ\ëÓ-W\6<>@y¶(Sâ…‘ÂH$’Mä 3ÉLa.O'Z’iY™ËEÒ±ö‚¶LK[³Õ>í®Û—Ocön_áöe_áöe_áön_>Yùÿ£S^¨ªIEND®B`‚flask-oauthlib-0.9.5/docs/_templates/000077500000000000000000000000001327671251000175235ustar00rootroot00000000000000flask-oauthlib-0.9.5/docs/_templates/brand.html000066400000000000000000000007511327671251000215020ustar00rootroot00000000000000

Flask-OAuthlib

Flask-OAuthlib is a replacement for Flask-OAuth. It depends on the oauthlib module.

{%- block footerinner %} Fork me on GitHub {%- endblock %} flask-oauthlib-0.9.5/docs/_templates/sidebarintro.html000066400000000000000000000011671327671251000231030ustar00rootroot00000000000000

Notice

Flask-OAuthlib is not maintained well. Please use Authlib instead.

Feedback

Feedback is greatly appreciated. If you have any questions, comments, random praise, or anymous threats, shoot me an email.

Useful Links

flask-oauthlib-0.9.5/docs/_themes/000077500000000000000000000000001327671251000170125ustar00rootroot00000000000000flask-oauthlib-0.9.5/docs/additional.rst000066400000000000000000000071761327671251000202430ustar00rootroot00000000000000Additional Features =================== This documentation covers some additional features. They are not required, but they may be very helpful. Request Hooks ------------- Like Flask, Flask-OAuthlib has before_request and after_request hooks too. It is usually useful for setting limitation on the client request with before_request:: @oauth.before_request def limit_client_request(): from flask_oauthlib.utils import extract_params uri, http_method, body, headers = extract_params() request = oauth._create_request(uri, http_method, body, headers) client_id = request.client_key if not client_id: return client = Client.get(client_id) if over_limit(client): return abort(403) track_request(client) And you can also modify the response with after_request:: @oauth.after_request def valid_after_request(valid, request): if request.user in black_list: return False, request return valid, oauth Bindings -------- .. versionchanged:: 0.4 .. module:: flask_oauthlib.contrib.oauth2 Bindings are objects you can use to configure flask-oauthlib for use with various data stores. They allow you to define the required getters and setters for each data store with little effort. SQLAlchemy OAuth2 ````````````````` :meth:`bind_sqlalchemy` sets up getters and setters for storing the user, client, token and grant with SQLAlchemy, with some sane defaults. To use this class you'll need to create a SQLAlchemy model for each object. You can find examples of how to setup your SQLAlchemy models here: ref:`oauth2`. You'll also need to provide another function which returns the currently logged-in user. An example of how to use :meth:`bind_sqlalchemy`:: oauth = OAuth2Provider(app) bind_sqlalchemy(oauth, db.session, user=User, client=Client, token=Token, grant=Grant, current_user=current_user) Any of the classes can be omitted if you wish to register the getters and setters yourself:: oauth = OAuth2Provider(app) bind_sqlalchemy(oauth, db.session, user=User, client=Client, token=Token) @oauth.grantgetter def get_grant(client_id, code): pass @oauth.grantsetter def set_grant(client_id, code, request, *args, **kwargs): pass # register tokensetter with oauth but keeping the tokengetter # registered by `SQLAlchemyBinding` # You would only do this for the token and grant since user and client # only have getters @oauth.tokensetter def set_token(token, request, *args, **kwargs): pass `current_user` is only used with the Grant bindings, therefore if you are going to register your own grant getter and setter you don't need to provide that function. Grant Cache ``````````` Since the life of a Grant token is very short (usually about 100 seconds), storing it in a relational database is inefficient. The :meth:`bind_cache_grant` allows you to more efficiently cache the grant token using Memcache, Redis, or some other caching system. An example:: oauth = OAuth2Provider(app) app.config.update({'OAUTH2_CACHE_TYPE': 'redis'}) bind_cache_grant(app, oauth, current_user) - `app`: flask application - `oauth`: OAuth2Provider instance - `current_user`: a function that returns the current user The configuration options are described below. The :meth:`bind_cache_grant` will use the configuration options from `Flask-Cache` if they are set, else it will set them to the following defaults. Any configuration specific to :meth:`bind_cache_grant` will take precedence over any `Flask-Cache` configuration that has been set. flask-oauthlib-0.9.5/docs/api.rst000066400000000000000000000021451327671251000166730ustar00rootroot00000000000000.. _api: Developer Interface =================== This part of the documentation covers the interface of Flask-OAuthlib. Client Reference ---------------- .. module:: flask_oauthlib.client .. autoclass:: OAuth :members: .. autoclass:: OAuthRemoteApp :members: .. autoclass:: OAuthResponse :members: .. autoclass:: OAuthException :members: OAuth1 Provider --------------- .. module:: flask_oauthlib.provider .. autoclass:: OAuth1Provider :members: .. autoclass:: OAuth1RequestValidator :members: OAuth2 Provider --------------- .. autoclass:: OAuth2Provider :members: .. autoclass:: OAuth2RequestValidator :members: Contrib Reference ----------------- Here are APIs provided by contributors. .. module:: flask_oauthlib.contrib.oauth2 .. autofunction:: bind_sqlalchemy .. autofunction:: bind_cache_grant .. automodule:: flask_oauthlib.contrib.apps .. autofunction:: douban .. autofunction:: dropbox .. autofunction:: facebook .. autofunction:: github .. autofunction:: google .. autofunction:: linkedin .. autofunction:: twitter .. autofunction:: weibo flask-oauthlib-0.9.5/docs/authors.rst000066400000000000000000000004441327671251000176070ustar00rootroot00000000000000Authors ======= Flask-OAuthlib is written and maintained by Hsiaoming Yang . Contributors ------------ People who send patches and suggestions: .. include:: ../AUTHORS Find more contributors on Github_. .. _Github: https://github.com/lepture/flask-oauthlib/contributors flask-oauthlib-0.9.5/docs/changelog.rst000066400000000000000000000000341327671251000200440ustar00rootroot00000000000000.. include:: ../CHANGES.rst flask-oauthlib-0.9.5/docs/client.rst000066400000000000000000000312031327671251000173750ustar00rootroot00000000000000Client ====== .. note:: Please read https://docs.authlib.org/en/latest/client/frameworks.html The client part keeps the same API as `Flask-OAuth`_. The only changes are the imports:: from flask_oauthlib.client import OAuth .. attention:: If you are testing the provider and the client locally, do not make them start listening on the same address because they will override the `session` of each other leading to strange bugs. eg: start the provider listening on `127.0.0.1:4000` and client listening on `localhost:4000` to avoid this problem. .. _`Flask-OAuth`: http://pythonhosted.org/Flask-OAuth/ OAuth1 Client ------------- The difference between OAuth1 and OAuth2 in the configuration is ``request_token_url``. In OAuth1 it is required, in OAuth2 it should be ``None``. To connect to a remote application create a :class:`OAuth` object and register a remote application on it using the :meth:`~OAuth.remote_app` method:: from flask_oauthlib.client import OAuth oauth = OAuth() the_remote_app = oauth.remote_app('the remote app', ... ) A remote application must define several URLs required by the OAuth machinery: - `request_token_url` - `access_token_url` - `authorize_url` Additionally the application should define an issued `consumer_key` and `consumer_secret`. You can find these values by registering your application with the remote application you want to connect with. Additionally you can provide a `base_url` that is prefixed to *all* relative URLs used in the remote app. For Twitter the setup would look like this:: twitter = oauth.remote_app('twitter', base_url='https://api.twitter.com/1/', request_token_url='https://api.twitter.com/oauth/request_token', access_token_url='https://api.twitter.com/oauth/access_token', authorize_url='https://api.twitter.com/oauth/authenticate', consumer_key='', consumer_secret='' ) Now that the application is created one can start using the OAuth system. One thing is missing: the tokengetter. OAuth uses a token and a secret to figure out who is connecting to the remote application. After authentication/authorization this information is passed to a function on your side and it is your responsibility to remember it. The following rules apply: - It's your responsibility to store that information somewhere - That information lives for as long as the user did not revoke the access for your application on the remote application. If it was revoked and the user re-enabled the application you will get different keys, so if you store them in the database don't forget to check if they changed in the authorization callback. - During the authorization handshake a temporary token and secret are issued. Your tokengetter is not used during that period. For a simple test application, storing that information in the session is probably sufficient:: from flask import session @twitter.tokengetter def get_twitter_token(token=None): return session.get('twitter_token') If the token does not exist, the function must return `None`, and otherwise return a tuple in the form ``(token, secret)``. The function might also be passed a `token` parameter. This is user defined and can be used to indicate another token. Imagine for instance you want to support user and application tokens or different tokens for the same user. The name of the token can be passed to to the :meth:`~OAuthRemoteApp.request` function. Signing in / Authorizing ------------------------ To sign in with Twitter or link a user account with a remote Twitter user, simply call into :meth:`~OAuthRemoteApp.authorize` and pass it the URL that the user should be redirected back to. For example:: @app.route('/login') def login(): return twitter.authorize(callback=url_for('oauth_authorized', next=request.args.get('next') or request.referrer or None)) If the application redirects back, the remote application can fetch all relevant information in the `oauth_authorized` function with :meth:`~OAuthRemoteApp.authorized_response`:: from flask import redirect @app.route('/oauth-authorized') def oauth_authorized(): next_url = request.args.get('next') or url_for('index') resp = twitter.authorized_response() if resp is None: flash(u'You denied the request to sign in.') return redirect(next_url) session['twitter_token'] = ( resp['oauth_token'], resp['oauth_token_secret'] ) session['twitter_user'] = resp['screen_name'] flash('You were signed in as %s' % resp['screen_name']) return redirect(next_url) We store the token and the associated secret in the session so that the tokengetter can return it. Additionally, we also store the Twitter username that was sent back to us so that we can later display it to the user. In larger applications it is recommended to store satellite information in a database instead to ease debugging and more easily handle additional information associated with the user. Facebook OAuth -------------- For Facebook the flow is very similar to Twitter or other OAuth systems but there is a small difference. You're not using the `request_token_url` at all and you need to provide a scope in the `request_token_params`:: facebook = oauth.remote_app('facebook', base_url='https://graph.facebook.com/', request_token_url=None, access_token_url='/oauth/access_token', authorize_url='https://www.facebook.com/dialog/oauth', consumer_key=FACEBOOK_APP_ID, consumer_secret=FACEBOOK_APP_SECRET, request_token_params={'scope': 'email'} ) Furthermore the `callback` is mandatory for the call to :meth:`~OAuthRemoteApp.authorize` and has to match the base URL that was specified in the Facebook application control panel. For development you can set it to ``localhost:5000``. The `APP_ID` and `APP_SECRET` can be retrieved from the Facebook app control panel. If you don't have an application registered yet you can do this at `facebook.com/developers `_. Invoking Remote Methods ----------------------- Now the user is signed in, but you probably want to use OAuth to call protected remote API methods and not just sign in. For that, the remote application object provides a :meth:`~OAuthRemoteApp.request` method that can request information from an OAuth protected resource. Additionally there are shortcuts like :meth:`~OAuthRemoteApp.get` or :meth:`~OAuthRemoteApp.post` to request data with a certain HTTP method. For example to create a new tweet you would call into the Twitter application as follows:: resp = twitter.post('statuses/update.json', data={ 'status': 'The text we want to tweet' }) if resp.status == 403: flash('Your tweet was too long.') else: flash('Successfully tweeted your tweet (ID: #%s)' % resp.data['id']) Or to display the users' feed we can do something like this:: resp = twitter.get('statuses/home_timeline.json') if resp.status == 200: tweets = resp.data else: tweets = None flash('Unable to load tweets from Twitter. Maybe out of ' 'API calls or Twitter is overloaded.') Flask-OAuthlib will do its best to send data encoded in the right format to the server and to decode it when it comes back. Incoming data is encoded based on the `mimetype` the server sent and is stored in the :attr:`~OAuthResponse.data` attribute. For outgoing data a default of ``'urlencode'`` is assumed. When a different format is needed, one can specify it with the `format` parameter. The following formats are supported: **Outgoing**: - ``'urlencode'`` - form encoded data (`GET` as URL and `POST`/`PUT` as request body) - ``'json'`` - JSON encoded data (`POST`/`PUT` as request body) **Incoming** - ``'urlencode'`` - stored as flat unicode dictionary - ``'json'`` - decoded with JSON rules, most likely a dictionary - ``'xml'`` - stored as elementtree element Unknown incoming data is stored as a string. If outgoing data of a different format is needed, `content_type` should be specified instead and the data provided should be an encoded string. Find the OAuth1 client example at `twitter.py`_. .. _`twitter.py`: https://github.com/lepture/flask-oauthlib/blob/master/example/twitter.py OAuth2 Client ------------- Find the OAuth2 client example at `github.py`_. .. _`github.py`: https://github.com/lepture/flask-oauthlib/blob/master/example/github.py .. versionadded:: 0.4.2 Request state parameters in authorization can be a function:: from werkzeug import security remote = oauth.remote_app( request_token_params={ 'state': lambda: security.gen_salt(10) } ) .. _lazy-configuration: Lazy Configuration ------------------ .. versionadded:: 0.3.0 When creating an open source project, we need to keep our consumer key and consumer secret secret. We usually keep them in a config file, and don't keep track of the config in the version control. Client of Flask-OAuthlib has a mechanism for you to lazy load your configuration from your Flask config object:: from flask_oauthlib.client import OAuth oauth = OAuth() twitter = oauth.remote_app( 'twitter', base_url='https://api.twitter.com/1/', request_token_url='https://api.twitter.com/oauth/request_token', access_token_url='https://api.twitter.com/oauth/access_token', authorize_url='https://api.twitter.com/oauth/authenticate', app_key='TWITTER' ) At this moment, we didn't put the ``consumer_key`` and ``consumer_secret`` in the ``remote_app``, instead, we set a ``app_key``. It will load from Flask config by the key ``TWITTER``, the configuration looks like:: app.config['TWITTER'] = { 'consumer_key': 'a random string key', 'consumer_secret': 'a random string secret', } oauth.init_app(app) .. versionadded:: 0.4.0 Or looks like that:: app.config['TWITTER_CONSUMER_KEY'] = 'a random string key' app.config['TWITTER_CONSUMER_SECRET'] = 'a random string secret' Twitter can get consumer key and secret from the Flask instance now. You can put all the configuration in ``app.config`` if you like, which means you can do it this way:: from flask_oauthlib.client import OAuth oauth = OAuth() twitter = oauth.remote_app( 'twitter', app_key='TWITTER' ) app.config['TWITTER'] = dict( consumer_key='a random key', consumer_secret='a random secret', base_url='https://api.twitter.com/1/', request_token_url='https://api.twitter.com/oauth/request_token', access_token_url='https://api.twitter.com/oauth/access_token', authorize_url='https://api.twitter.com/oauth/authenticate', ) oauth.init_app(app) Fix non-standard OAuth ---------------------- There are services that claimed they are providing OAuth API, but with a little differences. Some services even return with the wrong Content Type. This library takes all theses into consideration. Take an Chinese clone of twitter which is called weibo as the example. When you implement the authorization flow, the content type changes in the progress. Sometime it is application/json which is right. Sometime it is text/plain, which is wrong. And sometime, it didn't return anything. We can force to parse the returned response in a specified content type:: from flask_oauthlib.client import OAuth oauth = OAuth() weibo = oauth.remote_app( 'weibo', consumer_key='909122383', consumer_secret='2cdc60e5e9e14398c1cbdf309f2ebd3a', request_token_params={'scope': 'email,statuses_to_me_read'}, base_url='https://api.weibo.com/2/', authorize_url='https://api.weibo.com/oauth2/authorize', request_token_url=None, access_token_method='POST', access_token_url='https://api.weibo.com/oauth2/access_token', # force to parse the response in applcation/json content_type='application/json', ) The weibo site didn't follow the Bearer token, the acceptable header is:: 'OAuth2 a-token-string' The original behavior of Flask OAuthlib client is:: 'Bearer a-token-string' We can configure with a `pre_request` method to change the headers:: def change_weibo_header(uri, headers, body): auth = headers.get('Authorization') if auth: auth = auth.replace('Bearer', 'OAuth2') headers['Authorization'] = auth return uri, headers, body weibo.pre_request = change_weibo_header You can change uri, headers and body in the pre request. flask-oauthlib-0.9.5/docs/conf.py000066400000000000000000000201061327671251000166640ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Flask-OAuthlib documentation build configuration file, created by # sphinx-quickstart on Fri May 17 21:54:48 2013. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os sys.path.append(os.path.abspath('_themes')) sys.path.append(os.path.abspath('.')) sys.path.append(os.path.abspath('..')) # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Flask-OAuthlib' import datetime copyright = u'2013 - %i, Hsiaoming Yang' % datetime.datetime.utcnow().year # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. import flask_oauthlib version = flask_oauthlib.__version__ # The full version, including alpha/beta/rc tags. release = flask_oauthlib.__version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'flask' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. html_theme_path = ['_themes'] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. html_logo = 'flask-oauthlib.png' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. html_sidebars = { 'index': ['brand.html', 'sidebarintro.html', 'searchbox.html'], '**': ['brand.html', 'localtoc.html', 'relations.html', 'searchbox.html'] } # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. html_show_sourcelink = False # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'Flask-OAuthlibdoc' # -- Options for LaTeX output -------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'Flask-OAuthlib.tex', u'Flask-OAuthlib Documentation', u'Hsiaoming Yang', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'flask-oauthlib', u'Flask-OAuthlib Documentation', [u'Hsiaoming Yang'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'Flask-OAuthlib', u'Flask-OAuthlib Documentation', u'Hsiaoming Yang', 'Flask-OAuthlib', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' flask-oauthlib-0.9.5/docs/contrib.rst000066400000000000000000000000641327671251000175600ustar00rootroot00000000000000.. _contributing: .. include:: ../CONTRIBUTING.rst flask-oauthlib-0.9.5/docs/index.rst000066400000000000000000000030201327671251000172220ustar00rootroot00000000000000.. Flask-OAuthlib documentation master file, created by sphinx-quickstart on Fri May 17 21:54:48 2013. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. .. _oauthlib: https://github.com/idan/oauthlib Flask-OAuthlib ============== Flask-OAuthlib is designed to be a replacement for Flask-OAuth. It depends on oauthlib_. The client part of Flask-OAuthlib shares the same API as Flask-OAuth, which is pretty and simple. .. warning:: Please use https://github.com/lepture/authlib instead. Features -------- - Support for OAuth 1.0a, 1.0, 1.1, OAuth2 client - Friendly API (same as Flask-OAuth) - Direct integration with Flask - Basic support for remote method invocation of RESTful APIs - Support OAuth1 provider with HMAC and RSA signature - Support OAuth2 provider with Bearer token User's Guide ------------ This part of the documentation, which is mostly prose, begins with some background information about Flask-OAuthlib, then focuses on step-by-step instructions for getting the most out of Flask-OAuthlib .. toctree:: :maxdepth: 2 intro install client oauth1 oauth2 additional API Documentation ----------------- If you are looking for information on a specific function, class or method, this part of the documentation is for you. .. toctree:: :maxdepth: 2 api Additional Notes ---------------- Contribution guide, legal information and changelog are here. .. toctree:: :maxdepth: 2 contrib changelog authors flask-oauthlib-0.9.5/docs/install.rst000066400000000000000000000023051327671251000175660ustar00rootroot00000000000000.. _install: Installation ============ This part of the documentation covers the installation of Flask-OAuthlib. Pip --- Installing Flask-OAuthlib is simple with `pip `_:: $ pip install Flask-OAuthlib Cheeseshop Mirror ----------------- If the Cheeseshop is down, you can also install Flask-OAuthlib from one of the mirrors. `Crate.io `_ is one of them:: $ pip install -i http://simple.crate.io/ Flask-OAuthlib Get the Code ------------ Flask-OAuthlib is actively developed on GitHub, where the code is `always available `_. You can either clone the public repository:: git clone git://github.com/lepture/flask-oauthlib.git Download the `tarball `_:: $ curl -OL https://github.com/lepture/flask-oauthlib/tarball/master Or, download the `zipball `_:: $ curl -OL https://github.com/lepture/flask-oauthlib/zipball/master Once you have a copy of the source, you can embed it in your Python package, or install it into your site-packages easily:: $ python setup.py install flask-oauthlib-0.9.5/docs/intro.rst000066400000000000000000000026771327671251000172670ustar00rootroot00000000000000.. _introduction: Introduction ============ Flask-OAuthlib is designed to be a replacement for Flask-OAuth. It depends on oauthlib_. Why --- The original `Flask-OAuth`_ suffers from lack of maintenance, and oauthlib_ is a promising replacement for `python-oauth2`_. .. _`Flask-OAuth`: http://pythonhosted.org/Flask-OAuth/ .. _oauthlib: https://github.com/idan/oauthlib .. _`python-oauth2`: https://pypi.python.org/pypi/oauth2/ There are lots of non-standard services that claim they are oauth providers, but their APIs are always broken. While rewriting an oauth extension for Flask, I took them into consideration. Flask-OAuthlib does support these non-standard services. Flask-OAuthlib also provides the solution for creating an oauth service. It supports both oauth1 and oauth2 (with Bearer Token). import this ----------- Flask-OAuthlib was developed with a few :pep:`20` idioms in mind:: >>> import this #. Beautiful is better than ugly. #. Explicit is better than implicit. #. Simple is better than complex. #. Complex is better than complicated. #. Readability counts. All contributions to Flask-OAuthlib should keep these important rules in mind. License ------- A large number of open source projects in Python are `BSD Licensed`_, and Flask-OAuthlib is released under `BSD License`_ too. .. _`BSD License`: http://opensource.org/licenses/BSD-3-Clause .. _`BSD Licensed`: http://opensource.org/licenses/BSD-3-Clause .. include:: ../LICENSE flask-oauthlib-0.9.5/docs/oauth1.rst000066400000000000000000000345421327671251000173310ustar00rootroot00000000000000OAuth1 Server ============= .. note:: Please read https://docs.authlib.org/en/latest/flask/oauth1.html This part of documentation covers the tutorial of setting up an OAuth1 provider. An OAuth1 server concerns how to grant the authorization and how to protect the resource. Register an **OAuth** provider:: from flask_oauthlib.provider import OAuth1Provider app = Flask(__name__) oauth = OAuth1Provider(app) Like any other Flask extensions, we can pass the application later:: oauth = OAuth1Provider() def create_app(): app = Flask(__name__) oauth.init_app(app) return app To implemente the oauthorization flow, we need to understand the data model. User (Resource Owner) --------------------- A user, or resource owner, is usally the registered user on your site. You design your own user model, there is not much to say. Client (Application) --------------------- A client is the app which want to use the resource of a user. It is suggested that the client is registered by a user on your site, but it is not required. The client should contain at least these information: - client_key: A random string - client_secret: A random string - redirect_uris: A list of redirect uris - default_redirect_uri: One of the redirect uris - default_realms: Default realms/scopes of the client But it could be better, if you implemented: - validate_realms: A function to validate realms An example of the data model in SQLAlchemy (SQLAlchemy is not required):: class Client(db.Model): # human readable name, not required name = db.Column(db.String(40)) # human readable description, not required description = db.Column(db.String(400)) # creator of the client, not required user_id = db.Column(db.ForeignKey('user.id')) # required if you need to support client credential user = db.relationship('User') client_key = db.Column(db.String(40), primary_key=True) client_secret = db.Column(db.String(55), unique=True, index=True, nullable=False) _realms = db.Column(db.Text) _redirect_uris = db.Column(db.Text) @property def redirect_uris(self): if self._redirect_uris: return self._redirect_uris.split() return [] @property def default_redirect_uri(self): return self.redirect_uris[0] @property def default_realms(self): if self._realms: return self._realms.split() return [] Request Token and Verifier -------------------------- Request token is designed for exchanging access token. Verifier token is designed to verify the current user. It is always suggested that you combine request token and verifier together. The request token should contain: - client: Client associated with this token - token: Access token - secret: Access token secret - realms: Realms with this access token - redirect_uri: A URI for redirecting The verifier should contain: - verifier: A random string for verifier - user: The current user And the all in one token example:: class RequestToken(db.Model): id = db.Column(db.Integer, primary_key=True) user_id = db.Column( db.Integer, db.ForeignKey('user.id', ondelete='CASCADE') ) user = db.relationship('User') client_key = db.Column( db.String(40), db.ForeignKey('client.client_key'), nullable=False, ) client = db.relationship('Client') token = db.Column(db.String(255), index=True, unique=True) secret = db.Column(db.String(255), nullable=False) verifier = db.Column(db.String(255)) redirect_uri = db.Column(db.Text) _realms = db.Column(db.Text) @property def realms(self): if self._realms: return self._realms.split() return [] Since the request token and verifier is a one-time token, it would be better to put them in a cache. Timestamp and Nonce ------------------- Timestamp and nonce is a token for preventing repeating requests, it can store these information: - client_key: The client/consure key - timestamp: The ``oauth_timestamp`` parameter - nonce: The ``oauth_nonce`` parameter - request_token: Request token string, if any - access_token: Access token string, if any The timelife of a timestamp and nonce is 60 senconds, put it in a cache please. Here is an example in SQLAlchemy:: class Nonce(db.Model): id = db.Column(db.Integer, primary_key=True) timestamp = db.Column(db.Integer) nonce = db.Column(db.String(40)) client_key = db.Column( db.String(40), db.ForeignKey('client.client_key'), nullable=False, ) client = db.relationship('Client') request_token = db.Column(db.String(50)) access_token = db.Column(db.String(50)) Access Token ------------ An access token is the final token that could be use by the client. Client will send access token everytime when it need to access resource. A access token requires at least these information: - client: Client associated with this token - user: User associated with this token - token: Access token - secret: Access token secret - realms: Realms with this access token The implementation in SQLAlchemy:: class AccessToken(db.Model): id = db.Column(db.Integer, primary_key=True) client_key = db.Column( db.String(40), db.ForeignKey('client.client_key'), nullable=False, ) client = db.relationship('Client') user_id = db.Column( db.Integer, db.ForeignKey('user.id'), ) user = db.relationship('User') token = db.Column(db.String(255)) secret = db.Column(db.String(255)) _realms = db.Column(db.Text) @property def realms(self): if self._realms: return self._realms.split() return [] Configuration ------------- The oauth provider has some built-in defaults, you can change them with Flask config: ==================================== ========================================== `OAUTH1_PROVIDER_ERROR_URI` The error page when there is an error, default value is ``'/oauth/errors'``. `OAUTH1_PROVIDER_ERROR_ENDPOINT` You can also configure the error page uri with an endpoint name. `OAUTH1_PROVIDER_REALMS` A list of allowed realms, default is []. `OAUTH1_PROVIDER_KEY_LENGTH` A range allowed for key length, default value is (20, 30). `OAUTH1_PROVIDER_ENFORCE_SSL` If the server should be enforced through SSL. Default value is True. `OAUTH1_PROVIDER_SIGNATURE_METHODS` Allowed signature methods, default value is (SIGNATURE_HMAC, SIGNATURE_RSA). ==================================== ========================================== .. warning:: RSA signature is not ready at this moment, you should use HMAC. Implements ---------- The implementings of authorization flow needs three handlers, one is request token handler, one is authorize handler for user to confirm the grant, the other is token handler for client to exchange access token. Before the implementing of authorize and request/access token handler, we need to set up some getters and setter to communicate with the database. Client getter ````````````` A client getter is required. It tells which client is sending the requests, creating the getter with decorator:: @oauth.clientgetter def load_client(client_key): return Client.query.filter_by(client_key=client_key).first() Request token & verifier getters and setters ```````````````````````````````````````````` Request token & verifier getters and setters are required. They are used in the authorization flow, implemented with decorators:: @oauth.grantgetter def load_request_token(token): grant = RequestToken.query.filter_by(token=token).first() return grant @oauth.grantsetter def save_request_token(token, request): if oauth.realms: realms = ' '.join(request.realms) else: realms = None grant = RequestToken( token=token['oauth_token'], secret=token['oauth_token_secret'], client=request.client, redirect_uri=request.redirect_uri, _realms=realms, ) db.session.add(grant) db.session.commit() return grant @oauth.verifiergetter def load_verifier(verifier, token): return RequestToken.query.filter_by(verifier=verifier, token=token).first() @oauth.verifiersetter def save_verifier(token, verifier, *args, **kwargs): tok = RequestToken.query.filter_by(token=token).first() tok.verifier = verifier['oauth_verifier'] tok.user = get_current_user() db.session.add(tok) db.session.commit() return tok In the sample code, there is a ``get_current_user`` method, that will return the current user object, you should implement it yourself. The ``token`` for ``grantsetter`` is a dict, that contains:: { u'oauth_token': u'arandomstringoftoken', u'oauth_token_secret': u'arandomstringofsecret', u'oauth_authorized_realms': u'email address' } And the ``verifier`` for ``verifiersetter`` is a dict too, it contains:: { u'oauth_verifier': u'Gqm3id67MdkrASOCQIAlb3XODaPlun', u'oauth_token': u'eTYP46AJbhp8u4LE5QMjXeItRGGoAI', u'resource_owner_key': u'eTYP46AJbhp8u4LE5QMjXeItRGGoAI' } Token getter and setter ``````````````````````` Token getter and setters are required. They are used in the authorization flow and accessing resource flow. Implemented with decorators:: @oauth.tokengetter def load_access_token(client_key, token, *args, **kwargs): t = AccessToken.query.filter_by( client_key=client_key, token=token).first() return t @oauth.tokensetter def save_access_token(token, request): tok = AccessToken( client=request.client, user=request.user, token=token['oauth_token'], secret=token['oauth_token_secret'], _realms=token['oauth_authorized_realms'], ) db.session.add(tok) db.session.commit() The setter receives ``token`` and ``request`` parameters. The ``token`` is a dict, which contains:: { u'oauth_token_secret': u'H1xGH4X1ZkRAulHHdLfdFm7NR350tr', u'oauth_token': u'aXNlKcjkVImnTfTKj8CgFpc1XRZr6P', u'oauth_authorized_realms': u'email' } The ``request`` is an object, it contains at least a `user` and `client` objects for current flow. Timestamp and Nonce getter and setter ````````````````````````````````````` Timestamp and Nonce getter and setter is required. They are used everywhere:: @oauth.noncegetter def load_nonce(client_key, timestamp, nonce, request_token, access_token): return Nonce.query.filter_by( client_key=client_key, timestamp=timestamp, nonce=nonce, request_token=request_token, access_token=access_token, ).first() @oauth.noncesetter def save_nonce(client_key, timestamp, nonce, request_token, access_token): nonce = Nonce( client_key=client_key, timestamp=timestamp, nonce=nonce, request_token=request_token, access_token=access_token, ) db.session.add(nonce) db.session.commit() return nonce Request token handler ````````````````````` Request token handler is a decorator for generating request token. You don't need to do much:: @app.route('/oauth/request_token') @oauth.request_token_handler def request_token(): return {} You can add more data on token response:: @app.route('/oauth/request_token') @oauth.request_token_handler def request_token(): return {'version': '0.1.0'} Authorize handler ````````````````` Authorize handler is a decorator for authorize endpoint. It is suggested that you implemented it this way:: @app.route('/oauth/authorize', methods=['GET', 'POST']) @require_login @oauth.authorize_handler def authorize(*args, **kwargs): if request.method == 'GET': client_key = kwargs.get('resource_owner_key') client = Client.query.filter_by(client_key=client_key).first() kwargs['client'] = client return render_template('authorize.html', **kwargs) confirm = request.form.get('confirm', 'no') return confirm == 'yes' The GET request will render a page for user to confirm the grant, parameters in kwargs are: - resource_owner_key: same as client_key - realms: realms that this client requests The POST request needs to return a bool value that tells whether user grantted the access or not. Access token handler ```````````````````` Access token handler is a decorator for exchange access token. Client will request an access token with a request token. You don't need to do much:: @app.route('/oauth/access_token') @oauth.access_token_handler def access_token(): return {} Just like request token handler, you can add more data in access token. Protect Resource ---------------- Protect the resource of a user with ``require_oauth`` decorator now:: @app.route('/api/me') @oauth.require_oauth('email') def me(): user = request.oauth.user return jsonify(email=user.email, username=user.username) @app.route('/api/user/') @oauth.require_oauth('email') def user(username): user = User.query.filter_by(username=username).first() return jsonify(email=user.email, username=user.username) The decorator accepts a list of realms, only the clients with the given realms can access the defined resources. .. versionchanged:: 0.5.0 The ``request`` has an additional property ``oauth``, it contains at least: - client: client model object - realms: a list of scopes - user: user model object - headers: headers of the request - body: body content of the request Example for OAuth 1 ------------------- Here is an example of OAuth 1 server: https://github.com/lepture/example-oauth1-server Also read this article http://lepture.com/en/2013/create-oauth-server. flask-oauthlib-0.9.5/docs/oauth2.rst000066400000000000000000000357351327671251000173370ustar00rootroot00000000000000.. _oauth2: OAuth2 Server ============= .. note:: Please read https://docs.authlib.org/en/latest/flask/oauth2.html An OAuth2 server concerns how to grant the authorization and how to protect the resource. Register an **OAuth** provider:: from flask_oauthlib.provider import OAuth2Provider app = Flask(__name__) oauth = OAuth2Provider(app) Like any other Flask extensions, we can pass the application later:: oauth = OAuth2Provider() def create_app(): app = Flask(__name__) oauth.init_app(app) return app To implement the authorization flow, we need to understand the data model. User (Resource Owner) --------------------- A user, or resource owner, is usually the registered user on your site. You need to design your own user model. Client (Application) --------------------- A client is the app which wants to use the resource of a user. It is suggested that the client is registered by a user on your site, but it is not required. The client should contain at least these properties: - client_id: A random string - client_secret: A random string - client_type: A string represents if it is `confidential` - redirect_uris: A list of redirect uris - default_redirect_uri: One of the redirect uris - default_scopes: Default scopes of the client But it could be better, if you implemented: - allowed_grant_types: A list of grant types - allowed_response_types: A list of response types - validate_scopes: A function to validate scopes .. note:: The value of the scope parameter is expressed as a list of space- delimited, case-sensitive strings. via: http://tools.ietf.org/html/rfc6749#section-3.3 An example of the data model in SQLAlchemy (SQLAlchemy is not required):: class Client(db.Model): # human readable name, not required name = db.Column(db.String(40)) # human readable description, not required description = db.Column(db.String(400)) # creator of the client, not required user_id = db.Column(db.ForeignKey('user.id')) # required if you need to support client credential user = db.relationship('User') client_id = db.Column(db.String(40), primary_key=True) client_secret = db.Column(db.String(55), unique=True, index=True, nullable=False) # public or confidential is_confidential = db.Column(db.Boolean) _redirect_uris = db.Column(db.Text) _default_scopes = db.Column(db.Text) @property def client_type(self): if self.is_confidential: return 'confidential' return 'public' @property def redirect_uris(self): if self._redirect_uris: return self._redirect_uris.split() return [] @property def default_redirect_uri(self): return self.redirect_uris[0] @property def default_scopes(self): if self._default_scopes: return self._default_scopes.split() return [] Grant Token ----------- A grant token is created in the authorization flow, and will be destroyed when the authorization is finished. In this case, it would be better to store the data in a cache, which leads to better performance. A grant token should contain at least this information: - client_id: A random string of client_id - code: A random string - user: The authorization user - scopes: A list of scope - expires: A datetime.datetime in UTC - redirect_uri: A URI string - delete: A function to delete itself Also in an SQLAlchemy model (this should be in a cache):: class Grant(db.Model): id = db.Column(db.Integer, primary_key=True) user_id = db.Column( db.Integer, db.ForeignKey('user.id', ondelete='CASCADE') ) user = db.relationship('User') client_id = db.Column( db.String(40), db.ForeignKey('client.client_id'), nullable=False, ) client = db.relationship('Client') code = db.Column(db.String(255), index=True, nullable=False) redirect_uri = db.Column(db.String(255)) expires = db.Column(db.DateTime) _scopes = db.Column(db.Text) def delete(self): db.session.delete(self) db.session.commit() return self @property def scopes(self): if self._scopes: return self._scopes.split() return [] Bearer Token ------------ A bearer token is the final token that could be used by the client. There are other token types, but bearer token is widely used. Flask-OAuthlib only comes with a bearer token. A bearer token requires at least this information: - access_token: A string token - refresh_token: A string token - client_id: ID of the client - scopes: A list of scopes - expires: A `datetime.datetime` object - user: The user object - delete: A function to delete itself An example of the data model in SQLAlchemy:: class Token(db.Model): id = db.Column(db.Integer, primary_key=True) client_id = db.Column( db.String(40), db.ForeignKey('client.client_id'), nullable=False, ) client = db.relationship('Client') user_id = db.Column( db.Integer, db.ForeignKey('user.id') ) user = db.relationship('User') # currently only bearer is supported token_type = db.Column(db.String(40)) access_token = db.Column(db.String(255), unique=True) refresh_token = db.Column(db.String(255), unique=True) expires = db.Column(db.DateTime) _scopes = db.Column(db.Text) def delete(self): db.session.delete(self) db.session.commit() return self @property def scopes(self): if self._scopes: return self._scopes.split() return [] Configuration ------------- The Oauth provider has some built-in defaults. You can change them with Flask config: ================================== ========================================== `OAUTH2_PROVIDER_ERROR_URI` The error page when there is an error, default value is ``'/oauth/errors'``. `OAUTH2_PROVIDER_ERROR_ENDPOINT` You can also configure the error page uri with an endpoint name. `OAUTH2_PROVIDER_TOKEN_EXPIRES_IN` Default Bearer token expires time, default is ``3600``. ================================== ========================================== Implementation -------------- The implementation of the authorization flow needs two handlers: one is the authorization handler for the user to confirm the grant, the other is the token handler for the client to exchange/refresh access tokens. Before implementing the authorize and token handlers, we need to set up some getters and setters to communicate with the database. Client getter ````````````` A client getter is required. It tells which client is sending the requests, creating the getter with a decorator:: @oauth.clientgetter def load_client(client_id): return Client.query.filter_by(client_id=client_id).first() Grant getter and setter ``````````````````````` Grant getter and setter are required. They are used in the authorization flow, implemented with decorators:: from datetime import datetime, timedelta @oauth.grantgetter def load_grant(client_id, code): return Grant.query.filter_by(client_id=client_id, code=code).first() @oauth.grantsetter def save_grant(client_id, code, request, *args, **kwargs): # decide the expires time yourself expires = datetime.utcnow() + timedelta(seconds=100) grant = Grant( client_id=client_id, code=code['code'], redirect_uri=request.redirect_uri, _scopes=' '.join(request.scopes), user=get_current_user(), expires=expires ) db.session.add(grant) db.session.commit() return grant In the sample code, there is a ``get_current_user`` method, that will return the current user object. You should implement it yourself. The ``request`` object is defined by ``OAuthlib``. You can get at least this much information: - client: client model object - scopes: a list of scopes - user: user model object - redirect_uri: redirect_uri parameter - headers: headers of the request - body: body content of the request - state: state parameter - response_type: response_type paramter Token getter and setter ``````````````````````` Token getter and setter are required. They are used in the authorization flow and the accessing resource flow. They are implemented with decorators as follows:: @oauth.tokengetter def load_token(access_token=None, refresh_token=None): if access_token: return Token.query.filter_by(access_token=access_token).first() elif refresh_token: return Token.query.filter_by(refresh_token=refresh_token).first() from datetime import datetime, timedelta @oauth.tokensetter def save_token(token, request, *args, **kwargs): toks = Token.query.filter_by(client_id=request.client.client_id, user_id=request.user.id) # make sure that every client has only one token connected to a user for t in toks: db.session.delete(t) expires_in = token.get('expires_in') expires = datetime.utcnow() + timedelta(seconds=expires_in) tok = Token( access_token=token['access_token'], refresh_token=token['refresh_token'], token_type=token['token_type'], _scopes=token['scope'], expires=expires, client_id=request.client.client_id, user_id=request.user.id, ) db.session.add(tok) db.session.commit() return tok The getter will receive two parameters. If you don't need to support a refresh token, you can just load token by access token. The setter receives ``token`` and ``request`` parameters. The ``token`` is a dict, which contains:: { u'access_token': u'6JwgO77PApxsFCU8Quz0pnL9s23016', u'refresh_token': u'7cYSMmBg4T7F4kwoWfUQA99J8yqjp0', u'token_type': u'Bearer', u'expires_in': 3600, u'scope': u'email address' } The ``request`` is an object like the one in grant setter. User getter ``````````` User getter is optional. It is only required if you need password credential authorization:: @oauth.usergetter def get_user(username, password, *args, **kwargs): user = User.query.filter_by(username=username).first() if user.check_password(password): return user return None Authorize handler ````````````````` Authorize handler is a decorator for the authorize endpoint. It is suggested that you implemented it this way:: @app.route('/oauth/authorize', methods=['GET', 'POST']) @require_login @oauth.authorize_handler def authorize(*args, **kwargs): if request.method == 'GET': client_id = kwargs.get('client_id') client = Client.query.filter_by(client_id=client_id).first() kwargs['client'] = client return render_template('oauthorize.html', **kwargs) confirm = request.form.get('confirm', 'no') return confirm == 'yes' The GET request will render a page for user to confirm the grant. The parameters in kwargs are: - client_id: id of the client - scopes: a list of scope - state: state parameter - redirect_uri: redirect_uri parameter - response_type: response_type parameter The POST request needs to return a boolean value that tells whether user granted access or not. There is a ``@require_login`` decorator in the sample code. You should implement this yourself. Here is an `example`_ by Flask documentation. .. _`example`: http://flask.pocoo.org/docs/0.12/patterns/viewdecorators/#login-required-decorator Token handler ````````````` Token handler is a decorator for exchanging/refreshing access token. You don't need to do much:: @app.route('/oauth/token') @oauth.token_handler def access_token(): return None You can add more data on the token response:: @app.route('/oauth/token') @oauth.token_handler def access_token(): return {'version': '0.1.0'} Limit the HTTP method with Flask routes, for example, only POST is allowed for exchange tokens:: @app.route('/oauth/token', methods=['POST']) @oauth.token_handler def access_token(): return None The authorization flow is finished, everything should be working now. .. admonition:: Note: This token endpoint is for access token and refresh token both. But please remember that refresh token is only available for confidential client, and only available in password credential. Revoke handler `````````````` In some cases a user may wish to revoke access given to an application and the revoke handler makes it possible for an application to programmatically revoke the access given to it. Also here you don't need to do much, allowing POST only is recommended:: @app.route('/oauth/revoke', methods=['POST']) @oauth.revoke_handler def revoke_token(): pass Subclass way ```````````` If you are not satisfied with the decorator way of getters and setters, you can implement them in the subclass way:: class MyProvider(OAuth2Provider): def _clientgetter(self, client_id): return Client.query.filter_by(client_id=client_id).first() #: more getters and setters Every getter and setter is started with ``_``. Protect Resource ---------------- Protect the resource of a user with ``require_oauth`` decorator now:: @app.route('/api/me') @oauth.require_oauth('email') def me(): user = request.oauth.user return jsonify(email=user.email, username=user.username) @app.route('/api/user/') @oauth.require_oauth('email') def user(username): user = User.query.filter_by(username=username).first() return jsonify(email=user.email, username=user.username) The decorator accepts a list of scopes and only the clients with the given scopes can access the defined resources. .. versionchanged:: 0.5.0 The ``request`` has an additional property ``oauth``, which contains at least: - client: client model object - scopes: a list of scopes - user: user model object - redirect_uri: redirect_uri parameter - headers: headers of the request - body: body content of the request - state: state parameter - response_type: response_type paramter Example for OAuth 2 ------------------- An example server (and client) can be found in the tests folder: https://github.com/lepture/flask-oauthlib/tree/master/tests/oauth2 Other helpful resources include: - Another example of an OAuth 2 server: https://github.com/authlib/example-oauth2-server - An article on how to create an OAuth server: http://lepture.com/en/2013/create-oauth-server. flask-oauthlib-0.9.5/example/000077500000000000000000000000001327671251000160715ustar00rootroot00000000000000flask-oauthlib-0.9.5/example/contrib/000077500000000000000000000000001327671251000175315ustar00rootroot00000000000000flask-oauthlib-0.9.5/example/contrib/experiment-client/000077500000000000000000000000001327671251000231655ustar00rootroot00000000000000flask-oauthlib-0.9.5/example/contrib/experiment-client/.gitignore000066400000000000000000000000101327671251000251440ustar00rootroot00000000000000dev.cfg flask-oauthlib-0.9.5/example/contrib/experiment-client/douban.py000066400000000000000000000033441327671251000250130ustar00rootroot00000000000000from flask import Flask, url_for, session, jsonify from flask.ext.oauthlib.contrib.client import OAuth class AppConfig(object): DEBUG = True SECRET_KEY = 'your-secret-key' DOUBAN_CLIENT_ID = 'your-api-key' DOUBAN_CLIENT_SECRET = 'your-api-secret' DOUBAN_SCOPE = [ 'douban_basic_common', 'shuo_basic_r', ] app = Flask(__name__) app.config.from_object(AppConfig) app.config.from_pyfile('dev.cfg', silent=True) oauth = OAuth(app) # see also https://github.com/requests/requests-oauthlib/pull/138 douban = oauth.remote_app( name='douban', version='2', endpoint_url='https://api.douban.com/', access_token_url='https://www.douban.com/service/auth2/token', refresh_token_url='https://www.douban.com/service/auth2/token', authorization_url='https://www.douban.com/service/auth2/auth', compliance_fixes='.douban:douban_compliance_fix') @app.route('/') def home(): if obtain_douban_token(): response = douban.get('v2/user/~me') return jsonify(response=response.json()) return 'Login' % url_for('oauth_douban') @app.route('/auth/douban') def oauth_douban(): callback_uri = url_for('oauth_douban_callback', _external=True) return douban.authorize(callback_uri) @app.route('/auth/douban/callback') def oauth_douban_callback(): response = douban.authorized_response() if response: store_douban_token(response) return repr(dict(response)) else: return 'T_T Denied' % (url_for('oauth_douban')) @douban.tokengetter def obtain_douban_token(): return session.get('token') @douban.tokensaver def store_douban_token(token): session['token'] = token if __name__ == '__main__': app.run() flask-oauthlib-0.9.5/example/contrib/experiment-client/twitter.py000066400000000000000000000030271327671251000252430ustar00rootroot00000000000000from flask import Flask, url_for, session, jsonify from flask.ext.oauthlib.contrib.client import OAuth class DefaultConfig(object): DEBUG = True SECRET_KEY = 'your-secret-key' TWITTER_CONSUMER_KEY = 'your-api-key' TWITTER_CONSUMER_SECRET = 'your-api-secret' app = Flask(__name__) app.config.from_object(DefaultConfig) app.config.from_pyfile('dev.cfg', silent=True) oauth = OAuth(app) twitter = oauth.remote_app( name='twitter', version='1', endpoint_url='https://api.twitter.com/1.1/', request_token_url='https://api.twitter.com/oauth/request_token', access_token_url='https://api.twitter.com/oauth/access_token', authorization_url='https://api.twitter.com/oauth/authorize') @app.route('/') def home(): if oauth_twitter_token(): response = twitter.get('statuses/home_timeline.json') return jsonify(response=response.json()) return 'Login' % url_for('oauth_twitter') @app.route('/auth/twitter') def oauth_twitter(): callback_uri = url_for('oauth_twitter_callback', _external=True) return twitter.authorize(callback_uri) @app.route('/auth/twitter/callback') def oauth_twitter_callback(): response = twitter.authorized_response() if response: session['token'] = (response.token, response.token_secret) return repr(dict(response)) else: return 'T_T Denied' % (url_for('oauth_twitter')) @twitter.tokengetter def oauth_twitter_token(): return session.get('token') if __name__ == '__main__': app.run() flask-oauthlib-0.9.5/example/douban.py000066400000000000000000000030571327671251000177200ustar00rootroot00000000000000from flask import Flask, redirect, url_for, session, request, jsonify from flask_oauthlib.client import OAuth app = Flask(__name__) app.debug = True app.secret_key = 'development' oauth = OAuth(app) douban = oauth.remote_app( 'douban', consumer_key='0cfc3c5d9f873b1826f4b518de95b148', consumer_secret='3e209e4f9ecf6a4a', base_url='https://api.douban.com/', request_token_url=None, request_token_params={'scope': 'douban_basic_common,shuo_basic_r'}, access_token_url='https://www.douban.com/service/auth2/token', authorize_url='https://www.douban.com/service/auth2/auth', access_token_method='POST', ) @app.route('/') def index(): if 'douban_token' in session: resp = douban.get('shuo/v2/statuses/home_timeline') return jsonify(status=resp.status, data=resp.data) return redirect(url_for('login')) @app.route('/login') def login(): return douban.authorize(callback=url_for('authorized', _external=True)) @app.route('/logout') def logout(): session.pop('douban_token', None) return redirect(url_for('index')) @app.route('/login/authorized') def authorized(): resp = douban.authorized_response() if resp is None: return 'Access denied: reason=%s error=%s' % ( request.args['error_reason'], request.args['error_description'] ) session['douban_token'] = (resp['access_token'], '') return redirect(url_for('index')) @douban.tokengetter def get_douban_oauth_token(): return session.get('douban_token') if __name__ == '__main__': app.run() flask-oauthlib-0.9.5/example/dropbox.py000066400000000000000000000027421327671251000201250ustar00rootroot00000000000000from flask import Flask, redirect, url_for, session, request, jsonify from flask_oauthlib.client import OAuth app = Flask(__name__) app.debug = True app.secret_key = 'development' oauth = OAuth(app) dropbox = oauth.remote_app( 'dropbox', consumer_key='a68mwd4ngywz78d', consumer_secret='uzz3hr6spb7cspa', request_token_params={}, base_url='https://www.dropbox.com/1/', request_token_url=None, access_token_method='POST', access_token_url='https://api.dropbox.com/1/oauth2/token', authorize_url='https://www.dropbox.com/1/oauth2/authorize', ) @app.route('/') def index(): if 'dropbox_token' in session: me = dropbox.get('account/info') return jsonify(me.data) return redirect(url_for('login')) @app.route('/login') def login(): return dropbox.authorize(callback=url_for('authorized', _external=True)) @app.route('/logout') def logout(): session.pop('dropbox_token', None) return redirect(url_for('index')) @app.route('/login/authorized') def authorized(): resp = dropbox.authorized_response() if resp is None: return 'Access denied: reason=%s error=%s' % ( request.args['error'], request.args['error_description'] ) session['dropbox_token'] = (resp['access_token'], '') me = dropbox.get('account/info') return jsonify(me.data) @dropbox.tokengetter def get_dropbox_oauth_token(): return session.get('dropbox_token') if __name__ == '__main__': app.run() flask-oauthlib-0.9.5/example/facebook.py000066400000000000000000000032361327671251000202200ustar00rootroot00000000000000from flask import Flask, redirect, url_for, session, request from flask_oauthlib.client import OAuth, OAuthException FACEBOOK_APP_ID = '188477911223606' FACEBOOK_APP_SECRET = '621413ddea2bcc5b2e83d42fc40495de' app = Flask(__name__) app.debug = True app.secret_key = 'development' oauth = OAuth(app) facebook = oauth.remote_app( 'facebook', consumer_key=FACEBOOK_APP_ID, consumer_secret=FACEBOOK_APP_SECRET, request_token_params={'scope': 'email'}, base_url='https://graph.facebook.com', request_token_url=None, access_token_url='/oauth/access_token', access_token_method='GET', authorize_url='https://www.facebook.com/dialog/oauth' ) @app.route('/') def index(): return redirect(url_for('login')) @app.route('/login') def login(): callback = url_for( 'facebook_authorized', next=request.args.get('next') or request.referrer or None, _external=True ) return facebook.authorize(callback=callback) @app.route('/login/authorized') def facebook_authorized(): resp = facebook.authorized_response() if resp is None: return 'Access denied: reason=%s error=%s' % ( request.args['error_reason'], request.args['error_description'] ) if isinstance(resp, OAuthException): return 'Access denied: %s' % resp.message session['oauth_token'] = (resp['access_token'], '') me = facebook.get('/me') return 'Logged in as id=%s name=%s redirect=%s' % \ (me.data['id'], me.data['name'], request.args.get('next')) @facebook.tokengetter def get_facebook_oauth_token(): return session.get('oauth_token') if __name__ == '__main__': app.run() flask-oauthlib-0.9.5/example/github.py000066400000000000000000000030661327671251000177320ustar00rootroot00000000000000from flask import Flask, redirect, url_for, session, request, jsonify from flask_oauthlib.client import OAuth app = Flask(__name__) app.debug = True app.secret_key = 'development' oauth = OAuth(app) github = oauth.remote_app( 'github', consumer_key='a11a1bda412d928fb39a', consumer_secret='92b7cf30bc42c49d589a10372c3f9ff3bb310037', request_token_params={'scope': 'user:email'}, base_url='https://api.github.com/', request_token_url=None, access_token_method='POST', access_token_url='https://github.com/login/oauth/access_token', authorize_url='https://github.com/login/oauth/authorize' ) @app.route('/') def index(): if 'github_token' in session: me = github.get('user') return jsonify(me.data) return redirect(url_for('login')) @app.route('/login') def login(): return github.authorize(callback=url_for('authorized', _external=True)) @app.route('/logout') def logout(): session.pop('github_token', None) return redirect(url_for('index')) @app.route('/login/authorized') def authorized(): resp = github.authorized_response() if resp is None or resp.get('access_token') is None: return 'Access denied: reason=%s error=%s resp=%s' % ( request.args['error'], request.args['error_description'], resp ) session['github_token'] = (resp['access_token'], '') me = github.get('user') return jsonify(me.data) @github.tokengetter def get_github_oauth_token(): return session.get('github_token') if __name__ == '__main__': app.run() flask-oauthlib-0.9.5/example/google.py000066400000000000000000000035101327671251000177160ustar00rootroot00000000000000""" google example ~~~~~~~~~~~~~~ This example is contributed by Bruno Rocha GitHub: https://github.com/rochacbruno """ from flask import Flask, redirect, url_for, session, request, jsonify from flask_oauthlib.client import OAuth app = Flask(__name__) app.config['GOOGLE_ID'] = "cloud.google.com/console and get your ID" app.config['GOOGLE_SECRET'] = "cloud.google.com/console and get the secret" app.debug = True app.secret_key = 'development' oauth = OAuth(app) google = oauth.remote_app( 'google', consumer_key=app.config.get('GOOGLE_ID'), consumer_secret=app.config.get('GOOGLE_SECRET'), request_token_params={ 'scope': 'email' }, base_url='https://www.googleapis.com/oauth2/v1/', request_token_url=None, access_token_method='POST', access_token_url='https://accounts.google.com/o/oauth2/token', authorize_url='https://accounts.google.com/o/oauth2/auth', ) @app.route('/') def index(): if 'google_token' in session: me = google.get('userinfo') return jsonify({"data": me.data}) return redirect(url_for('login')) @app.route('/login') def login(): return google.authorize(callback=url_for('authorized', _external=True)) @app.route('/logout') def logout(): session.pop('google_token', None) return redirect(url_for('index')) @app.route('/login/authorized') def authorized(): resp = google.authorized_response() if resp is None: return 'Access denied: reason=%s error=%s' % ( request.args['error_reason'], request.args['error_description'] ) session['google_token'] = (resp['access_token'], '') me = google.get('userinfo') return jsonify({"data": me.data}) @google.tokengetter def get_google_oauth_token(): return session.get('google_token') if __name__ == '__main__': app.run() flask-oauthlib-0.9.5/example/linkedin.py000066400000000000000000000037271327671251000202510ustar00rootroot00000000000000from flask import Flask, redirect, url_for, session, request, jsonify from flask_oauthlib.client import OAuth app = Flask(__name__) app.debug = True app.secret_key = 'development' oauth = OAuth(app) linkedin = oauth.remote_app( 'linkedin', consumer_key='k8fhkgkkqzub', consumer_secret='ZZtLETQOQYNDjMrz', request_token_params={ 'scope': 'r_basicprofile', 'state': 'RandomString', }, base_url='https://api.linkedin.com/v1/', request_token_url=None, access_token_method='POST', access_token_url='https://www.linkedin.com/uas/oauth2/accessToken', authorize_url='https://www.linkedin.com/uas/oauth2/authorization', ) @app.route('/') def index(): if 'linkedin_token' in session: me = linkedin.get('people/~') return jsonify(me.data) return redirect(url_for('login')) @app.route('/login') def login(): return linkedin.authorize(callback=url_for('authorized', _external=True)) @app.route('/logout') def logout(): session.pop('linkedin_token', None) return redirect(url_for('index')) @app.route('/login/authorized') def authorized(): resp = linkedin.authorized_response() if resp is None: return 'Access denied: reason=%s error=%s' % ( request.args['error_reason'], request.args['error_description'] ) session['linkedin_token'] = (resp['access_token'], '') me = linkedin.get('people/~') return jsonify(me.data) @linkedin.tokengetter def get_linkedin_oauth_token(): return session.get('linkedin_token') def change_linkedin_query(uri, headers, body): auth = headers.pop('Authorization') headers['x-li-format'] = 'json' if auth: auth = auth.replace('Bearer', '').strip() if '?' in uri: uri += '&oauth2_access_token=' + auth else: uri += '?oauth2_access_token=' + auth return uri, headers, body linkedin.pre_request = change_linkedin_query if __name__ == '__main__': app.run() flask-oauthlib-0.9.5/example/qq.py000066400000000000000000000072021327671251000170650ustar00rootroot00000000000000# -*- coding: utf-8 -*- import os import json from flask import Flask, redirect, url_for, session, request, jsonify, Markup from flask_oauthlib.client import OAuth # get yours at http://connect.qq.com QQ_APP_ID = os.getenv('QQ_APP_ID', '101187283') QQ_APP_KEY = os.getenv('QQ_APP_KEY', '993983549da49e384d03adfead8b2489') app = Flask(__name__) app.debug = True app.secret_key = 'development' oauth = OAuth(app) qq = oauth.remote_app( 'qq', consumer_key=QQ_APP_ID, consumer_secret=QQ_APP_KEY, base_url='https://graph.qq.com', request_token_url=None, request_token_params={'scope': 'get_user_info'}, access_token_url='/oauth2.0/token', authorize_url='/oauth2.0/authorize', ) def json_to_dict(x): '''OAuthResponse class can't parse the JSON data with content-type - text/html and because of a rubbish api, we can't just tell flask-oauthlib to treat it as json.''' if x.find(b'callback') > -1: # the rubbish api (https://graph.qq.com/oauth2.0/authorize) is handled here as special case pos_lb = x.find(b'{') pos_rb = x.find(b'}') x = x[pos_lb:pos_rb + 1] try: if type(x) != str: # Py3k x = x.decode('utf-8') return json.loads(x, encoding='utf-8') except: return x def update_qq_api_request_data(data={}): '''Update some required parameters for OAuth2.0 API calls''' defaults = { 'openid': session.get('qq_openid'), 'access_token': session.get('qq_token')[0], 'oauth_consumer_key': QQ_APP_ID, } defaults.update(data) return defaults @app.route('/') def index(): '''just for verify website owner here.''' return Markup('''''') @app.route('/user_info') def get_user_info(): if 'qq_token' in session: data = update_qq_api_request_data() resp = qq.get('/user/get_user_info', data=data) return jsonify(status=resp.status, data=json_to_dict(resp.data)) return redirect(url_for('login')) @app.route('/login') def login(): return qq.authorize(callback=url_for('authorized', _external=True)) @app.route('/logout') def logout(): session.pop('qq_token', None) return redirect(url_for('get_user_info')) @app.route('/login/authorized') def authorized(): resp = qq.authorized_response() if resp is None: return 'Access denied: reason=%s error=%s' % ( request.args['error_reason'], request.args['error_description'] ) session['qq_token'] = (resp['access_token'], '') # Get openid via access_token, openid and access_token are needed for API calls resp = qq.get('/oauth2.0/me', {'access_token': session['qq_token'][0]}) resp = json_to_dict(resp.data) if isinstance(resp, dict): session['qq_openid'] = resp.get('openid') return redirect(url_for('get_user_info')) @qq.tokengetter def get_qq_oauth_token(): return session.get('qq_token') def convert_keys_to_string(dictionary): '''Recursively converts dictionary keys to strings.''' if not isinstance(dictionary, dict): return dictionary return dict((str(k), convert_keys_to_string(v)) for k, v in dictionary.items()) def change_qq_header(uri, headers, body): '''On SAE platform, when headers' keys are unicode type, will raise ``HTTP Error 400: Bad request``, so need convert keys from unicode to str. Otherwise, ignored it.''' # uncomment below line while deploy on SAE platform # headers = convert_keys_to_string(headers) return uri, headers, body qq.pre_request = change_qq_header if __name__ == '__main__': app.run() flask-oauthlib-0.9.5/example/reddit.py000066400000000000000000000073031327671251000177210ustar00rootroot00000000000000from flask import Flask, redirect, url_for, session, request from flask_oauthlib.client import OAuth, OAuthException, OAuthRemoteApp, parse_response from flask_oauthlib.utils import to_bytes import uuid import base64 import time REDDIT_APP_ID = '6WnQXb-elQ3DLw' REDDIT_APP_SECRET = 'KzQickJEBxNHmt5bpO_HmSiupTw' # Reddit requires you to set nice User-Agent containing your username REDDIT_USER_AGENT = 'flask-oauthlib testing by /u/' app = Flask(__name__) app.debug = True app.secret_key = 'development' oauth = OAuth(app) class RedditOAuthRemoteApp(OAuthRemoteApp): def __init__(self, *args, **kwargs): super(RedditOAuthRemoteApp, self).__init__(*args, **kwargs) def handle_oauth2_response(self): if self.access_token_method != 'POST': raise OAuthException( 'Unsupported access_token_method: %s' % self.access_token_method ) client = self.make_client() remote_args = { 'code': request.args.get('code'), 'client_secret': self.consumer_secret, 'redirect_uri': session.get('%s_oauthredir' % self.name) } remote_args.update(self.access_token_params) reddit_basic_auth = base64.encodestring('%s:%s' % (REDDIT_APP_ID, REDDIT_APP_SECRET)).replace('\n', '') body = client.prepare_request_body(**remote_args) while True: resp, content = self.http_request( self.expand_url(self.access_token_url), headers={'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': 'Basic %s' % reddit_basic_auth, 'User-Agent': REDDIT_USER_AGENT}, data=to_bytes(body, self.encoding), method=self.access_token_method, ) # Reddit API is rate-limited, so if we get 429, we need to retry if resp.code != 429: break time.sleep(1) data = parse_response(resp, content, content_type=self.content_type) if resp.code not in (200, 201): raise OAuthException( 'Invalid response from %s' % self.name, type='invalid_response', data=data ) return data reddit = RedditOAuthRemoteApp( oauth, 'reddit', consumer_key=REDDIT_APP_ID, consumer_secret=REDDIT_APP_SECRET, request_token_params={'scope': 'identity'}, base_url='https://oauth.reddit.com/api/v1/', request_token_url=None, access_token_url='https://www.reddit.com/api/v1/access_token', access_token_method='POST', authorize_url='https://www.reddit.com/api/v1/authorize' ) oauth.remote_apps['reddit'] = reddit @app.route('/') def index(): return redirect(url_for('login')) @app.route('/login') def login(): callback = url_for('reddit_authorized', _external=True) return reddit.authorize(callback=callback, state=uuid.uuid4()) @app.route('/login/authorized') def reddit_authorized(): resp = reddit.authorized_response() if isinstance(resp, OAuthException): print(resp.data) if resp is None: return 'Access denied: error=%s' % request.args['error'], session['reddit_oauth_token'] = (resp['access_token'], '') # This request may fail(429 Too Many Requests) # If you plan to use API heavily(and not just for auth), # it may be better to use PRAW: https://github.com/praw-dev/praw me = reddit.get('me') return 'Logged in as name=%s link_karma=%s comment_karma=%s' % \ (me.data['name'], me.data['link_karma'], me.data['comment_karma']) @reddit.tokengetter def get_reddit_oauth_token(): return session.get('reddit_oauth_token') if __name__ == '__main__': app.run() flask-oauthlib-0.9.5/example/static/000077500000000000000000000000001327671251000173605ustar00rootroot00000000000000flask-oauthlib-0.9.5/example/static/openid.png000066400000000000000000000006611327671251000213470ustar00rootroot00000000000000‰PNG  IHDRóÿabKGDÿÿÿ ½§“ pHYs  šœtIMEØ)7¯­½>IDAT8˵Ó?HÖqðÏûòÃ!ÞÁEAÐW¡†‚¦:—\¢!É‚à…·¥Í)·$''q¬Ý)¢¹ ¢ Ž«Apˆþ RQ/Q¨ËoÁ×^Ã;îû<ÜÝs_ŽÚ¶' mOšÚ¯^=|ï0~(L ÖéAõG¬ìN2³ÀE\Æ™ã‹Ó§{ןÕ~ÖÏm®]¹»Šñ8"þdæ‰b¸‰yŒà–Šö·^œ,Úß?ã®á>fæk WKð,¢·0Ú—•Ûxÿ6"š¨ãÑ@¥ÈÌó˜Á®FÄÓ#ŸÂBéðµ@³ÜÅ«À""‘{Uø[Æ=‡Q¡ŠûeûQ.²;‚ˆxƒ›%Ƀ̜ËÌþn:÷p˸O™ùò×àèÚxòO‡TJ¸„³õçs¿ûVÝ©,X?²ßºÊxb¬ù EIEND®B`‚flask-oauthlib-0.9.5/example/static/sign-in.png000066400000000000000000000045021327671251000214330ustar00rootroot00000000000000‰PNG  IHDR—÷yotsBIT|dˆ pHYs  ÒÝ~ütEXtCreation Time5/19/09rãÓstEXtSoftwareAdobe Fireworks CS4²Ó ›IDAThí›klSçÇÇŽ_ã8äâ$$)i !(´eÊz]×QªVh‹Ôû•©Z«ulƒ­Ò¾tTê´®cÚNjѦÑjT*•Е–•”B¢R!NÈÝŽkœÄŽíÄ—sÎ>8qÇ·¢Í?é(~ô¾ÏóÿŸ‹ßóž×'@õö]2Y²dŽW¶ Bõö]rí]›æL¤õàæ²~–ùIëÁ(®´‰,ÿ»äÈ¢8çB—C#Ëü"rqIÒœ ],ó‹¤#WµAÃKË«(ѨØß{‘7ÛíßI(;rýÿ‘ %8ñ/ÕW1$ªyÙ µzx®¢ #~Ûg-”Hc1D ‡ÉÑh‘©à˜{¥:•V;k½dÌUÝtë»::È51ωþlýÌã·Åø'¾D¹°Ž E¶ p»ÅÌ€,ÚÇ1À1L)”HC–$¾=wM`³AOßà0yW×`(*¢lȉ_£c4×ò]ö-!sU7Y}Y–€­ËÑî Дà˜LÐsâR8·-¿¢‚üŠŠŒø‰g‚ñÛbâù'fÿWùC1{C"¯~ÓÉŽ¡¤B‰4Nܸ[—Ö0&Šä*üî«s|æôðÞ³r {€?}Ó•æî¤Ç\ÕMT¿÷ÔI ¦<Ì5KxzÃZ>8cåT—+i£ÛžD¯R2’("Šè¨þžµ½ñgŠY=->Ö:=6*agµ’Ÿ/¿ŠÿØ.&ÍM¤q÷âJn¯«áå†F> QeÒedQdÝ®·PMä––àîïÇm· 3›óx0WV¡_°€¾æ&tf3á@ÿð0£‘’ڥѓ1•غéæ÷õás]¤lÅJAÀöÍiT E‹—ðzqu^ lùŠiõ]4<ÿ0_÷ðËŸRº¬>r<$GËÙ¤z;Ï!z¼~ó*>:ßɾþa†A†º»/ÙZ§ŸáÏc·á±Û ‡BhŒFŠj£T«éknB›ŸOÈ? Xê–%=ç‘9WŠá9#"ìqÀm朔5µk ›ôìm<†¯´]A{ŸÚÌ‘Ž^Þé$02–š2žzüJ%ÿîèf•¥¿6§Ñ3Fà ðéùNª,E¬¯,ãdW¿ø¬¥)†æÔº@Ú¹[×­ä–ò"ûè8’ °ïñûèv¹ÙeµQ­†¿=}??9~vZýßnº™2Sz{ËJxþóÓäˆ"»ï¿3©^—Ç @ž2r»òCtºG2ê§}Ø;-~æÀçÜWYÌÝE¡^Ë©î~~s¸‘pa1 /<±öjZ@Pðâñ³IϹ"£J¼-ÖäEþ¦ª‘¨ýP——›nXο¶>È­͆,ŠT˜MrÕÈ¢H^ÍÏ~°ž/mN6ú‚‘°L…ÙD®Bˆöýñu˰ú‚ü³µ“ïUWpãÂ⸚SëÎ&·Ë¤¦x+M:êt*êÊJ¸¥v∇õ¥…Ø|£Háéõÿq¾(Ä—=v~²5zLIËkd“âÇLø‰W›tüêGßçÃ3V~øÖ\]TÀ«—FÓÆåKøò¢‡¿·õ&ñ+N½¸¤¸[,Û«àíÚÉ­áZx® ÞiéJY#Q»?âùOšØÓÒI¡ÉÈk[6±¹fád®É]³Ð‚BøÈÚÃh D³Í9Þ.Gû6´u³çÌû¨ŠÄžd¦Åéäž°»ðC¬.)`}y1Ç{ЩU\kÒq}e)Í=3ê;½£H’ÌX(D¯Û7k¯²(M®ÊdÜOl¼®ª … PdÐóèõõD™eå–h#ÖÞméâ¤Ý•ØïxߤO‹±”«'Gª Žõ;yûLGÊÜD¡ÑQÂJ%o}måckol\ÇæÕË8ÔðÕD&²$¢VGÖÚüîa|ƒ^‚ŠÐx³­ÅÈgYžÑÇÑ´¶tr’È×v'«Ê‹ÑªTì±öQ•§ç¦J +ÊJØÙd’3™/H³×‹"M¼W0½O¦üLsÔ*†|~F”*Þ·ö2ìóGûJ²”öõ’rB_®†‰Ó¼;2—ƨœl¿±¼ˆçVÖð—¦óI…i\¥ÉÁÞoÃ6âG6èï\‹O’&ûË‘\«ÇÀý+j1œiãñ ×ͬ=Þwâ›.Ërâ}“c<¥™{Ú9Ì“«jñƒïu°®ØÄ=×.¥Å1È·^ÜúAI"/WÅ@K %×\3k¯òÄ¡‡D$]ŠhòpoaûÇ[}ðSëô>—ÀŽkªxý«sI…iÜRWÃS[6âô‘Ÿ«Æéãfk´¿Z«E%ŽvÙù ¸“MKª¨¯,åÐÙvê-Eˆ3úªr5‘X£Eã?îOôSåžr ñ¬RA“cˆpXäÌ —»•JN»Üqëî²ó@Ý"¾Ø±•{÷™µWA•×s¦üÄÆo6ŸgKýî^±hv¸ØÖp"¡‡DÕÛwÉ%Kâ¯o¬²,àÏw¬e· v÷Å/p›^_ öŒÛîhk£ö®M YÛš°´”èµø‚a:†<$z¹,ÇådÀãAàÅ[ײõ¦5<úáQ¬ƒîdû8/(Ðæ¢SåÐ7>_ibýÄÆ9 åFî@á±Ô‹ä±8ÚÚ’ß›ú¿eç±f~}ã*n0Àþ‹Ð?E'O™ÐÛ½þ”Ce²v»Û‹ÝíMiøÝgD–%DI¦"ßÈû-hsÎþ§¨+Ëë'ù’éå%ÖOlEº.ñK›rÎu°µ‹‘ÑO¬^ÊÎjÓŒvûˆŸ —tq¥ËCûS[dF¯Î¡}ÐÍÀˆ?uR–+FÊŸŽ^èçè…þKJ÷> ¿¤iüÑ=ËügVK—ÂåÐÈ2¿HúÊM&¹YæÙל³Ìdÿµ,Kæéxe›ð_a(¤>êù™ôIEND®B`‚flask-oauthlib-0.9.5/example/static/style.css000066400000000000000000000012431327671251000212320ustar00rootroot00000000000000body { font-family: 'Georgia', serif; font-size: 16px; margin: 30px; padding: 0; } img { border: none; } a { color: #335E79; } p.message { color: #335E79; padding: 10px; background: #CADEEB; } p.error { color: #783232; padding: 10px; background: #EBCACA; } input { font-family: 'Georgia', serif; font-size: 16px; border: 1px solid black; color: #335E79; padding: 2px; } input[type="submit"] { background: #CADEEB; color: #335E79; border-color: #335E79; } input[name="openid"] { background: url(openid.png) 4px no-repeat; padding-left: 24px; } h1, h2 { font-weight: normal; } flask-oauthlib-0.9.5/example/templates/000077500000000000000000000000001327671251000200675ustar00rootroot00000000000000flask-oauthlib-0.9.5/example/templates/index.html000066400000000000000000000016051327671251000220660ustar00rootroot00000000000000{% extends "layout.html" %} {% block body %}

Overview

{% if g.user %}

Hello {{ g.user.screen_name }}! Wanna tweet something?

{% if tweets %}

Your Timeline

{% endif %} {% else %}

Sign in to view your public timeline and to tweet from this example application.

sign in

{% endif %} {% endblock %} flask-oauthlib-0.9.5/example/templates/layout.html000066400000000000000000000012051327671251000222700ustar00rootroot00000000000000 {% block title %}Welcome{% endblock %} | Flask OAuth Example

Flask OAuth Example

{% for message in get_flashed_messages() %}

{{ message }}

{% endfor %} {% block body %}{% endblock %} flask-oauthlib-0.9.5/example/twitter.py000066400000000000000000000050761327671251000201550ustar00rootroot00000000000000# coding: utf-8 from flask import Flask from flask import g, session, request, url_for, flash from flask import redirect, render_template from flask_oauthlib.client import OAuth app = Flask(__name__) app.debug = True app.secret_key = 'development' oauth = OAuth(app) twitter = oauth.remote_app( 'twitter', consumer_key='xBeXxg9lyElUgwZT6AZ0A', consumer_secret='aawnSpNTOVuDCjx7HMh6uSXetjNN8zWLpZwCEU4LBrk', base_url='https://api.twitter.com/1.1/', request_token_url='https://api.twitter.com/oauth/request_token', access_token_url='https://api.twitter.com/oauth/access_token', authorize_url='https://api.twitter.com/oauth/authorize' ) @twitter.tokengetter def get_twitter_token(): if 'twitter_oauth' in session: resp = session['twitter_oauth'] return resp['oauth_token'], resp['oauth_token_secret'] @app.before_request def before_request(): g.user = None if 'twitter_oauth' in session: g.user = session['twitter_oauth'] @app.route('/') def index(): tweets = None if g.user is not None: resp = twitter.request('statuses/home_timeline.json') if resp.status == 200: tweets = resp.data else: flash('Unable to load tweets from Twitter.') return render_template('index.html', tweets=tweets) @app.route('/tweet', methods=['POST']) def tweet(): if g.user is None: return redirect(url_for('login', next=request.url)) status = request.form['tweet'] if not status: return redirect(url_for('index')) resp = twitter.post('statuses/update.json', data={ 'status': status }) if resp.status == 403: flash("Error: #%d, %s " % ( resp.data.get('errors')[0].get('code'), resp.data.get('errors')[0].get('message')) ) elif resp.status == 401: flash('Authorization error with Twitter.') else: flash('Successfully tweeted your tweet (ID: #%s)' % resp.data['id']) return redirect(url_for('index')) @app.route('/login') def login(): callback_url = url_for('oauthorized', next=request.args.get('next')) return twitter.authorize(callback=callback_url or request.referrer or None) @app.route('/logout') def logout(): session.pop('twitter_oauth', None) return redirect(url_for('index')) @app.route('/oauthorized') def oauthorized(): resp = twitter.authorized_response() if resp is None: flash('You denied the request to sign in.') else: session['twitter_oauth'] = resp return redirect(url_for('index')) if __name__ == '__main__': app.run() flask-oauthlib-0.9.5/example/weibo.py000066400000000000000000000041511327671251000175510ustar00rootroot00000000000000from flask import Flask, redirect, url_for, session, request, jsonify from flask_oauthlib.client import OAuth app = Flask(__name__) app.debug = True app.secret_key = 'development' oauth = OAuth(app) weibo = oauth.remote_app( 'weibo', consumer_key='909122383', consumer_secret='2cdc60e5e9e14398c1cbdf309f2ebd3a', request_token_params={'scope': 'email,statuses_to_me_read'}, base_url='https://api.weibo.com/2/', authorize_url='https://api.weibo.com/oauth2/authorize', request_token_url=None, access_token_method='POST', access_token_url='https://api.weibo.com/oauth2/access_token', # since weibo's response is a shit, we need to force parse the content content_type='application/json', ) @app.route('/') def index(): if 'oauth_token' in session: access_token = session['oauth_token'][0] resp = weibo.get('statuses/home_timeline.json') return jsonify(resp.data) return redirect(url_for('login')) @app.route('/login') def login(): return weibo.authorize(callback=url_for('authorized', next=request.args.get('next') or request.referrer or None, _external=True)) @app.route('/logout') def logout(): session.pop('oauth_token', None) return redirect(url_for('index')) @app.route('/login/authorized') def authorized(): resp = weibo.authorized_response() if resp is None: return 'Access denied: reason=%s error=%s' % ( request.args['error_reason'], request.args['error_description'] ) session['oauth_token'] = (resp['access_token'], '') return redirect(url_for('index')) @weibo.tokengetter def get_weibo_oauth_token(): return session.get('oauth_token') def change_weibo_header(uri, headers, body): """Since weibo is a rubbish server, it does not follow the standard, we need to change the authorization header for it.""" auth = headers.get('Authorization') if auth: auth = auth.replace('Bearer', 'OAuth2') headers['Authorization'] = auth return uri, headers, body weibo.pre_request = change_weibo_header if __name__ == '__main__': app.run() flask-oauthlib-0.9.5/flask_oauthlib/000077500000000000000000000000001327671251000174255ustar00rootroot00000000000000flask-oauthlib-0.9.5/flask_oauthlib/__init__.py000066400000000000000000000007471327671251000215460ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ flask_oauthlib ~~~~~~~~~~~~~~ Flask-OAuthlib is an extension for Flask that allows you to interact with remote OAuth enabled applications, and also helps you creating your own OAuth servers. :copyright: (c) 2013 by Hsiaoming Yang. :license: BSD, see LICENSE for more details. """ __version__ = "0.9.5" __author__ = "Hsiaoming Yang " __homepage__ = 'https://github.com/lepture/flask-oauthlib' __license__ = 'BSD' flask-oauthlib-0.9.5/flask_oauthlib/client.py000066400000000000000000000603351327671251000212640ustar00rootroot00000000000000# coding: utf-8 """ flask_oauthlib.client ~~~~~~~~~~~~~~~~~~~~~ Implemnts OAuth1 and OAuth2 support for Flask. :copyright: (c) 2013 - 2014 by Hsiaoming Yang. """ import logging import oauthlib.oauth1 import oauthlib.oauth2 from copy import copy from functools import wraps from oauthlib.common import to_unicode, PY3, add_params_to_uri from flask import request, redirect, json, session, current_app from werkzeug import url_quote, url_decode, url_encode from werkzeug import parse_options_header, cached_property from .utils import to_bytes try: from urlparse import urljoin import urllib2 as http except ImportError: from urllib import request as http from urllib.parse import urljoin log = logging.getLogger('flask_oauthlib') if PY3: string_types = (str,) else: string_types = (str, unicode) __all__ = ('OAuth', 'OAuthRemoteApp', 'OAuthResponse', 'OAuthException') class OAuth(object): """Registry for remote applications. :param app: the app instance of Flask Create an instance with Flask:: oauth = OAuth(app) """ state_key = 'oauthlib.client' def __init__(self, app=None): self.remote_apps = {} self.app = app if app: self.init_app(app) def init_app(self, app): """Init app with Flask instance. You can also pass the instance of Flask later:: oauth = OAuth() oauth.init_app(app) """ self.app = app app.extensions = getattr(app, 'extensions', {}) app.extensions[self.state_key] = self def remote_app(self, name, register=True, **kwargs): """Registers a new remote application. :param name: the name of the remote application :param register: whether the remote app will be registered Find more parameters from :class:`OAuthRemoteApp`. """ remote = OAuthRemoteApp(self, name, **kwargs) if register: assert name not in self.remote_apps self.remote_apps[name] = remote return remote def __getattr__(self, key): try: return object.__getattribute__(self, key) except AttributeError: app = self.remote_apps.get(key) if app: return app raise AttributeError('No such app: %s' % key) _etree = None def get_etree(): global _etree if _etree is not None: return _etree try: from lxml import etree as _etree except ImportError: try: from xml.etree import cElementTree as _etree except ImportError: try: from xml.etree import ElementTree as _etree except ImportError: raise TypeError('lxml or etree not found') return _etree def parse_response(resp, content, strict=False, content_type=None): """Parse the response returned by :meth:`OAuthRemoteApp.http_request`. :param resp: response of http_request :param content: content of the response :param strict: strict mode for form urlencoded content :param content_type: assign a content type manually """ if not content_type: content_type = resp.headers.get('content-type', 'application/json') ct, options = parse_options_header(content_type) if ct in ('application/json', 'text/javascript'): if not content: return {} return json.loads(content) if ct in ('application/xml', 'text/xml'): return get_etree().fromstring(content) if ct != 'application/x-www-form-urlencoded' and strict: return content charset = options.get('charset', 'utf-8') return url_decode(content, charset=charset).to_dict() def prepare_request(uri, headers=None, data=None, method=None): """Make request parameters right.""" if headers is None: headers = {} if data and not method: method = 'POST' elif not method: method = 'GET' if method == 'GET' and data: uri = add_params_to_uri(uri, data) data = None return uri, headers, data, method def encode_request_data(data, format): if format is None: return data, None if format == 'json': return json.dumps(data or {}), 'application/json' if format == 'urlencoded': return url_encode(data or {}), 'application/x-www-form-urlencoded' raise TypeError('Unknown format %r' % format) class OAuthResponse(object): def __init__(self, resp, content, content_type=None): self._resp = resp self.raw_data = content self.data = parse_response( resp, content, strict=True, content_type=content_type, ) @property def status(self): """The status code of the response.""" return self._resp.code class OAuthException(RuntimeError): def __init__(self, message, type=None, data=None): self.message = message self.type = type self.data = data def __str__(self): if PY3: return self.message return self.message.encode('utf-8') def __unicode__(self): return self.message class OAuthRemoteApp(object): """Represents a remote application. :param oauth: the associated :class:`OAuth` object :param name: the name of the remote application :param base_url: the base url for every request :param request_token_url: the url for requesting new tokens :param access_token_url: the url for token exchange :param authorize_url: the url for authorization :param consumer_key: the application specific consumer key :param consumer_secret: the application specific consumer secret :param request_token_params: an optional dictionary of parameters to forward to the request token url or authorize url depending on oauth version :param request_token_method: the HTTP method that should be used for the access_token_url. Default is ``GET`` :param access_token_params: an optional dictionary of parameters to forward to the access token url :param access_token_method: the HTTP method that should be used for the access_token_url. Default is ``GET`` :param access_token_headers: additonal headers that should be used for the access_token_url. :param content_type: force to parse the content with this content_type, usually used when the server didn't return the right content type. .. versionadded:: 0.3.0 :param app_key: lazy load configuration from Flask app config with this app key """ def __init__( self, oauth, name, base_url=None, request_token_url=None, access_token_url=None, authorize_url=None, consumer_key=None, consumer_secret=None, rsa_key=None, signature_method=None, request_token_params=None, request_token_method=None, access_token_params=None, access_token_method=None, access_token_headers=None, content_type=None, app_key=None, encoding='utf-8', ): self.oauth = oauth self.name = name self._base_url = base_url self._request_token_url = request_token_url self._access_token_url = access_token_url self._authorize_url = authorize_url self._consumer_key = consumer_key self._consumer_secret = consumer_secret self._rsa_key = rsa_key self._signature_method = signature_method self._request_token_params = request_token_params self._request_token_method = request_token_method self._access_token_params = access_token_params self._access_token_method = access_token_method self._access_token_headers = access_token_headers or {} self._content_type = content_type self._tokengetter = None self.app_key = app_key self.encoding = encoding # Check for required authentication information. # Skip this check if app_key is specified, since the information is # specified in the Flask config, instead. if not app_key: if signature_method == oauthlib.oauth1.SIGNATURE_RSA: # check for consumer_key and rsa_key if not consumer_key or not rsa_key: raise TypeError( "OAuthRemoteApp with RSA authentication requires " "consumer key and rsa key" ) else: # check for consumer_key and consumer_secret if not consumer_key or not consumer_secret: raise TypeError( "OAuthRemoteApp requires consumer key and secret" ) @cached_property def base_url(self): return self._get_property('base_url') @cached_property def request_token_url(self): return self._get_property('request_token_url', None) @cached_property def access_token_url(self): return self._get_property('access_token_url') @cached_property def authorize_url(self): return self._get_property('authorize_url') @cached_property def consumer_key(self): return self._get_property('consumer_key') @cached_property def consumer_secret(self): return self._get_property('consumer_secret') @cached_property def rsa_key(self): return self._get_property('rsa_key') @cached_property def signature_method(self): return self._get_property('signature_method') @cached_property def request_token_params(self): return self._get_property('request_token_params', {}) @cached_property def request_token_method(self): return self._get_property('request_token_method', 'GET') @cached_property def access_token_params(self): return self._get_property('access_token_params', {}) @cached_property def access_token_method(self): return self._get_property('access_token_method', 'POST') @cached_property def content_type(self): return self._get_property('content_type', None) def _get_property(self, key, default=False): attr = getattr(self, '_%s' % key) if attr is not None: return attr if not self.app_key: if default is not False: return default return attr app = self.oauth.app or current_app if self.app_key in app.config: # works with dict config config = app.config[self.app_key] if default is not False: return config.get(key, default) return config[key] # works with plain text config config_key = "%s_%s" % (self.app_key, key.upper()) if default is not False: return app.config.get(config_key, default) return app.config[config_key] def get_oauth1_client_params(self, token): params = copy(self.request_token_params) or {} if token and isinstance(token, (tuple, list)): params["resource_owner_key"] = token[0] params["resource_owner_secret"] = token[1] # Set params for SIGNATURE_RSA if self.signature_method == oauthlib.oauth1.SIGNATURE_RSA: params["signature_method"] = self.signature_method params["rsa_key"] = self.rsa_key return params def make_client(self, token=None): # request_token_url is for oauth1 if self.request_token_url: # get params for client params = self.get_oauth1_client_params(token) client = oauthlib.oauth1.Client( client_key=self.consumer_key, client_secret=self.consumer_secret, **params ) else: if token: if isinstance(token, (tuple, list)): token = {'access_token': token[0]} elif isinstance(token, string_types): token = {'access_token': token} client = oauthlib.oauth2.WebApplicationClient( self.consumer_key, token=token ) return client @staticmethod def http_request(uri, headers=None, data=None, method=None): uri, headers, data, method = prepare_request( uri, headers, data, method ) log.debug('Request %r with %r method' % (uri, method)) req = http.Request(uri, headers=headers, data=data) req.get_method = lambda: method.upper() try: resp = http.urlopen(req) content = resp.read() resp.close() return resp, content except http.HTTPError as resp: content = resp.read() resp.close() return resp, content def get(self, *args, **kwargs): """Sends a ``GET`` request. Accepts the same parameters as :meth:`request`. """ kwargs['method'] = 'GET' return self.request(*args, **kwargs) def post(self, *args, **kwargs): """Sends a ``POST`` request. Accepts the same parameters as :meth:`request`. """ kwargs['method'] = 'POST' return self.request(*args, **kwargs) def put(self, *args, **kwargs): """Sends a ``PUT`` request. Accepts the same parameters as :meth:`request`. """ kwargs['method'] = 'PUT' return self.request(*args, **kwargs) def delete(self, *args, **kwargs): """Sends a ``DELETE`` request. Accepts the same parameters as :meth:`request`. """ kwargs['method'] = 'DELETE' return self.request(*args, **kwargs) def patch(self, *args, **kwargs): """Sends a ``PATCH`` request. Accepts the same parameters as :meth:`post`. """ kwargs['method'] = 'PATCH' return self.request(*args, **kwargs) def request(self, url, data=None, headers=None, format='urlencoded', method='GET', content_type=None, token=None): """ Sends a request to the remote server with OAuth tokens attached. :param data: the data to be sent to the server. :param headers: an optional dictionary of headers. :param format: the format for the `data`. Can be `urlencoded` for URL encoded data or `json` for JSON. :param method: the HTTP request method to use. :param content_type: an optional content type. If a content type is provided, the data is passed as it, and the `format` is ignored. :param token: an optional token to pass, if it is None, token will be generated by tokengetter. """ headers = dict(headers or {}) if token is None: token = self.get_request_token() client = self.make_client(token) url = self.expand_url(url) if method == 'GET': assert format == 'urlencoded' if data: url = add_params_to_uri(url, data) data = None else: if content_type is None: data, content_type = encode_request_data(data, format) if content_type is not None: headers['Content-Type'] = content_type if self.request_token_url: # oauth1 uri, headers, body = client.sign( url, http_method=method, body=data, headers=headers ) else: # oauth2 uri, headers, body = client.add_token( url, http_method=method, body=data, headers=headers ) if hasattr(self, 'pre_request'): # This is designed for some rubbish services like weibo. # Since they don't follow the standards, we need to # change the uri, headers, or body. uri, headers, body = self.pre_request(uri, headers, body) if body: data = to_bytes(body, self.encoding) else: data = None resp, content = self.http_request( uri, headers, data=to_bytes(body, self.encoding), method=method ) return OAuthResponse(resp, content, self.content_type) def authorize(self, callback=None, state=None, **kwargs): """ Returns a redirect response to the remote authorization URL with the signed callback given. :param callback: a redirect url for the callback :param state: an optional value to embed in the OAuth request. Use this if you want to pass around application state (e.g. CSRF tokens). :param kwargs: add optional key/value pairs to the query string """ params = dict(self.request_token_params) or {} params.update(**kwargs) if self.request_token_url: token = self.generate_request_token(callback)[0] url = '%s?oauth_token=%s' % ( self.expand_url(self.authorize_url), url_quote(token) ) if params: url += '&' + url_encode(params) else: assert callback is not None, 'Callback is required for OAuth2' client = self.make_client() if 'scope' in params: scope = params.pop('scope') else: scope = None if isinstance(scope, str): # oauthlib need unicode scope = _encode(scope, self.encoding) if 'state' in params: if not state: state = params.pop('state') else: # remove state in params params.pop('state') if callable(state): # state can be function for generate a random string state = state() session['%s_oauthredir' % self.name] = callback url = client.prepare_request_uri( self.expand_url(self.authorize_url), redirect_uri=callback, scope=scope, state=state, **params ) return redirect(url) def tokengetter(self, f): """ Register a function as token getter. """ self._tokengetter = f return f def expand_url(self, url): return urljoin(self.base_url, url) def generate_request_token(self, callback=None): # for oauth1 only if callback is not None: callback = urljoin(request.url, callback) client = self.make_client() client.callback_uri = _encode(callback, self.encoding) realm = self.request_token_params.get('realm') realms = self.request_token_params.get('realms') if not realm and realms: realm = ' '.join(realms) uri, headers, _ = client.sign( self.expand_url(self.request_token_url), http_method=self.request_token_method, realm=realm, ) log.debug('Generate request token header %r', headers) resp, content = self.http_request( uri, headers, method=self.request_token_method, ) data = parse_response(resp, content) if not data: raise OAuthException( 'Invalid token response from %s' % self.name, type='token_generation_failed' ) if resp.code not in (200, 201): message = 'Failed to generate request token' if 'oauth_problem' in data: message += ' (%s)' % data['oauth_problem'] raise OAuthException( message, type='token_generation_failed', data=data, ) tup = (data['oauth_token'], data['oauth_token_secret']) session['%s_oauthtok' % self.name] = tup return tup def get_request_token(self): assert self._tokengetter is not None, 'missing tokengetter' rv = self._tokengetter() if rv is None: raise OAuthException('No token available', type='token_missing') return rv def handle_oauth1_response(self, args): """Handles an oauth1 authorization response.""" client = self.make_client() client.verifier = args.get('oauth_verifier') tup = session.get('%s_oauthtok' % self.name) if not tup: raise OAuthException( 'Token not found, maybe you disabled cookie', type='token_not_found' ) client.resource_owner_key = tup[0] client.resource_owner_secret = tup[1] uri, headers, data = client.sign( self.expand_url(self.access_token_url), _encode(self.access_token_method) ) headers.update(self._access_token_headers) resp, content = self.http_request( uri, headers, to_bytes(data, self.encoding), method=self.access_token_method ) data = parse_response(resp, content) if resp.code not in (200, 201): raise OAuthException( 'Invalid response from %s' % self.name, type='invalid_response', data=data ) return data def handle_oauth2_response(self, args): """Handles an oauth2 authorization response.""" client = self.make_client() remote_args = { 'code': args.get('code'), 'client_secret': self.consumer_secret, 'redirect_uri': session.get('%s_oauthredir' % self.name) } log.debug('Prepare oauth2 remote args %r', remote_args) remote_args.update(self.access_token_params) headers = copy(self._access_token_headers) if self.access_token_method == 'POST': headers.update({'Content-Type': 'application/x-www-form-urlencoded'}) body = client.prepare_request_body(**remote_args) resp, content = self.http_request( self.expand_url(self.access_token_url), headers=headers, data=to_bytes(body, self.encoding), method=self.access_token_method, ) elif self.access_token_method == 'GET': qs = client.prepare_request_body(**remote_args) url = self.expand_url(self.access_token_url) url += ('?' in url and '&' or '?') + qs resp, content = self.http_request( url, headers=headers, method=self.access_token_method, ) else: raise OAuthException( 'Unsupported access_token_method: %s' % self.access_token_method ) data = parse_response(resp, content, content_type=self.content_type) if resp.code not in (200, 201): raise OAuthException( 'Invalid response from %s' % self.name, type='invalid_response', data=data ) return data def handle_unknown_response(self): """Handles a unknown authorization response.""" return None def authorized_response(self, args=None): """Handles authorization response smartly.""" if args is None: args = request.args if 'oauth_verifier' in args: data = self.handle_oauth1_response(args) elif 'code' in args: data = self.handle_oauth2_response(args) else: data = self.handle_unknown_response() # free request token session.pop('%s_oauthtok' % self.name, None) session.pop('%s_oauthredir' % self.name, None) return data def authorized_handler(self, f): """Handles an OAuth callback. .. versionchanged:: 0.7 @authorized_handler is deprecated in favor of authorized_response. """ @wraps(f) def decorated(*args, **kwargs): log.warn( '@authorized_handler is deprecated in favor of ' 'authorized_response' ) data = self.authorized_response() return f(*((data,) + args), **kwargs) return decorated def _encode(text, encoding='utf-8'): if encoding: return to_unicode(text, encoding) return text flask-oauthlib-0.9.5/flask_oauthlib/contrib/000077500000000000000000000000001327671251000210655ustar00rootroot00000000000000flask-oauthlib-0.9.5/flask_oauthlib/contrib/__init__.py000066400000000000000000000002511327671251000231740ustar00rootroot00000000000000# coding: utf-8 """ flask_oauthlib.contrib ~~~~~~~~~~~~~~~~~~~~~~ Contributions for Flask OAuthlib. :copyright: (c) 2013 - 2014 by Hsiaoming Yang. """ flask-oauthlib-0.9.5/flask_oauthlib/contrib/apps.py000066400000000000000000000165771327671251000224220ustar00rootroot00000000000000""" flask_oauthlib.contrib.apps ~~~~~~~~~~~~~~~~~~~~~~~~~~~ The bundle of remote app factories for famous third platforms. Usage:: from flask import Flask from flask_oauthlib.client import OAuth from flask_oauthlib.contrib.apps import github app = Flask(__name__) oauth = OAuth(app) github.register_to(oauth, scope=['user:email']) github.register_to(oauth, name='github2') Of course, it requires consumer keys in your config:: GITHUB_CONSUMER_KEY = '' GITHUB_CONSUMER_SECRET = '' GITHUB2_CONSUMER_KEY = '' GITHUB2_CONSUMER_SECRET = '' Some apps with OAuth 1.0a such as Twitter could not accept the ``scope`` argument. Contributed by: tonyseek """ import copy from oauthlib.common import unicode_type, bytes_type __all__ = ['douban', 'dropbox', 'facebook', 'github', 'google', 'linkedin', 'twitter', 'weibo'] class RemoteAppFactory(object): """The factory to create remote app and bind it to given extension. :param default_name: the default name which be used for registering. :param kwargs: the pre-defined kwargs. :param docstring: the docstring of factory. """ def __init__(self, default_name, kwargs, docstring=''): assert 'name' not in kwargs assert 'register' not in kwargs self.default_name = default_name self.kwargs = kwargs self._kwargs_processor = None self.__doc__ = docstring.lstrip() def register_to(self, oauth, name=None, **kwargs): """Creates a remote app and registers it.""" kwargs = self._process_kwargs( name=(name or self.default_name), **kwargs) return oauth.remote_app(**kwargs) def create(self, oauth, **kwargs): """Creates a remote app only.""" kwargs = self._process_kwargs( name=self.default_name, register=False, **kwargs) return oauth.remote_app(**kwargs) def kwargs_processor(self, fn): """Sets a function to process kwargs before creating any app.""" self._kwargs_processor = fn return fn def _process_kwargs(self, **kwargs): final_kwargs = copy.deepcopy(self.kwargs) # merges with pre-defined kwargs final_kwargs.update(copy.deepcopy(kwargs)) # use name as app key final_kwargs.setdefault('app_key', final_kwargs['name'].upper()) # processes by pre-defined function if self._kwargs_processor is not None: final_kwargs = self._kwargs_processor(**final_kwargs) return final_kwargs def make_scope_processor(default_scope): def processor(**kwargs): # request_token_params scope = kwargs.pop('scope', [default_scope]) # default scope if not isinstance(scope, (unicode_type, bytes_type)): scope = ','.join(scope) # allows list-style scope request_token_params = kwargs.setdefault('request_token_params', {}) request_token_params.setdefault('scope', scope) # doesn't override return kwargs return processor douban = RemoteAppFactory('douban', { 'base_url': 'https://api.douban.com/v2/', 'request_token_url': None, 'access_token_url': 'https://www.douban.com/service/auth2/token', 'authorize_url': 'https://www.douban.com/service/auth2/auth', 'access_token_method': 'POST', }, """ The OAuth app for douban.com API. :param scope: optional. default: ``['douban_basic_common']``. see also: http://developers.douban.com/wiki/?title=oauth2 """) douban.kwargs_processor(make_scope_processor('douban_basic_common')) dropbox = RemoteAppFactory('dropbox', { 'base_url': 'https://www.dropbox.com/1/', 'request_token_url': None, 'access_token_url': 'https://api.dropbox.com/1/oauth2/token', 'authorize_url': 'https://www.dropbox.com/1/oauth2/authorize', 'access_token_method': 'POST', 'request_token_params': {}, }, """The OAuth app for Dropbox API.""") facebook = RemoteAppFactory('facebook', { 'request_token_params': {'scope': 'email'}, 'base_url': 'https://graph.facebook.com', 'request_token_url': None, 'access_token_url': '/oauth/access_token', 'authorize_url': 'https://www.facebook.com/dialog/oauth', }, """ The OAuth app for Facebook API. :param scope: optional. default: ``['email']``. """) facebook.kwargs_processor(make_scope_processor('email')) github = RemoteAppFactory('github', { 'base_url': 'https://api.github.com/', 'request_token_url': None, 'access_token_method': 'POST', 'access_token_url': 'https://github.com/login/oauth/access_token', 'authorize_url': 'https://github.com/login/oauth/authorize', }, """ The OAuth app for GitHub API. :param scope: optional. default: ``['user:email']``. """) github.kwargs_processor(make_scope_processor('user:email')) google = RemoteAppFactory('google', { 'base_url': 'https://www.googleapis.com/oauth2/v1/', 'request_token_url': None, 'access_token_method': 'POST', 'access_token_url': 'https://accounts.google.com/o/oauth2/token', 'authorize_url': 'https://accounts.google.com/o/oauth2/auth', }, """ The OAuth app for Google API. :param scope: optional. default: ``['email']``. """) google.kwargs_processor(make_scope_processor( 'email')) twitter = RemoteAppFactory('twitter', { 'base_url': 'https://api.twitter.com/1.1/', 'request_token_url': 'https://api.twitter.com/oauth/request_token', 'access_token_url': 'https://api.twitter.com/oauth/access_token', 'authorize_url': 'https://api.twitter.com/oauth/authenticate', }, """The OAuth app for Twitter API.""") weibo = RemoteAppFactory('weibo', { 'base_url': 'https://api.weibo.com/2/', 'authorize_url': 'https://api.weibo.com/oauth2/authorize', 'request_token_url': None, 'access_token_method': 'POST', 'access_token_url': 'https://api.weibo.com/oauth2/access_token', # since weibo's response is a shit, we need to force parse the content 'content_type': 'application/json', }, """ The OAuth app for weibo.com API. :param scope: optional. default: ``['email']`` """) weibo.kwargs_processor(make_scope_processor('email')) def change_weibo_header(uri, headers, body): """Since weibo is a rubbish server, it does not follow the standard, we need to change the authorization header for it.""" auth = headers.get('Authorization') if auth: auth = auth.replace('Bearer', 'OAuth2') headers['Authorization'] = auth return uri, headers, body weibo.pre_request = change_weibo_header linkedin = RemoteAppFactory('linkedin', { 'request_token_params': {'state': 'RandomString'}, 'base_url': 'https://api.linkedin.com/v1/', 'request_token_url': None, 'access_token_method': 'POST', 'access_token_url': 'https://www.linkedin.com/uas/oauth2/accessToken', 'authorize_url': 'https://www.linkedin.com/uas/oauth2/authorization', }, """ The OAuth app for LinkedIn API. :param scope: optional. default: ``['r_basicprofile']`` """) linkedin.kwargs_processor(make_scope_processor('r_basicprofile')) def change_linkedin_query(uri, headers, body): auth = headers.pop('Authorization') headers['x-li-format'] = 'json' if auth: auth = auth.replace('Bearer', '').strip() if '?' in uri: uri += '&oauth2_access_token=' + auth else: uri += '?oauth2_access_token=' + auth return uri, headers, body linkedin.pre_request = change_linkedin_query flask-oauthlib-0.9.5/flask_oauthlib/contrib/cache.py000066400000000000000000000054671327671251000225160ustar00rootroot00000000000000# coding: utf-8 from werkzeug.contrib.cache import NullCache, SimpleCache, FileSystemCache from werkzeug.contrib.cache import MemcachedCache, RedisCache class Cache(object): def __init__(self, app, config_prefix='OAUTHLIB', **kwargs): self.config_prefix = config_prefix self.config = app.config cache_type = '_%s' % self._config('type') kwargs.update(dict( default_timeout=self._config('DEFAULT_TIMEOUT', 100) )) try: self.cache = getattr(self, cache_type)(**kwargs) except AttributeError: raise RuntimeError( '`%s` is not a valid cache type!' % cache_type ) app.extensions[config_prefix.lower() + '_cache'] = self.cache def __getattr__(self, key): try: return object.__getattribute__(self, key) except AttributeError: try: return getattr(self.cache, key) except AttributeError: raise AttributeError('No such attribute: %s' % key) def _config(self, key, default='error'): key = key.upper() prior = '%s_CACHE_%s' % (self.config_prefix, key) if prior in self.config: return self.config[prior] fallback = 'CACHE_%s' % key if fallback in self.config: return self.config[fallback] if default == 'error': raise RuntimeError('%s is missing.' % prior) return default def _null(self, **kwargs): """Returns a :class:`NullCache` instance""" return NullCache() def _simple(self, **kwargs): """Returns a :class:`SimpleCache` instance .. warning:: This cache system might not be thread safe. Use with caution. """ kwargs.update(dict(threshold=self._config('threshold', 500))) return SimpleCache(**kwargs) def _memcache(self, **kwargs): """Returns a :class:`MemcachedCache` instance""" kwargs.update(dict( servers=self._config('MEMCACHED_SERVERS', None), key_prefix=self._config('key_prefix', None), )) return MemcachedCache(**kwargs) def _redis(self, **kwargs): """Returns a :class:`RedisCache` instance""" kwargs.update(dict( host=self._config('REDIS_HOST', 'localhost'), port=self._config('REDIS_PORT', 6379), password=self._config('REDIS_PASSWORD', None), db=self._config('REDIS_DB', 0), key_prefix=self._config('KEY_PREFIX', None), )) return RedisCache(**kwargs) def _filesystem(self, **kwargs): """Returns a :class:`FileSystemCache` instance""" kwargs.update(dict( threshold=self._config('threshold', 500), )) return FileSystemCache(self._config('dir', None), **kwargs) flask-oauthlib-0.9.5/flask_oauthlib/contrib/client/000077500000000000000000000000001327671251000223435ustar00rootroot00000000000000flask-oauthlib-0.9.5/flask_oauthlib/contrib/client/__init__.py000066400000000000000000000063151327671251000244610ustar00rootroot00000000000000import copy from flask import current_app from werkzeug.local import LocalProxy from .application import OAuth1Application, OAuth2Application __all__ = ['OAuth', 'OAuth1Application', 'OAuth2Application'] class OAuth(object): """The extension to integrate OAuth 1.0a/2.0 to Flask applications. oauth = OAuth(app) or:: oauth = OAuth() oauth.init_app(app) """ state_key = 'oauthlib.contrib.client' def __init__(self, app=None): self.remote_apps = {} if app is not None: self.init_app(app) def init_app(self, app): app.extensions = getattr(app, 'extensions', {}) app.extensions[self.state_key] = OAuthState() def add_remote_app(self, remote_app, name=None, **kwargs): """Adds remote application and applies custom attributes on it. If the application instance's name is different from the argument provided name, or the keyword arguments is not empty, then the application instance will not be modified but be copied as a prototype. :param remote_app: the remote application instance. :type remote_app: the subclasses of :class:`BaseApplication` :params kwargs: the overriding attributes for the application instance. """ if name is None: name = remote_app.name if name != remote_app.name or kwargs: remote_app = copy.copy(remote_app) remote_app.name = name vars(remote_app).update(kwargs) if not hasattr(remote_app, 'clients'): remote_app.clients = cached_clients self.remote_apps[name] = remote_app return remote_app def remote_app(self, name, version=None, **kwargs): """Creates and adds new remote application. :param name: the remote application's name. :param version: '1' or '2', the version code of OAuth protocol. :param kwargs: the attributes of remote application. """ if version is None: if 'request_token_url' in kwargs: version = '1' else: version = '2' if version == '1': remote_app = OAuth1Application(name, clients=cached_clients) elif version == '2': remote_app = OAuth2Application(name, clients=cached_clients) else: raise ValueError('unkonwn version %r' % version) return self.add_remote_app(remote_app, **kwargs) def __getitem__(self, name): return self.remote_apps[name] def __getattr__(self, key): try: return object.__getattribute__(self, key) except AttributeError: app = self.remote_apps.get(key) if app: return app raise AttributeError('No such app: %s' % key) class OAuthState(object): def __init__(self): self.cached_clients = {} def get_cached_clients(): """Gets the cached clients dictionary in current context.""" if OAuth.state_key not in current_app.extensions: raise RuntimeError('%r is not initialized.' % current_app) state = current_app.extensions[OAuth.state_key] return state.cached_clients cached_clients = LocalProxy(get_cached_clients) flask-oauthlib-0.9.5/flask_oauthlib/contrib/client/application.py000066400000000000000000000272511327671251000252270ustar00rootroot00000000000000""" flask_oauthlib.contrib.client ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ An experiment client with requests-oauthlib as backend. """ import os import contextlib import warnings try: from urllib.parse import urljoin except ImportError: from urlparse import urljoin from flask import current_app, redirect, request from requests_oauthlib import OAuth1Session, OAuth2Session from requests_oauthlib.oauth1_session import TokenMissing from oauthlib.oauth2.rfc6749.errors import MissingCodeError from werkzeug.utils import import_string from .descriptor import OAuthProperty, WebSessionData from .structure import OAuth1Response, OAuth2Response from .exceptions import AccessTokenNotFound from .signals import request_token_fetched __all__ = ['OAuth1Application', 'OAuth2Application'] class BaseApplication(object): """The base class of OAuth application. An application instance could be used in mupltiple context. It never stores any session-scope state in the ``__dict__`` of itself. :param name: the name of this application. :param clients: optional. a reference to the cached clients dictionary. """ session_class = None endpoint_url = OAuthProperty('endpoint_url', default='') def __init__(self, name, clients=None, **kwargs): # oauth property required self.name = name if clients: self.clients = clients # other descriptor assignable attributes for k, v in kwargs.items(): if not hasattr(self.__class__, k): raise TypeError('descriptor %r not found' % k) setattr(self, k, v) def __repr__(self): class_name = self.__class__.__name__ return '<%s:%s at %s>' % (class_name, self.name, hex(id(self))) def tokengetter(self, fn): self._tokengetter = fn return fn def obtain_token(self): """Obtains the access token by calling ``tokengetter`` which was defined by users. :returns: token or ``None``. """ tokengetter = getattr(self, '_tokengetter', None) if tokengetter is None: raise RuntimeError('%r missing tokengetter' % self) return tokengetter() @property def client(self): """The lazy-created OAuth session with the return value of :meth:`tokengetter`. :returns: The OAuth session instance or ``None`` while token missing. """ token = self.obtain_token() if token is None: raise AccessTokenNotFound return self._make_client_with_token(token) def _make_client_with_token(self, token): """Uses cached client or create new one with specific token.""" cached_clients = getattr(self, 'clients', None) hashed_token = _hash_token(self, token) if cached_clients and hashed_token in cached_clients: return cached_clients[hashed_token] client = self.make_client(token) # implemented in subclasses if cached_clients: cached_clients[hashed_token] = client return client def authorize(self, callback_uri, code=302): """Redirects to third-part URL and authorizes. :param callback_uri: the callback URI. if you generate it with the :func:`~flask.url_for`, don't forget to use the ``_external=True`` keyword argument. :param code: default is 302. the HTTP code for redirection. :returns: the redirection response. """ raise NotImplementedError def authorized_response(self): """Obtains access token from third-part API. :returns: the response with the type of :class:`OAuthResponse` dict, or ``None`` if the authorization has been denied. """ raise NotImplementedError def request(self, method, url, token=None, *args, **kwargs): if token is None: client = self.client else: client = self._make_client_with_token(token) url = urljoin(self.endpoint_url, url) return getattr(client, method)(url, *args, **kwargs) def head(self, *args, **kwargs): return self.request('head', *args, **kwargs) def get(self, *args, **kwargs): return self.request('get', *args, **kwargs) def post(self, *args, **kwargs): return self.request('post', *args, **kwargs) def put(self, *args, **kwargs): return self.request('put', *args, **kwargs) def delete(self, *args, **kwargs): return self.request('delete', *args, **kwargs) def patch(self, *args, **kwargs): return self.request('patch', *args, **kwargs) class OAuth1Application(BaseApplication): """The remote application for OAuth 1.0a.""" request_token_url = OAuthProperty('request_token_url') access_token_url = OAuthProperty('access_token_url') authorization_url = OAuthProperty('authorization_url') consumer_key = OAuthProperty('consumer_key') consumer_secret = OAuthProperty('consumer_secret') session_class = OAuth1Session def make_client(self, token): """Creates a client with specific access token pair. :param token: a tuple of access token pair ``(token, token_secret)`` or a dictionary of access token response. :returns: a :class:`requests_oauthlib.oauth1_session.OAuth1Session` object. """ if isinstance(token, dict): access_token = token['oauth_token'] access_token_secret = token['oauth_token_secret'] else: access_token, access_token_secret = token return self.make_oauth_session( resource_owner_key=access_token, resource_owner_secret=access_token_secret) def authorize(self, callback_uri, code=302): # TODO add support for oauth_callback=oob (out-of-band) here # http://tools.ietf.org/html/rfc5849#section-2.1 oauth = self.make_oauth_session(callback_uri=callback_uri) # fetches request token token = oauth.fetch_request_token(self.request_token_url) request_token_fetched.send(self, response=OAuth1Response(token)) # TODO check oauth_callback_confirmed here # http://tools.ietf.org/html/rfc5849#section-2.1 # redirects to third-part URL authorization_url = oauth.authorization_url(self.authorization_url) return redirect(authorization_url, code) def authorized_response(self): oauth = self.make_oauth_session() # obtains verifier try: oauth.parse_authorization_response(request.url) except TokenMissing: return # authorization denied # obtains access token token = oauth.fetch_access_token(self.access_token_url) return OAuth1Response(token) def make_oauth_session(self, **kwargs): oauth = self.session_class( self.consumer_key, client_secret=self.consumer_secret, **kwargs) return oauth class OAuth2Application(BaseApplication): """The remote application for OAuth 2.""" session_class = OAuth2Session access_token_url = OAuthProperty('access_token_url') authorization_url = OAuthProperty('authorization_url') refresh_token_url = OAuthProperty('refresh_token_url', default='') client_id = OAuthProperty('client_id') client_secret = OAuthProperty('client_secret') scope = OAuthProperty('scope', default=None) compliance_fixes = OAuthProperty('compliance_fixes', default=None) _session_state = WebSessionData('state') _session_redirect_url = WebSessionData('redir') def make_client(self, token): """Creates a client with specific access token dictionary. :param token: a dictionary of access token response. :returns: a :class:`requests_oauthlib.oauth2_session.OAuth2Session` object. """ return self.make_oauth_session(token=token) def tokensaver(self, fn): """A decorator to register a callback function for saving refreshed token while the old token has expired and the ``refresh_token_url`` has been specified. It is necessary for using the automatic refresh mechanism. :param fn: the callback function with ``token`` as its unique argument. """ self._tokensaver = fn return fn def authorize(self, callback_uri, code=302, **kwargs): oauth = self.make_oauth_session(redirect_uri=callback_uri) authorization_url, state = oauth.authorization_url( self.authorization_url, **kwargs) self._session_state = state self._session_redirect_url = callback_uri return redirect(authorization_url, code) def authorized_response(self): oauth = self.make_oauth_session( state=self._session_state, redirect_uri=self._session_redirect_url) del self._session_state del self._session_redirect_url with self.insecure_transport(): try: token = oauth.fetch_token( self.access_token_url, client_secret=self.client_secret, authorization_response=request.url) except MissingCodeError: return return OAuth2Response(token) def make_oauth_session(self, **kwargs): kwargs.setdefault('scope', self.scope) # configures automatic token refresh if possible if self.refresh_token_url: if not hasattr(self, '_tokensaver'): raise RuntimeError('missing tokensaver') kwargs.setdefault('auto_refresh_url', self.refresh_token_url) kwargs.setdefault('auto_refresh_kwargs', { 'client_id': self.client_id, 'client_secret': self.client_secret, }) kwargs.setdefault('token_updater', self._tokensaver) # creates session oauth = self.session_class(self.client_id, **kwargs) # patches session compliance_fixes = self.compliance_fixes if compliance_fixes is not None: if compliance_fixes.startswith('.'): compliance_fixes = \ 'requests_oauthlib.compliance_fixes' + compliance_fixes apply_fixes = import_string(compliance_fixes) oauth = apply_fixes(oauth) return oauth @contextlib.contextmanager def insecure_transport(self): """Creates a context to enable the oauthlib environment variable in order to debug with insecure transport. """ origin = os.environ.get('OAUTHLIB_INSECURE_TRANSPORT') if current_app.debug or current_app.testing: try: os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' yield finally: if origin: os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = origin else: os.environ.pop('OAUTHLIB_INSECURE_TRANSPORT', None) else: if origin: warnings.warn( 'OAUTHLIB_INSECURE_TRANSPORT has been found in os.environ ' 'but the app is not running in debug mode or testing mode.' ' It may put you in danger of the Man-in-the-middle attack' ' while using OAuth 2.', RuntimeWarning) yield def _hash_token(application, token): """Creates a hashable object for given token then we could use it as a dictionary key. """ if isinstance(token, dict): hashed_token = tuple(sorted(token.items())) elif isinstance(token, tuple): hashed_token = token else: raise TypeError('%r is unknown type of token' % token) return (application.__class__.__name__, application.name, hashed_token) flask-oauthlib-0.9.5/flask_oauthlib/contrib/client/descriptor.py000066400000000000000000000041471327671251000251010ustar00rootroot00000000000000from flask import current_app, session __all__ = ['OAuthProperty', 'WebSessionData'] class OAuthProperty(object): """The property which providing config item to remote applications. The application classes must have ``name`` to identity themselves. """ _missing = object() def __init__(self, name, default=_missing): self.name = name self.default = default def __get__(self, instance, owner): if instance is None: return self # instance resources instance_namespace = vars(instance) instance_ident = instance.name # gets from instance namespace if self.name in instance_namespace: return instance_namespace[self.name] # gets from app config (or default value) config_name = '{0}_{1}'.format(instance_ident, self.name).upper() if config_name not in current_app.config: if self.default is not self._missing: return self.default exception_message = ( '{0!r} missing {1} \n\n You need to provide it in arguments' ' `{0.__class__.__name__}(..., {1}="foobar", ...)` or in ' 'app.config `{2}`').format(instance, self.name, config_name) raise RuntimeError(exception_message) return current_app.config[config_name] def __set__(self, instance, value): # assigns into instance namespace instance_namespace = vars(instance) instance_namespace[self.name] = value class WebSessionData(object): """The property which providing accessing of Flask session.""" key_format = '_oauth_{0}_{1}' def __init__(self, ident): self.ident = ident def make_key(self, instance): return self.key_format.format(instance.name, self.ident) def __get__(self, instance, owner): if instance is None: return self return session.get(self.make_key(instance)) def __set__(self, instance, value): session[self.make_key(instance)] = value def __delete__(self, instance): session.pop(self.make_key(instance), None) flask-oauthlib-0.9.5/flask_oauthlib/contrib/client/exceptions.py000066400000000000000000000002261327671251000250760ustar00rootroot00000000000000__all__ = ['OAuthException', 'AccessTokenNotFound'] class OAuthException(Exception): pass class AccessTokenNotFound(OAuthException): pass flask-oauthlib-0.9.5/flask_oauthlib/contrib/client/signals.py000066400000000000000000000002421327671251000243530ustar00rootroot00000000000000from flask.signals import Namespace __all__ = ['request_token_fetched'] _signals = Namespace() request_token_fetched = _signals.signal('request-token-fetched') flask-oauthlib-0.9.5/flask_oauthlib/contrib/client/structure.py000066400000000000000000000010701327671251000247530ustar00rootroot00000000000000import operator __all__ = ['OAuth1Response', 'OAuth2Response'] class OAuth1Response(dict): token = property(operator.itemgetter('oauth_token')) token_secret = property(operator.itemgetter('oauth_token_secret')) class OAuth2Response(dict): access_token = property(operator.itemgetter('access_token')) refresh_token = property(operator.itemgetter('refresh_token')) token_type = property(operator.itemgetter('token_type')) expires_in = property(operator.itemgetter('expires_in')) expires_at = property(operator.itemgetter('expires_at')) flask-oauthlib-0.9.5/flask_oauthlib/contrib/oauth2.py000066400000000000000000000244171327671251000226510ustar00rootroot00000000000000# coding: utf-8 """ flask_oauthlib.contrib.oauth2 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SQLAlchemy and Grant-Caching for OAuth2 provider. contributed by: Randy Topliffe """ import logging from datetime import datetime, timedelta from .cache import Cache __all__ = ('bind_cache_grant', 'bind_sqlalchemy') log = logging.getLogger('flask_oauthlib') class Grant(object): """Grant is only used by `GrantCacheBinding` to store the data returned from the cache system. :param cache: Werkzeug cache instance :param client_id: ID of the client :param code: A random string :param redirect_uri: A URI string :param scopes: A space delimited list of scopes :param user: the authorizatopm user """ def __init__(self, cache=None, client_id=None, code=None, redirect_uri=None, scopes=None, user=None): self._cache = cache self.client_id = client_id self.code = code self.redirect_uri = redirect_uri self.scopes = scopes self.user = user def delete(self): """Removes itself from the cache Note: This is required by the oauthlib """ log.debug( "Deleting grant %s for client %s" % (self.code, self.client_id) ) self._cache.delete(self.key) return None @property def key(self): """The string used as the key for the cache""" return '%s%s' % (self.code, self.client_id) def __getitem__(self, item): return getattr(self, item) def keys(self): return ['client_id', 'code', 'redirect_uri', 'scopes', 'user'] def bind_cache_grant(app, provider, current_user, config_prefix='OAUTH2'): """Configures an :class:`OAuth2Provider` instance to use various caching systems to get and set the grant token. This removes the need to register :func:`grantgetter` and :func:`grantsetter` yourself. :param app: Flask application instance :param provider: :class:`OAuth2Provider` instance :param current_user: function that returns an :class:`User` object :param config_prefix: prefix for config A usage example:: oauth = OAuth2Provider(app) app.config.update({'OAUTH2_CACHE_TYPE': 'redis'}) bind_cache_grant(app, oauth, current_user) You can define which cache system you would like to use by setting the following configuration option:: OAUTH2_CACHE_TYPE = 'null' // memcache, simple, redis, filesystem For more information on the supported cache systems please visit: `Cache `_ """ cache = Cache(app, config_prefix) @provider.grantsetter def create_grant(client_id, code, request, *args, **kwargs): """Sets the grant token with the configured cache system""" grant = Grant( cache, client_id=client_id, code=code['code'], redirect_uri=request.redirect_uri, scopes=request.scopes, user=current_user(), ) log.debug("Set Grant Token with key %s" % grant.key) cache.set(grant.key, dict(grant)) @provider.grantgetter def get(client_id, code): """Gets the grant token with the configured cache system""" grant = Grant(cache, client_id=client_id, code=code) ret = cache.get(grant.key) if not ret: log.debug("Grant Token not found with key %s" % grant.key) return None log.debug("Grant Token found with key %s" % grant.key) for k, v in ret.items(): setattr(grant, k, v) return grant def bind_sqlalchemy(provider, session, user=None, client=None, token=None, grant=None, current_user=None): """Configures the given :class:`OAuth2Provider` instance with the required getters and setters for persistence with SQLAlchemy. An example of using all models:: oauth = OAuth2Provider(app) bind_sqlalchemy(oauth, session, user=User, client=Client, token=Token, grant=Grant, current_user=current_user) You can omit any model if you wish to register the functions yourself. It is also possible to override the functions by registering them afterwards:: oauth = OAuth2Provider(app) bind_sqlalchemy(oauth, session, user=User, client=Client, token=Token) @oauth.grantgetter def get_grant(client_id, code): pass @oauth.grantsetter def set_grant(client_id, code, request, *args, **kwargs): pass # register tokensetter with oauth but keeping the tokengetter # registered by `SQLAlchemyBinding` # You would only do this for the token and grant since user and client # only have getters @oauth.tokensetter def set_token(token, request, *args, **kwargs): pass Note that current_user is only required if you're using SQLAlchemy for grant caching. If you're using another caching system with GrantCacheBinding instead, omit current_user. :param provider: :class:`OAuth2Provider` instance :param session: A :class:`Session` object :param user: :class:`User` model :param client: :class:`Client` model :param token: :class:`Token` model :param grant: :class:`Grant` model :param current_user: function that returns a :class:`User` object """ if user: user_binding = UserBinding(user, session) provider.usergetter(user_binding.get) if client: client_binding = ClientBinding(client, session) provider.clientgetter(client_binding.get) if token: token_binding = TokenBinding(token, session, current_user) provider.tokengetter(token_binding.get) provider.tokensetter(token_binding.set) if grant: if not current_user: raise ValueError(('`current_user` is required' 'for Grant Binding')) grant_binding = GrantBinding(grant, session, current_user) provider.grantgetter(grant_binding.get) provider.grantsetter(grant_binding.set) class BaseBinding(object): """Base Binding :param model: SQLAlchemy Model class :param session: A :class:`Session` object """ def __init__(self, model, session): self.session = session self.model = model @property def query(self): """Determines which method of getting the query object for use""" if hasattr(self.model, 'query'): return self.model.query else: return self.session.query(self.model) class UserBinding(BaseBinding): """Object use by SQLAlchemyBinding to register the user getter""" def get(self, username, password, *args, **kwargs): """Returns the User object Returns None if the user isn't found or the passwords don't match :param username: username of the user :param password: password of the user """ user = self.query.filter_by(username=username).first() if user and user.check_password(password): return user return None class ClientBinding(BaseBinding): """Object use by SQLAlchemyBinding to register the client getter""" def get(self, client_id): """Returns a Client object with the given client ID :param client_id: ID if the client """ return self.query.filter_by(client_id=client_id).first() class TokenBinding(BaseBinding): """Object use by SQLAlchemyBinding to register the token getter and setter """ def __init__(self, model, session, current_user=None): self.current_user = current_user super(TokenBinding, self).__init__(model, session) def get(self, access_token=None, refresh_token=None): """returns a Token object with the given access token or refresh token :param access_token: User's access token :param refresh_token: User's refresh token """ if access_token: return self.query.filter_by(access_token=access_token).first() elif refresh_token: return self.query.filter_by(refresh_token=refresh_token).first() return None def set(self, token, request, *args, **kwargs): """Creates a Token object and removes all expired tokens that belong to the user :param token: token object :param request: OAuthlib request object """ if hasattr(request, 'user') and request.user: user = request.user elif self.current_user: # for implicit token user = self.current_user() client = request.client tokens = self.query.filter_by( client_id=client.client_id, user_id=user.id).all() if tokens: for tk in tokens: self.session.delete(tk) self.session.commit() expires_in = token.get('expires_in') expires = datetime.utcnow() + timedelta(seconds=expires_in) tok = self.model(**token) tok.expires = expires tok.client_id = client.client_id tok.user_id = user.id self.session.add(tok) self.session.commit() return tok class GrantBinding(BaseBinding): """Object use by SQLAlchemyBinding to register the grant getter and setter """ def __init__(self, model, session, current_user): self.current_user = current_user super(GrantBinding, self).__init__(model, session) def set(self, client_id, code, request, *args, **kwargs): """Creates Grant object with the given params :param client_id: ID of the client :param code: :param request: OAuthlib request object """ expires = datetime.utcnow() + timedelta(seconds=100) grant = self.model( client_id=request.client.client_id, code=code['code'], redirect_uri=request.redirect_uri, scope=' '.join(request.scopes), user=self.current_user(), expires=expires ) self.session.add(grant) self.session.commit() def get(self, client_id, code): """Get the Grant object with the given client ID and code :param client_id: ID of the client :param code: """ return self.query.filter_by(client_id=client_id, code=code).first() flask-oauthlib-0.9.5/flask_oauthlib/provider/000077500000000000000000000000001327671251000212575ustar00rootroot00000000000000flask-oauthlib-0.9.5/flask_oauthlib/provider/__init__.py000066400000000000000000000005101327671251000233640ustar00rootroot00000000000000# coding: utf-8 """ flask_oauthlib.provider ~~~~~~~~~~~~~~~~~~~~~~~ Implemnts OAuth1 and OAuth2 providers support for Flask. :copyright: (c) 2013 - 2014 by Hsiaoming Yang. """ # flake8: noqa from .oauth1 import OAuth1Provider, OAuth1RequestValidator from .oauth2 import OAuth2Provider, OAuth2RequestValidator flask-oauthlib-0.9.5/flask_oauthlib/provider/oauth1.py000066400000000000000000000750571327671251000230500ustar00rootroot00000000000000# coding: utf-8 """ flask_oauthlib.provider.oauth1 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Implemnts OAuth1 provider support for Flask. :copyright: (c) 2013 - 2014 by Hsiaoming Yang. """ import logging from functools import wraps from werkzeug import cached_property from flask import request, redirect, url_for from flask import make_response, abort from oauthlib.oauth1 import RequestValidator from oauthlib.oauth1 import WebApplicationServer as Server from oauthlib.oauth1 import SIGNATURE_HMAC, SIGNATURE_RSA from oauthlib.common import to_unicode, add_params_to_uri, urlencode from oauthlib.oauth1.rfc5849 import errors from ..utils import extract_params, create_response SIGNATURE_METHODS = (SIGNATURE_HMAC, SIGNATURE_RSA) __all__ = ('OAuth1Provider', 'OAuth1RequestValidator') log = logging.getLogger('flask_oauthlib') class OAuth1Provider(object): """Provide secure services using OAuth1. Like many other Flask extensions, there are two usage modes. One is binding the Flask app instance:: app = Flask(__name__) oauth = OAuth1Provider(app) The second possibility is to bind the Flask app later:: oauth = OAuth1Provider() def create_app(): app = Flask(__name__) oauth.init_app(app) return app And now you can protect the resource with realms:: @app.route('/api/user') @oauth.require_oauth('email', 'username') def user(): return jsonify(request.oauth.user) """ def __init__(self, app=None): self._before_request_funcs = [] self._after_request_funcs = [] if app: self.init_app(app) def init_app(self, app): """ This callback can be used to initialize an application for the oauth provider instance. """ self.app = app app.extensions = getattr(app, 'extensions', {}) app.extensions['oauthlib.provider.oauth1'] = self @cached_property def error_uri(self): """The error page URI. When something turns error, it will redirect to this error page. You can configure the error page URI with Flask config:: OAUTH1_PROVIDER_ERROR_URI = '/error' You can also define the error page by a named endpoint:: OAUTH1_PROVIDER_ERROR_ENDPOINT = 'oauth.error' """ error_uri = self.app.config.get('OAUTH1_PROVIDER_ERROR_URI') if error_uri: return error_uri error_endpoint = self.app.config.get('OAUTH1_PROVIDER_ERROR_ENDPOINT') if error_endpoint: return url_for(error_endpoint) return '/oauth/errors' @cached_property def server(self): """ All in one endpoints. This property is created automaticly if you have implemented all the getters and setters. """ if hasattr(self, '_validator'): return Server(self._validator) if hasattr(self, '_clientgetter') and \ hasattr(self, '_tokengetter') and \ hasattr(self, '_tokensetter') and \ hasattr(self, '_noncegetter') and \ hasattr(self, '_noncesetter') and \ hasattr(self, '_grantgetter') and \ hasattr(self, '_grantsetter') and \ hasattr(self, '_verifiergetter') and \ hasattr(self, '_verifiersetter'): validator = OAuth1RequestValidator( clientgetter=self._clientgetter, tokengetter=self._tokengetter, tokensetter=self._tokensetter, grantgetter=self._grantgetter, grantsetter=self._grantsetter, noncegetter=self._noncegetter, noncesetter=self._noncesetter, verifiergetter=self._verifiergetter, verifiersetter=self._verifiersetter, config=self.app.config, ) self._validator = validator server = Server(validator) if self.app.testing: # It will always be false, since the redirect_uri # didn't match when doing the testing server._check_signature = lambda *args, **kwargs: True return server raise RuntimeError( 'application not bound to required getters and setters' ) def before_request(self, f): """Register functions to be invoked before accessing the resource. The function accepts nothing as parameters, but you can get information from `Flask.request` object. It is usually useful for setting limitation on the client request:: @oauth.before_request def limit_client_request(): client_key = request.values.get('client_key') if not client_key: return client = Client.get(client_key) if over_limit(client): return abort(403) track_request(client) """ self._before_request_funcs.append(f) return f def after_request(self, f): """Register functions to be invoked after accessing the resource. The function accepts ``valid`` and ``request`` as parameters, and it should return a tuple of them:: @oauth.after_request def valid_after_request(valid, oauth): if oauth.user in black_list: return False, oauth return valid, oauth """ self._after_request_funcs.append(f) return f def clientgetter(self, f): """Register a function as the client getter. The function accepts one parameter `client_key`, and it returns a client object with at least these information: - client_key: A random string - client_secret: A random string - redirect_uris: A list of redirect uris - default_realms: Default scopes of the client The client may contain more information, which is suggested: - default_redirect_uri: One of the redirect uris Implement the client getter:: @oauth.clientgetter def get_client(client_key): client = get_client_model(client_key) # Client is an object return client """ self._clientgetter = f return f def tokengetter(self, f): """Register a function as the access token getter. The function accepts `client_key` and `token` parameters, and it returns an access token object contains: - client: Client associated with this token - user: User associated with this token - token: Access token - secret: Access token secret - realms: Realms with this access token Implement the token getter:: @oauth.tokengetter def get_access_token(client_key, token): return AccessToken.get(client_key=client_key, token=token) """ self._tokengetter = f return f def tokensetter(self, f): """Register a function as the access token setter. The setter accepts two parameters at least, one is token, the other is request:: @oauth.tokensetter def save_access_token(token, request): access_token = AccessToken( client=request.client, user=request.user, token=token['oauth_token'], secret=token['oauth_token_secret'], realms=token['oauth_authorized_realms'].split(' '), ) return access_token.save() The parameter token is a dict, that looks like:: { u'oauth_token': u'arandomstringoftoken', u'oauth_token_secret': u'arandomstringofsecret', u'oauth_authorized_realms': u'email address' } The `request` object would provide these information (at least):: - client: Client object associated with this token - user: User object associated with this token - request_token: Requst token for exchanging this access token """ self._tokensetter = f return f def grantgetter(self, f): """Register a function as the request token getter. The function accepts a `token` parameter, and it returns an request token object contains: - client: Client associated with this token - token: Access token - secret: Access token secret - realms: Realms with this access token - redirect_uri: A URI for redirecting Implement the token getter:: @oauth.tokengetter def get_request_token(token): return RequestToken.get(token=token) """ self._grantgetter = f return f def grantsetter(self, f): """Register a function as the request token setter. The setter accepts a token and request parameters:: @oauth.grantsetter def save_request_token(token, request): data = RequestToken( token=token['oauth_token'], secret=token['oauth_token_secret'], client=request.client, redirect_uri=oauth.redirect_uri, realms=request.realms, ) return data.save() """ self._grantsetter = f return f def noncegetter(self, f): """Register a function as the nonce and timestamp getter. The function accepts parameters: - client_key: The client/consure key - timestamp: The ``oauth_timestamp`` parameter - nonce: The ``oauth_nonce`` parameter - request_token: Request token string, if any - access_token: Access token string, if any A nonce and timestamp make each request unique. The implementation:: @oauth.noncegetter def get_nonce(client_key, timestamp, nonce, request_token, access_token): return Nonce.get("...") """ self._noncegetter = f return f def noncesetter(self, f): """Register a function as the nonce and timestamp setter. The parameters are the same with :meth:`noncegetter`:: @oauth.noncegetter def save_nonce(client_key, timestamp, nonce, request_token, access_token): data = Nonce("...") return data.save() The timestamp will be expired in 60s, it would be a better design if you put timestamp and nonce object in a cache. """ self._noncesetter = f return f def verifiergetter(self, f): """Register a function as the verifier getter. The return verifier object should at least contain a user object which is the current user. The implemented code looks like:: @oauth.verifiergetter def load_verifier(verifier, token): data = Verifier.get(verifier) if data.request_token == token: # check verifier for safety return data return data """ self._verifiergetter = f return f def verifiersetter(self, f): """Register a function as the verifier setter. A verifier is better together with request token, but it is not required. A verifier is used together with request token for exchanging access token, it has an expire time, in this case, it would be a better design if you put them in a cache. The implemented code looks like:: @oauth.verifiersetter def save_verifier(verifier, token, *args, **kwargs): data = Verifier( verifier=verifier['oauth_verifier'], request_token=token, user=get_current_user() ) return data.save() """ self._verifiersetter = f return f def authorize_handler(self, f): """Authorization handler decorator. This decorator will sort the parameters and headers out, and pre validate everything:: @app.route('/oauth/authorize', methods=['GET', 'POST']) @oauth.authorize_handler def authorize(*args, **kwargs): if request.method == 'GET': # render a page for user to confirm the authorization return render_template('oauthorize.html') confirm = request.form.get('confirm', 'no') return confirm == 'yes' """ @wraps(f) def decorated(*args, **kwargs): if request.method == 'POST': if not f(*args, **kwargs): uri = add_params_to_uri( self.error_uri, [('error', 'denied')] ) return redirect(uri) return self.confirm_authorization_request() server = self.server uri, http_method, body, headers = extract_params() try: realms, credentials = server.get_realms_and_credentials( uri, http_method=http_method, body=body, headers=headers ) kwargs['realms'] = realms kwargs.update(credentials) return f(*args, **kwargs) except errors.OAuth1Error as e: return redirect(e.in_uri(self.error_uri)) except errors.InvalidClientError as e: return redirect(e.in_uri(self.error_uri)) return decorated def confirm_authorization_request(self): """When consumer confirm the authrozation.""" server = self.server uri, http_method, body, headers = extract_params() try: realms, credentials = server.get_realms_and_credentials( uri, http_method=http_method, body=body, headers=headers ) ret = server.create_authorization_response( uri, http_method, body, headers, realms, credentials ) log.debug('Authorization successful.') return create_response(*ret) except errors.OAuth1Error as e: return redirect(e.in_uri(self.error_uri)) except errors.InvalidClientError as e: return redirect(e.in_uri(self.error_uri)) def request_token_handler(self, f): """Request token handler decorator. The decorated function should return an dictionary or None as the extra credentials for creating the token response. If you don't need to add any extra credentials, it could be as simple as:: @app.route('/oauth/request_token') @oauth.request_token_handler def request_token(): return {} """ @wraps(f) def decorated(*args, **kwargs): server = self.server uri, http_method, body, headers = extract_params() credentials = f(*args, **kwargs) try: ret = server.create_request_token_response( uri, http_method, body, headers, credentials) return create_response(*ret) except errors.OAuth1Error as e: return _error_response(e) return decorated def access_token_handler(self, f): """Access token handler decorator. The decorated function should return an dictionary or None as the extra credentials for creating the token response. If you don't need to add any extra credentials, it could be as simple as:: @app.route('/oauth/access_token') @oauth.access_token_handler def access_token(): return {} """ @wraps(f) def decorated(*args, **kwargs): server = self.server uri, http_method, body, headers = extract_params() credentials = f(*args, **kwargs) try: ret = server.create_access_token_response( uri, http_method, body, headers, credentials) return create_response(*ret) except errors.OAuth1Error as e: return _error_response(e) return decorated def require_oauth(self, *realms, **kwargs): """Protect resource with specified scopes.""" def wrapper(f): @wraps(f) def decorated(*args, **kwargs): for func in self._before_request_funcs: func() if hasattr(request, 'oauth') and request.oauth: return f(*args, **kwargs) server = self.server uri, http_method, body, headers = extract_params() try: valid, req = server.validate_protected_resource_request( uri, http_method, body, headers, realms ) except Exception as e: log.warn('Exception: %r', e) e.urlencoded = urlencode([('error', 'unknown')]) e.status_code = 400 return _error_response(e) for func in self._after_request_funcs: valid, req = func(valid, req) if not valid: return abort(401) # alias user for convenience req.user = req.access_token.user request.oauth = req return f(*args, **kwargs) return decorated return wrapper class OAuth1RequestValidator(RequestValidator): """Subclass of Request Validator. :param clientgetter: a function to get client object :param tokengetter: a function to get access token :param tokensetter: a function to save access token :param grantgetter: a function to get request token :param grantsetter: a function to save request token :param noncegetter: a function to get nonce and timestamp :param noncesetter: a function to save nonce and timestamp """ def __init__(self, clientgetter, tokengetter, tokensetter, grantgetter, grantsetter, noncegetter, noncesetter, verifiergetter, verifiersetter, config=None): self._clientgetter = clientgetter # access token getter and setter self._tokengetter = tokengetter self._tokensetter = tokensetter # request token getter and setter self._grantgetter = grantgetter self._grantsetter = grantsetter # nonce and timestamp self._noncegetter = noncegetter self._noncesetter = noncesetter # verifier getter and setter self._verifiergetter = verifiergetter self._verifiersetter = verifiersetter self._config = config or {} @property def allowed_signature_methods(self): """Allowed signature methods. Default value: SIGNATURE_HMAC and SIGNATURE_RSA. You can customize with Flask Config: - OAUTH1_PROVIDER_SIGNATURE_METHODS """ return self._config.get( 'OAUTH1_PROVIDER_SIGNATURE_METHODS', SIGNATURE_METHODS, ) @property def client_key_length(self): return self._config.get( 'OAUTH1_PROVIDER_KEY_LENGTH', (20, 30) ) @property def request_token_length(self): return self._config.get( 'OAUTH1_PROVIDER_KEY_LENGTH', (20, 30) ) @property def access_token_length(self): return self._config.get( 'OAUTH1_PROVIDER_KEY_LENGTH', (20, 30) ) @property def nonce_length(self): return self._config.get( 'OAUTH1_PROVIDER_KEY_LENGTH', (20, 30) ) @property def verifier_length(self): return self._config.get( 'OAUTH1_PROVIDER_KEY_LENGTH', (20, 30) ) @property def realms(self): return self._config.get('OAUTH1_PROVIDER_REALMS', []) @property def enforce_ssl(self): """Enforce SSL request. Default is True. You can customize with: - OAUTH1_PROVIDER_ENFORCE_SSL """ return self._config.get('OAUTH1_PROVIDER_ENFORCE_SSL', True) @property def dummy_client(self): return to_unicode('dummy_client', 'utf-8') @property def dummy_request_token(self): return to_unicode('dummy_request_token', 'utf-8') @property def dummy_access_token(self): return to_unicode('dummy_access_token', 'utf-8') def get_client_secret(self, client_key, request): """Get client secret. The client object must has ``client_secret`` attribute. """ log.debug('Get client secret of %r', client_key) if not request.client: request.client = self._clientgetter(client_key=client_key) if request.client: return request.client.client_secret return None def get_request_token_secret(self, client_key, token, request): """Get request token secret. The request token object should a ``secret`` attribute. """ log.debug('Get request token secret of %r for %r', token, client_key) tok = request.request_token or self._grantgetter(token=token) if tok and tok.client_key == client_key: request.request_token = tok return tok.secret return None def get_access_token_secret(self, client_key, token, request): """Get access token secret. The access token object should a ``secret`` attribute. """ log.debug('Get access token secret of %r for %r', token, client_key) tok = request.access_token or self._tokengetter( client_key=client_key, token=token, ) if tok: request.access_token = tok return tok.secret return None def get_default_realms(self, client_key, request): """Default realms of the client.""" log.debug('Get realms for %r', client_key) if not request.client: request.client = self._clientgetter(client_key=client_key) client = request.client if hasattr(client, 'default_realms'): return client.default_realms return [] def get_realms(self, token, request): """Realms for this request token.""" log.debug('Get realms of %r', token) tok = request.request_token or self._grantgetter(token=token) if not tok: return [] request.request_token = tok if hasattr(tok, 'realms'): return tok.realms or [] return [] def get_redirect_uri(self, token, request): """Redirect uri for this request token.""" log.debug('Get redirect uri of %r', token) tok = request.request_token or self._grantgetter(token=token) return tok.redirect_uri def get_rsa_key(self, client_key, request): """Retrieves a previously stored client provided RSA key.""" if not request.client: request.client = self._clientgetter(client_key=client_key) if hasattr(request.client, 'rsa_key'): return request.client.rsa_key return None def invalidate_request_token(self, client_key, request_token, request): """Invalidates a used request token.""" # TODO def validate_client_key(self, client_key, request): """Validates that supplied client key.""" log.debug('Validate client key for %r', client_key) if not request.client: request.client = self._clientgetter(client_key=client_key) if request.client: return True return False def validate_request_token(self, client_key, token, request): """Validates request token is available for client.""" log.debug('Validate request token %r for %r', token, client_key) tok = request.request_token or self._grantgetter(token=token) if tok and tok.client_key == client_key: request.request_token = tok return True return False def validate_access_token(self, client_key, token, request): """Validates access token is available for client.""" log.debug('Validate access token %r for %r', token, client_key) tok = request.access_token or self._tokengetter( client_key=client_key, token=token, ) if tok: request.access_token = tok return True return False def validate_timestamp_and_nonce(self, client_key, timestamp, nonce, request, request_token=None, access_token=None): """Validate the timestamp and nonce is used or not.""" log.debug('Validate timestamp and nonce %r', client_key) nonce_exists = self._noncegetter( client_key=client_key, timestamp=timestamp, nonce=nonce, request_token=request_token, access_token=access_token ) if nonce_exists: return False self._noncesetter( client_key=client_key, timestamp=timestamp, nonce=nonce, request_token=request_token, access_token=access_token ) return True def validate_redirect_uri(self, client_key, redirect_uri, request): """Validate if the redirect_uri is allowed by the client.""" log.debug('Validate redirect_uri %r for %r', redirect_uri, client_key) if not request.client: request.client = self._clientgetter(client_key=client_key) if not request.client: return False if not request.client.redirect_uris and redirect_uri is None: return True request.redirect_uri = redirect_uri return redirect_uri in request.client.redirect_uris def validate_requested_realms(self, client_key, realms, request): log.debug('Validate requested realms %r for %r', realms, client_key) if not request.client: request.client = self._clientgetter(client_key=client_key) client = request.client if not client: return False if hasattr(client, 'validate_realms'): return client.validate_realms(realms) if set(client.default_realms).issuperset(set(realms)): return True return True def validate_realms(self, client_key, token, request, uri=None, realms=None): """Check if the token has permission on those realms.""" log.debug('Validate realms %r for %r', realms, client_key) if request.access_token: tok = request.access_token else: tok = self._tokengetter(client_key=client_key, token=token) request.access_token = tok if not tok: return False return set(tok.realms).issuperset(set(realms)) def validate_verifier(self, client_key, token, verifier, request): """Validate verifier exists.""" log.debug('Validate verifier %r for %r', verifier, client_key) data = self._verifiergetter(verifier=verifier, token=token) if not data: return False if not hasattr(data, 'user'): log.debug('Verifier should has user attribute') return False request.user = data.user if hasattr(data, 'client_key'): return data.client_key == client_key return True def verify_request_token(self, token, request): """Verify if the request token is existed.""" log.debug('Verify request token %r', token) tok = request.request_token or self._grantgetter(token=token) if tok: request.request_token = tok return True return False def verify_realms(self, token, realms, request): """Verify if the realms match the requested realms.""" log.debug('Verify realms %r', realms) tok = request.request_token or self._grantgetter(token=token) if not tok: return False request.request_token = tok if not hasattr(tok, 'realms'): # realms not enabled return True return set(tok.realms) == set(realms) def save_access_token(self, token, request): """Save access token to database. A tokensetter is required, which accepts a token and request parameters:: def tokensetter(token, request): access_token = Token( client=request.client, user=request.user, token=token['oauth_token'], secret=token['oauth_token_secret'], realms=token['oauth_authorized_realms'], ) return access_token.save() """ log.debug('Save access token %r', token) self._tokensetter(token, request) def save_request_token(self, token, request): """Save request token to database. A grantsetter is required, which accepts a token and request parameters:: def grantsetter(token, request): grant = Grant( token=token['oauth_token'], secret=token['oauth_token_secret'], client=request.client, redirect_uri=oauth.redirect_uri, realms=request.realms, ) return grant.save() """ log.debug('Save request token %r', token) self._grantsetter(token, request) def save_verifier(self, token, verifier, request): """Save verifier to database. A verifiersetter is required. It would be better to combine request token and verifier together:: def verifiersetter(token, verifier, request): tok = Grant.query.filter_by(token=token).first() tok.verifier = verifier['oauth_verifier'] tok.user = get_current_user() return tok.save() .. admonition:: Note: A user is required on verifier, remember to attach current user to verifier. """ log.debug('Save verifier %r for %r', verifier, token) self._verifiersetter( token=token, verifier=verifier, request=request ) def _error_response(e): res = make_response(e.urlencoded, e.status_code) res.headers['Content-Type'] = 'application/x-www-form-urlencoded' return res flask-oauthlib-0.9.5/flask_oauthlib/provider/oauth2.py000066400000000000000000001110361327671251000230350ustar00rootroot00000000000000# coding: utf-8 """ flask_oauthlib.provider.oauth2 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Implemnts OAuth2 provider support for Flask. :copyright: (c) 2013 - 2014 by Hsiaoming Yang. """ import os import logging import datetime from functools import wraps from flask import request, url_for from flask import redirect, abort from werkzeug import cached_property from werkzeug.utils import import_string from oauthlib import oauth2 from oauthlib.oauth2 import RequestValidator, Server from oauthlib.common import to_unicode, add_params_to_uri from ..utils import extract_params, decode_base64, create_response __all__ = ('OAuth2Provider', 'OAuth2RequestValidator') log = logging.getLogger('flask_oauthlib') class OAuth2Provider(object): """Provide secure services using OAuth2. The server should provide an authorize handler and a token hander, But before the handlers are implemented, the server should provide some getters for the validation. Like many other Flask extensions, there are two usage modes. One is binding the Flask app instance:: app = Flask(__name__) oauth = OAuth2Provider(app) The second possibility is to bind the Flask app later:: oauth = OAuth2Provider() def create_app(): app = Flask(__name__) oauth.init_app(app) return app Configure :meth:`tokengetter` and :meth:`tokensetter` to get and set tokens. Configure :meth:`grantgetter` and :meth:`grantsetter` to get and set grant tokens. Configure :meth:`clientgetter` to get the client. Configure :meth:`usergetter` if you need password credential authorization. With everything ready, implement the authorization workflow: * :meth:`authorize_handler` for consumer to confirm the grant * :meth:`token_handler` for client to exchange access token And now you can protect the resource with scopes:: @app.route('/api/user') @oauth.require_oauth('email', 'username') def user(): return jsonify(request.oauth.user) """ def __init__(self, app=None): self._before_request_funcs = [] self._after_request_funcs = [] self._invalid_response = None if app: self.init_app(app) def init_app(self, app): """ This callback can be used to initialize an application for the oauth provider instance. """ self.app = app app.extensions = getattr(app, 'extensions', {}) app.extensions['oauthlib.provider.oauth2'] = self @cached_property def error_uri(self): """The error page URI. When something turns error, it will redirect to this error page. You can configure the error page URI with Flask config:: OAUTH2_PROVIDER_ERROR_URI = '/error' You can also define the error page by a named endpoint:: OAUTH2_PROVIDER_ERROR_ENDPOINT = 'oauth.error' """ error_uri = self.app.config.get('OAUTH2_PROVIDER_ERROR_URI') if error_uri: return error_uri error_endpoint = self.app.config.get('OAUTH2_PROVIDER_ERROR_ENDPOINT') if error_endpoint: return url_for(error_endpoint) return '/oauth/errors' @cached_property def server(self): """ All in one endpoints. This property is created automaticly if you have implemented all the getters and setters. However, if you are not satisfied with the getter and setter, you can create a validator with :class:`OAuth2RequestValidator`:: class MyValidator(OAuth2RequestValidator): def validate_client_id(self, client_id): # do something return True And assign the validator for the provider:: oauth._validator = MyValidator() """ expires_in = self.app.config.get('OAUTH2_PROVIDER_TOKEN_EXPIRES_IN') token_generator = self.app.config.get( 'OAUTH2_PROVIDER_TOKEN_GENERATOR', None ) if token_generator and not callable(token_generator): token_generator = import_string(token_generator) refresh_token_generator = self.app.config.get( 'OAUTH2_PROVIDER_REFRESH_TOKEN_GENERATOR', None ) if refresh_token_generator and not callable(refresh_token_generator): refresh_token_generator = import_string(refresh_token_generator) if hasattr(self, '_validator'): return Server( self._validator, token_expires_in=expires_in, token_generator=token_generator, refresh_token_generator=refresh_token_generator, ) if hasattr(self, '_clientgetter') and \ hasattr(self, '_tokengetter') and \ hasattr(self, '_tokensetter') and \ hasattr(self, '_grantgetter') and \ hasattr(self, '_grantsetter'): usergetter = None if hasattr(self, '_usergetter'): usergetter = self._usergetter validator = OAuth2RequestValidator( clientgetter=self._clientgetter, tokengetter=self._tokengetter, grantgetter=self._grantgetter, usergetter=usergetter, tokensetter=self._tokensetter, grantsetter=self._grantsetter, ) self._validator = validator return Server( validator, token_expires_in=expires_in, token_generator=token_generator, refresh_token_generator=refresh_token_generator, ) raise RuntimeError('application not bound to required getters') def before_request(self, f): """Register functions to be invoked before accessing the resource. The function accepts nothing as parameters, but you can get information from `Flask.request` object. It is usually useful for setting limitation on the client request:: @oauth.before_request def limit_client_request(): client_id = request.values.get('client_id') if not client_id: return client = Client.get(client_id) if over_limit(client): return abort(403) track_request(client) """ self._before_request_funcs.append(f) return f def after_request(self, f): """Register functions to be invoked after accessing the resource. The function accepts ``valid`` and ``request`` as parameters, and it should return a tuple of them:: @oauth.after_request def valid_after_request(valid, oauth): if oauth.user in black_list: return False, oauth return valid, oauth """ self._after_request_funcs.append(f) return f def invalid_response(self, f): """Register a function for responsing with invalid request. When an invalid request proceeds to :meth:`require_oauth`, we can handle the request with the registered function. The function accepts one parameter, which is an oauthlib Request object:: @oauth.invalid_response def invalid_require_oauth(req): return jsonify(message=req.error_message), 401 If no function is registered, it will return with ``abort(401)``. """ self._invalid_response = f return f def clientgetter(self, f): """Register a function as the client getter. The function accepts one parameter `client_id`, and it returns a client object with at least these information: - client_id: A random string - client_secret: A random string - is_confidential: A bool represents if it is confidential - redirect_uris: A list of redirect uris - default_redirect_uri: One of the redirect uris - default_scopes: Default scopes of the client The client may contain more information, which is suggested: - allowed_grant_types: A list of grant types - allowed_response_types: A list of response types - validate_scopes: A function to validate scopes Implement the client getter:: @oauth.clientgetter def get_client(client_id): client = get_client_model(client_id) # Client is an object return client """ self._clientgetter = f return f def usergetter(self, f): """Register a function as the user getter. This decorator is only required for **password credential** authorization:: @oauth.usergetter def get_user(username, password, client, request, *args, **kwargs): # client: current request client if not client.has_password_credential_permission: return None user = User.get_user_by_username(username) if not user.validate_password(password): return None # parameter `request` is an OAuthlib Request object. # maybe you will need it somewhere return user """ self._usergetter = f return f def tokengetter(self, f): """Register a function as the token getter. The function accepts an `access_token` or `refresh_token` parameters, and it returns a token object with at least these information: - access_token: A string token - refresh_token: A string token - client_id: ID of the client - scopes: A list of scopes - expires: A `datetime.datetime` object - user: The user object The implementation of tokengetter should accepts two parameters, one is access_token the other is refresh_token:: @oauth.tokengetter def bearer_token(access_token=None, refresh_token=None): if access_token: return get_token(access_token=access_token) if refresh_token: return get_token(refresh_token=refresh_token) return None """ self._tokengetter = f return f def tokensetter(self, f): """Register a function to save the bearer token. The setter accepts two parameters at least, one is token, the other is request:: @oauth.tokensetter def set_token(token, request, *args, **kwargs): save_token(token, request.client, request.user) The parameter token is a dict, that looks like:: { u'access_token': u'6JwgO77PApxsFCU8Quz0pnL9s23016', u'token_type': u'Bearer', u'expires_in': 3600, u'scope': u'email address' } The request is an object, that contains an user object and a client object. """ self._tokensetter = f return f def grantgetter(self, f): """Register a function as the grant getter. The function accepts `client_id`, `code` and more:: @oauth.grantgetter def grant(client_id, code): return get_grant(client_id, code) It returns a grant object with at least these information: - delete: A function to delete itself """ self._grantgetter = f return f def grantsetter(self, f): """Register a function to save the grant code. The function accepts `client_id`, `code`, `request` and more:: @oauth.grantsetter def set_grant(client_id, code, request, *args, **kwargs): save_grant(client_id, code, request.user, request.scopes) """ self._grantsetter = f return f def authorize_handler(self, f): """Authorization handler decorator. This decorator will sort the parameters and headers out, and pre validate everything:: @app.route('/oauth/authorize', methods=['GET', 'POST']) @oauth.authorize_handler def authorize(*args, **kwargs): if request.method == 'GET': # render a page for user to confirm the authorization return render_template('oauthorize.html') confirm = request.form.get('confirm', 'no') return confirm == 'yes' """ @wraps(f) def decorated(*args, **kwargs): # raise if server not implemented server = self.server uri, http_method, body, headers = extract_params() if request.method in ('GET', 'HEAD'): redirect_uri = request.args.get('redirect_uri', self.error_uri) log.debug('Found redirect_uri %s.', redirect_uri) try: ret = server.validate_authorization_request( uri, http_method, body, headers ) scopes, credentials = ret kwargs['scopes'] = scopes kwargs.update(credentials) except oauth2.FatalClientError as e: log.debug('Fatal client error %r', e, exc_info=True) return redirect(e.in_uri(self.error_uri)) except oauth2.OAuth2Error as e: log.debug('OAuth2Error: %r', e, exc_info=True) return redirect(e.in_uri(redirect_uri)) except Exception as e: log.exception(e) return redirect(add_params_to_uri( self.error_uri, {'error': str(e)} )) else: redirect_uri = request.values.get( 'redirect_uri', self.error_uri ) try: rv = f(*args, **kwargs) except oauth2.FatalClientError as e: log.debug('Fatal client error %r', e, exc_info=True) return redirect(e.in_uri(self.error_uri)) except oauth2.OAuth2Error as e: log.debug('OAuth2Error: %r', e, exc_info=True) return redirect(e.in_uri(redirect_uri)) if not isinstance(rv, bool): # if is a response or redirect return rv if not rv: # denied by user e = oauth2.AccessDeniedError() return redirect(e.in_uri(redirect_uri)) return self.confirm_authorization_request() return decorated def confirm_authorization_request(self): """When consumer confirm the authorization.""" server = self.server scope = request.values.get('scope') or '' scopes = scope.split() credentials = dict( client_id=request.values.get('client_id'), redirect_uri=request.values.get('redirect_uri', None), response_type=request.values.get('response_type', None), state=request.values.get('state', None) ) log.debug('Fetched credentials from request %r.', credentials) redirect_uri = credentials.get('redirect_uri') log.debug('Found redirect_uri %s.', redirect_uri) uri, http_method, body, headers = extract_params() try: ret = server.create_authorization_response( uri, http_method, body, headers, scopes, credentials) log.debug('Authorization successful.') return create_response(*ret) except oauth2.FatalClientError as e: log.debug('Fatal client error %r', e, exc_info=True) return redirect(e.in_uri(self.error_uri)) except oauth2.OAuth2Error as e: log.debug('OAuth2Error: %r', e, exc_info=True) return redirect(e.in_uri(redirect_uri or self.error_uri)) except Exception as e: log.exception(e) return redirect(add_params_to_uri( self.error_uri, {'error': str(e)} )) def verify_request(self, scopes): """Verify current request, get the oauth data. If you can't use the ``require_oauth`` decorator, you can fetch the data in your request body:: def your_handler(): valid, req = oauth.verify_request(['email']) if valid: return jsonify(user=req.user) return jsonify(status='error') """ uri, http_method, body, headers = extract_params() return self.server.verify_request( uri, http_method, body, headers, scopes ) def token_handler(self, f): """Access/refresh token handler decorator. The decorated function should return an dictionary or None as the extra credentials for creating the token response. You can control the access method with standard flask route mechanism. If you only allow the `POST` method:: @app.route('/oauth/token', methods=['POST']) @oauth.token_handler def access_token(): return None """ @wraps(f) def decorated(*args, **kwargs): server = self.server uri, http_method, body, headers = extract_params() credentials = f(*args, **kwargs) or {} log.debug('Fetched extra credentials, %r.', credentials) ret = server.create_token_response( uri, http_method, body, headers, credentials ) return create_response(*ret) return decorated def revoke_handler(self, f): """Access/refresh token revoke decorator. Any return value by the decorated function will get discarded as defined in [`RFC7009`_]. You can control the access method with the standard flask routing mechanism, as per [`RFC7009`_] it is recommended to only allow the `POST` method:: @app.route('/oauth/revoke', methods=['POST']) @oauth.revoke_handler def revoke_token(): pass .. _`RFC7009`: http://tools.ietf.org/html/rfc7009 """ @wraps(f) def decorated(*args, **kwargs): server = self.server token = request.values.get('token') request.token_type_hint = request.values.get('token_type_hint') if token: request.token = token uri, http_method, body, headers = extract_params() ret = server.create_revocation_response( uri, headers=headers, body=body, http_method=http_method) return create_response(*ret) return decorated def require_oauth(self, *scopes): """Protect resource with specified scopes.""" def wrapper(f): @wraps(f) def decorated(*args, **kwargs): for func in self._before_request_funcs: func() if hasattr(request, 'oauth') and request.oauth: return f(*args, **kwargs) valid, req = self.verify_request(scopes) for func in self._after_request_funcs: valid, req = func(valid, req) if not valid: if self._invalid_response: return self._invalid_response(req) return abort(401) request.oauth = req return f(*args, **kwargs) return decorated return wrapper class OAuth2RequestValidator(RequestValidator): """Subclass of Request Validator. :param clientgetter: a function to get client object :param tokengetter: a function to get bearer token :param tokensetter: a function to save bearer token :param grantgetter: a function to get grant token :param grantsetter: a function to save grant token """ def __init__(self, clientgetter, tokengetter, grantgetter, usergetter=None, tokensetter=None, grantsetter=None): self._clientgetter = clientgetter self._tokengetter = tokengetter self._usergetter = usergetter self._tokensetter = tokensetter self._grantgetter = grantgetter self._grantsetter = grantsetter def _get_client_creds_from_request(self, request): """Return client credentials based on the current request. According to the rfc6749, client MAY use the HTTP Basic authentication scheme as defined in [RFC2617] to authenticate with the authorization server. The client identifier is encoded using the "application/x-www-form-urlencoded" encoding algorithm per Appendix B, and the encoded value is used as the username; the client password is encoded using the same algorithm and used as the password. The authorization server MUST support the HTTP Basic authentication scheme for authenticating clients that were issued a client password. See `Section 2.3.1`_. .. _`Section 2.3.1`: https://tools.ietf.org/html/rfc6749#section-2.3.1 """ if request.client_id is not None: return request.client_id, request.client_secret auth = request.headers.get('Authorization') # If Werkzeug successfully parsed the Authorization header, # `extract_params` helper will replace the header with a parsed dict, # otherwise, there is nothing useful in the header and we just skip it. if isinstance(auth, dict): return auth['username'], auth['password'] return None, None def client_authentication_required(self, request, *args, **kwargs): """Determine if client authentication is required for current request. According to the rfc6749, client authentication is required in the following cases: Resource Owner Password Credentials Grant: see `Section 4.3.2`_. Authorization Code Grant: see `Section 4.1.3`_. Refresh Token Grant: see `Section 6`_. .. _`Section 4.3.2`: http://tools.ietf.org/html/rfc6749#section-4.3.2 .. _`Section 4.1.3`: http://tools.ietf.org/html/rfc6749#section-4.1.3 .. _`Section 6`: http://tools.ietf.org/html/rfc6749#section-6 """ def is_confidential(client): if hasattr(client, 'is_confidential'): return client.is_confidential client_type = getattr(client, 'client_type', None) if client_type: return client_type == 'confidential' return True grant_types = ('password', 'authorization_code', 'refresh_token') client_id, _ = self._get_client_creds_from_request(request) if client_id and request.grant_type in grant_types: client = self._clientgetter(client_id) if client: return is_confidential(client) return False def authenticate_client(self, request, *args, **kwargs): """Authenticate itself in other means. Other means means is described in `Section 3.2.1`_. .. _`Section 3.2.1`: http://tools.ietf.org/html/rfc6749#section-3.2.1 """ client_id, client_secret = self._get_client_creds_from_request(request) log.debug('Authenticate client %r', client_id) client = self._clientgetter(client_id) if not client: log.debug('Authenticate client failed, client not found.') return False request.client = client # http://tools.ietf.org/html/rfc6749#section-2 # The client MAY omit the parameter if the client secret is an empty string. if hasattr(client, 'client_secret') and client.client_secret != client_secret: log.debug('Authenticate client failed, secret not match.') return False log.debug('Authenticate client success.') return True def authenticate_client_id(self, client_id, request, *args, **kwargs): """Authenticate a non-confidential client. :param client_id: Client ID of the non-confidential client :param request: The Request object passed by oauthlib """ if client_id is None: client_id, _ = self._get_client_creds_from_request(request) log.debug('Authenticate client %r.', client_id) client = request.client or self._clientgetter(client_id) if not client: log.debug('Authenticate failed, client not found.') return False # attach client on request for convenience request.client = client return True def confirm_redirect_uri(self, client_id, code, redirect_uri, client, *args, **kwargs): """Ensure client is authorized to redirect to the redirect_uri. This method is used in the authorization code grant flow. It will compare redirect_uri and the one in grant token strictly, you can add a `validate_redirect_uri` function on grant for a customized validation. """ client = client or self._clientgetter(client_id) log.debug('Confirm redirect uri for client %r and code %r.', client.client_id, code) grant = self._grantgetter(client_id=client.client_id, code=code) if not grant: log.debug('Grant not found.') return False if hasattr(grant, 'validate_redirect_uri'): return grant.validate_redirect_uri(redirect_uri) log.debug('Compare redirect uri for grant %r and %r.', grant.redirect_uri, redirect_uri) testing = 'OAUTHLIB_INSECURE_TRANSPORT' in os.environ if testing and redirect_uri is None: # For testing return True return grant.redirect_uri == redirect_uri def get_original_scopes(self, refresh_token, request, *args, **kwargs): """Get the list of scopes associated with the refresh token. This method is used in the refresh token grant flow. We return the scope of the token to be refreshed so it can be applied to the new access token. """ log.debug('Obtaining scope of refreshed token.') tok = self._tokengetter(refresh_token=refresh_token) return tok.scopes def confirm_scopes(self, refresh_token, scopes, request, *args, **kwargs): """Ensures the requested scope matches the scope originally granted by the resource owner. If the scope is omitted it is treated as equal to the scope originally granted by the resource owner. DEPRECATION NOTE: This method will cease to be used in oauthlib>0.4.2, future versions of ``oauthlib`` use the validator method ``get_original_scopes`` to determine the scope of the refreshed token. """ if not scopes: log.debug('Scope omitted for refresh token %r', refresh_token) return True log.debug('Confirm scopes %r for refresh token %r', scopes, refresh_token) tok = self._tokengetter(refresh_token=refresh_token) return set(tok.scopes) == set(scopes) def get_default_redirect_uri(self, client_id, request, *args, **kwargs): """Default redirect_uri for the given client.""" request.client = request.client or self._clientgetter(client_id) redirect_uri = request.client.default_redirect_uri log.debug('Found default redirect uri %r', redirect_uri) return redirect_uri def get_default_scopes(self, client_id, request, *args, **kwargs): """Default scopes for the given client.""" request.client = request.client or self._clientgetter(client_id) scopes = request.client.default_scopes log.debug('Found default scopes %r', scopes) return scopes def invalidate_authorization_code(self, client_id, code, request, *args, **kwargs): """Invalidate an authorization code after use. We keep the temporary code in a grant, which has a `delete` function to destroy itself. """ log.debug('Destroy grant token for client %r, %r', client_id, code) grant = self._grantgetter(client_id=client_id, code=code) if grant: grant.delete() def save_authorization_code(self, client_id, code, request, *args, **kwargs): """Persist the authorization code.""" log.debug( 'Persist authorization code %r for client %r', code, client_id ) request.client = request.client or self._clientgetter(client_id) self._grantsetter(client_id, code, request, *args, **kwargs) return request.client.default_redirect_uri def save_bearer_token(self, token, request, *args, **kwargs): """Persist the Bearer token.""" log.debug('Save bearer token %r', token) self._tokensetter(token, request, *args, **kwargs) return request.client.default_redirect_uri def validate_bearer_token(self, token, scopes, request): """Validate access token. :param token: A string of random characters :param scopes: A list of scopes :param request: The Request object passed by oauthlib The validation validates: 1) if the token is available 2) if the token has expired 3) if the scopes are available """ log.debug('Validate bearer token %r', token) tok = self._tokengetter(access_token=token) if not tok: msg = 'Bearer token not found.' request.error_message = msg log.debug(msg) return False # validate expires if tok.expires is not None and \ datetime.datetime.utcnow() > tok.expires: msg = 'Bearer token is expired.' request.error_message = msg log.debug(msg) return False # validate scopes if scopes and not set(tok.scopes) & set(scopes): msg = 'Bearer token scope not valid.' request.error_message = msg log.debug(msg) return False request.access_token = tok request.user = tok.user request.scopes = scopes if hasattr(tok, 'client'): request.client = tok.client elif hasattr(tok, 'client_id'): request.client = self._clientgetter(tok.client_id) return True def validate_client_id(self, client_id, request, *args, **kwargs): """Ensure client_id belong to a valid and active client.""" log.debug('Validate client %r', client_id) client = request.client or self._clientgetter(client_id) if client: # attach client to request object request.client = client return True return False def validate_code(self, client_id, code, client, request, *args, **kwargs): """Ensure the grant code is valid.""" client = client or self._clientgetter(client_id) log.debug( 'Validate code for client %r and code %r', client.client_id, code ) grant = self._grantgetter(client_id=client.client_id, code=code) if not grant: log.debug('Grant not found.') return False if hasattr(grant, 'expires') and \ datetime.datetime.utcnow() > grant.expires: log.debug('Grant is expired.') return False request.state = kwargs.get('state') request.user = grant.user request.scopes = grant.scopes return True def validate_grant_type(self, client_id, grant_type, client, request, *args, **kwargs): """Ensure the client is authorized to use the grant type requested. It will allow any of the four grant types (`authorization_code`, `password`, `client_credentials`, `refresh_token`) by default. Implemented `allowed_grant_types` for client object to authorize the request. It is suggested that `allowed_grant_types` should contain at least `authorization_code` and `refresh_token`. """ if self._usergetter is None and grant_type == 'password': log.debug('Password credential authorization is disabled.') return False default_grant_types = ( 'authorization_code', 'password', 'client_credentials', 'refresh_token', ) # Grant type is allowed if it is part of the 'allowed_grant_types' # of the selected client or if it is one of the default grant types if hasattr(client, 'allowed_grant_types'): if grant_type not in client.allowed_grant_types: return False else: if grant_type not in default_grant_types: return False if grant_type == 'client_credentials': if not hasattr(client, 'user'): log.debug('Client should have a user property') return False request.user = client.user return True def validate_redirect_uri(self, client_id, redirect_uri, request, *args, **kwargs): """Ensure client is authorized to redirect to the redirect_uri. This method is used in the authorization code grant flow and also in implicit grant flow. It will detect if redirect_uri in client's redirect_uris strictly, you can add a `validate_redirect_uri` function on grant for a customized validation. """ request.client = request.client or self._clientgetter(client_id) client = request.client if hasattr(client, 'validate_redirect_uri'): return client.validate_redirect_uri(redirect_uri) return redirect_uri in client.redirect_uris def validate_refresh_token(self, refresh_token, client, request, *args, **kwargs): """Ensure the token is valid and belongs to the client This method is used by the authorization code grant indirectly by issuing refresh tokens, resource owner password credentials grant (also indirectly) and the refresh token grant. """ token = self._tokengetter(refresh_token=refresh_token) if token and token.client_id == client.client_id: # Make sure the request object contains user and client_id request.client_id = token.client_id request.user = token.user return True return False def validate_response_type(self, client_id, response_type, client, request, *args, **kwargs): """Ensure client is authorized to use the response type requested. It will allow any of the two (`code`, `token`) response types by default. Implemented `allowed_response_types` for client object to authorize the request. """ if response_type not in ('code', 'token'): return False if hasattr(client, 'allowed_response_types'): return response_type in client.allowed_response_types return True def validate_scopes(self, client_id, scopes, client, request, *args, **kwargs): """Ensure the client is authorized access to requested scopes.""" if hasattr(client, 'validate_scopes'): return client.validate_scopes(scopes) return set(client.default_scopes).issuperset(set(scopes)) def validate_user(self, username, password, client, request, *args, **kwargs): """Ensure the username and password is valid. Attach user object on request for later using. """ log.debug('Validating username %r and its password', username) if self._usergetter is not None: user = self._usergetter( username, password, client, request, *args, **kwargs ) if user: request.user = user return True return False log.debug('Password credential authorization is disabled.') return False def revoke_token(self, token, token_type_hint, request, *args, **kwargs): """Revoke an access or refresh token. """ if token_type_hint: tok = self._tokengetter(**{token_type_hint: token}) else: tok = self._tokengetter(access_token=token) if not tok: tok = self._tokengetter(refresh_token=token) if tok: request.client_id = tok.client_id request.user = tok.user tok.delete() return True msg = 'Invalid token supplied.' log.debug(msg) request.error_message = msg return False flask-oauthlib-0.9.5/flask_oauthlib/utils.py000066400000000000000000000033701327671251000211420ustar00rootroot00000000000000# coding: utf-8 import base64 from flask import request, Response from oauthlib.common import to_unicode, bytes_type def _get_uri_from_request(request): """ The uri returned from request.uri is not properly urlencoded (sometimes it's partially urldecoded) This is a weird hack to get werkzeug to return the proper urlencoded string uri """ uri = request.base_url if request.query_string: uri += '?' + request.query_string.decode('utf-8') return uri def extract_params(): """Extract request params.""" uri = _get_uri_from_request(request) http_method = request.method headers = dict(request.headers) if 'wsgi.input' in headers: del headers['wsgi.input'] if 'wsgi.errors' in headers: del headers['wsgi.errors'] # Werkzeug, and subsequently Flask provide a safe Authorization header # parsing, so we just replace the Authorization header with the extraced # info if it was successfully parsed. if request.authorization: headers['Authorization'] = request.authorization body = request.form.to_dict() return uri, http_method, body, headers def to_bytes(text, encoding='utf-8'): """Make sure text is bytes type.""" if not text: return text if not isinstance(text, bytes_type): text = text.encode(encoding) return text def decode_base64(text, encoding='utf-8'): """Decode base64 string.""" text = to_bytes(text, encoding) return to_unicode(base64.b64decode(text), encoding) def create_response(headers, body, status): """Create response class for Flask.""" response = Response(body or '') for k, v in headers.items(): response.headers[str(k)] = v response.status_code = status return response flask-oauthlib-0.9.5/requirements.txt000066400000000000000000000001311327671251000177150ustar00rootroot00000000000000Flask==0.12.2 mock==2.0.0 oauthlib==2.0.6 requests-oauthlib==0.8.0 Flask-SQLAlchemy==2.1 flask-oauthlib-0.9.5/setup.py000066400000000000000000000040011327671251000161430ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- try: import multiprocessing except ImportError: pass try: from setuptools import setup except ImportError: from distutils.core import setup from email.utils import parseaddr import flask_oauthlib author, author_email = parseaddr(flask_oauthlib.__author__) def fread(filename): with open(filename) as f: return f.read() setup( name='Flask-OAuthlib', version=flask_oauthlib.__version__, author=author, author_email=author_email, url=flask_oauthlib.__homepage__, packages=[ "flask_oauthlib", "flask_oauthlib.provider", "flask_oauthlib.contrib", "flask_oauthlib.contrib.client", ], description="OAuthlib for Flask", zip_safe=False, include_package_data=True, platforms='any', long_description=fread('README.rst'), license='BSD', install_requires=[ 'Flask', 'oauthlib>=1.1.2,!=2.0.3,!=2.0.4,!=2.0.5,<3.0.0', 'requests-oauthlib>=0.6.2', ], tests_require=['nose', 'Flask-SQLAlchemy', 'mock'], test_suite='nose.collector', classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved', 'License :: OSI Approved :: BSD License', 'Operating System :: MacOS', 'Operating System :: POSIX', 'Operating System :: POSIX :: Linux', 'Programming Language :: Python', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: Implementation', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 'Topic :: Software Development :: Libraries :: Python Modules', ] ) flask-oauthlib-0.9.5/tests/000077500000000000000000000000001327671251000156005ustar00rootroot00000000000000flask-oauthlib-0.9.5/tests/__init__.py000066400000000000000000000000001327671251000176770ustar00rootroot00000000000000flask-oauthlib-0.9.5/tests/_base.py000066400000000000000000000047351327671251000172340ustar00rootroot00000000000000# coding: utf-8 import base64 import os import sys import tempfile import unittest from flask_oauthlib.client import prepare_request try: from urlparse import urlparse except ImportError: from urllib.parse import urlparse if sys.version_info[0] == 3: python_version = 3 string_type = str else: python_version = 2 string_type = unicode # os.environ['DEBUG'] = 'true' # for oauthlib 0.6.3 os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = 'true' class BaseSuite(unittest.TestCase): def setUp(self): app = self.create_app() self.db_fd, self.db_file = tempfile.mkstemp() config = { 'OAUTH1_PROVIDER_ENFORCE_SSL': False, 'OAUTH1_PROVIDER_KEY_LENGTH': (3, 30), 'OAUTH1_PROVIDER_REALMS': ['email', 'address'], 'SQLALCHEMY_DATABASE_URI': 'sqlite:///%s' % self.db_file, 'SQLALCHEMY_TRACK_MODIFICATIONS': False } app.config.update(config) self.setup_app(app) self.app = app self.client = app.test_client() return app def tearDown(self): self.database.session.remove() self.database.drop_all() os.close(self.db_fd) os.unlink(self.db_file) @property def database(self): raise NotImplementedError def create_app(self): raise NotImplementedError def setup_app(self, app): raise NotImplementedError def patch_request(self, app): test_client = app.test_client() def make_request(uri, headers=None, data=None, method=None): uri, headers, data, method = prepare_request( uri, headers, data, method ) # test client is a `werkzeug.test.Client` parsed = urlparse(uri) uri = '%s?%s' % (parsed.path, parsed.query) resp = test_client.open( uri, headers=headers, data=data, method=method ) # for compatible resp.code = resp.status_code return resp, resp.data return make_request def to_unicode(text): if not isinstance(text, string_type): text = text.decode('utf-8') return text def to_bytes(text): if isinstance(text, string_type): text = text.encode('utf-8') return text def to_base64(text): return to_unicode(base64.b64encode(to_bytes(text))) def clean_url(location): location = to_unicode(location) ret = urlparse(location) return '%s?%s' % (ret.path, ret.query) flask-oauthlib-0.9.5/tests/oauth1/000077500000000000000000000000001327671251000170015ustar00rootroot00000000000000flask-oauthlib-0.9.5/tests/oauth1/__init__.py000066400000000000000000000000001327671251000211000ustar00rootroot00000000000000flask-oauthlib-0.9.5/tests/oauth1/client.py000066400000000000000000000044211327671251000206320ustar00rootroot00000000000000from flask import Flask, redirect, url_for, session, request, jsonify, abort from flask_oauthlib.client import OAuth def create_oauth(app): oauth = OAuth(app) remote = oauth.remote_app( 'dev', consumer_key='dev', consumer_secret='dev', request_token_params={'realm': 'email'}, base_url='http://127.0.0.1:5000/api/', request_token_url='http://127.0.0.1:5000/oauth/request_token', access_token_method='GET', access_token_url='http://127.0.0.1:5000/oauth/access_token', authorize_url='http://127.0.0.1:5000/oauth/authorize' ) return remote def create_client(app, oauth=None): if not oauth: oauth = create_oauth(app) @app.route('/') def index(): if 'dev_oauth' in session: ret = oauth.get('email') if isinstance(ret.data, dict): return jsonify(ret.data) return str(ret.data) return redirect(url_for('login')) @app.route('/login') def login(): return oauth.authorize(callback=url_for('authorized', _external=True)) @app.route('/logout') def logout(): session.pop('dev_oauth', None) return redirect(url_for('index')) @app.route('/authorized') def authorized(): resp = oauth.authorized_response() if resp is None: return 'Access denied: error=%s' % ( request.args['error'] ) if 'oauth_token' in resp: session['dev_oauth'] = resp return jsonify(resp) return str(resp) @app.route('/address') def address(): ret = oauth.get('address/hangzhou') if ret.status not in (200, 201): return abort(ret.status) return ret.raw_data @app.route('/method/') def method(name): func = getattr(oauth, name) ret = func('method') return ret.raw_data @oauth.tokengetter def get_oauth_token(): if 'dev_oauth' in session: resp = session['dev_oauth'] return resp['oauth_token'], resp['oauth_token_secret'] return oauth if __name__ == '__main__': app = Flask(__name__) app.debug = True app.secret_key = 'development' create_client(app) app.run(host='localhost', port=8000) flask-oauthlib-0.9.5/tests/oauth1/server.py000066400000000000000000000157601327671251000206720ustar00rootroot00000000000000# coding: utf-8 from flask import g, render_template, request, jsonify from flask_sqlalchemy import SQLAlchemy from flask_oauthlib.provider import OAuth1Provider db = SQLAlchemy() def enable_log(name='flask_oauthlib'): import logging logger = logging.getLogger(name) logger.addHandler(logging.StreamHandler()) logger.setLevel(logging.DEBUG) # enable_log() class User(db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(40), unique=True, index=True, nullable=False) class Client(db.Model): # id = db.Column(db.Integer, primary_key=True) # human readable name client_key = db.Column(db.String(40), primary_key=True) client_secret = db.Column(db.String(55), unique=True, index=True, nullable=False) rsa_key = db.Column(db.String(55)) _realms = db.Column(db.Text) _redirect_uris = db.Column(db.Text) @property def user(self): return User.query.get(1) @property def redirect_uris(self): if self._redirect_uris: return self._redirect_uris.split() return [] @property def default_redirect_uri(self): return self.redirect_uris[0] @property def default_realms(self): if self._realms: return self._realms.split() return [] class Grant(db.Model): id = db.Column(db.Integer, primary_key=True) user_id = db.Column( db.Integer, db.ForeignKey('user.id', ondelete='CASCADE') ) user = db.relationship('User') client_key = db.Column( db.String(40), db.ForeignKey('client.client_key'), nullable=False, ) client = db.relationship('Client') token = db.Column(db.String(255), index=True, unique=True) secret = db.Column(db.String(255), nullable=False) verifier = db.Column(db.String(255)) expires = db.Column(db.DateTime) redirect_uri = db.Column(db.Text) _realms = db.Column(db.Text) def delete(self): db.session.delete(self) db.session.commit() return self @property def realms(self): if self._realms: return self._realms.split() return [] class Token(db.Model): id = db.Column(db.Integer, primary_key=True) client_key = db.Column( db.String(40), db.ForeignKey('client.client_key'), nullable=False, ) client = db.relationship('Client') user_id = db.Column( db.Integer, db.ForeignKey('user.id'), ) user = db.relationship('User') token = db.Column(db.String(255)) secret = db.Column(db.String(255)) _realms = db.Column(db.Text) @property def realms(self): if self._realms: return self._realms.split() return [] def prepare_app(app): db.init_app(app) db.app = app db.create_all() client1 = Client( client_key='dev', client_secret='dev', _redirect_uris=( 'http://localhost:8000/authorized ' 'http://localhost/authorized' ), _realms='email', ) user = User(username='admin') try: db.session.add(client1) db.session.add(user) db.session.commit() except: db.session.rollback() return app def create_server(app): app = prepare_app(app) oauth = OAuth1Provider(app) @oauth.clientgetter def get_client(client_key): return Client.query.filter_by(client_key=client_key).first() @oauth.tokengetter def load_access_token(client_key, token, *args, **kwargs): t = Token.query.filter_by(client_key=client_key, token=token).first() return t @oauth.tokensetter def save_access_token(token, req): tok = Token( client_key=req.client.client_key, user_id=req.user.id, token=token['oauth_token'], secret=token['oauth_token_secret'], _realms=token['oauth_authorized_realms'], ) db.session.add(tok) db.session.commit() @oauth.grantgetter def load_request_token(token): grant = Grant.query.filter_by(token=token).first() return grant @oauth.grantsetter def save_request_token(token, oauth): if oauth.realms: realms = ' '.join(oauth.realms) else: realms = None grant = Grant( token=token['oauth_token'], secret=token['oauth_token_secret'], client_key=oauth.client.client_key, redirect_uri=oauth.redirect_uri, _realms=realms, ) db.session.add(grant) db.session.commit() return grant @oauth.verifiergetter def load_verifier(verifier, token): return Grant.query.filter_by(verifier=verifier, token=token).first() @oauth.verifiersetter def save_verifier(token, verifier, *args, **kwargs): tok = Grant.query.filter_by(token=token).first() tok.verifier = verifier['oauth_verifier'] tok.user_id = g.user.id db.session.add(tok) db.session.commit() return tok @oauth.noncegetter def load_nonce(*args, **kwargs): return None @oauth.noncesetter def save_nonce(*args, **kwargs): return None @app.before_request def load_current_user(): user = User.query.get(1) g.user = user @app.route('/home') def home(): return render_template('home.html') @app.route('/oauth/authorize', methods=['GET', 'POST']) @oauth.authorize_handler def authorize(*args, **kwargs): # NOTICE: for real project, you need to require login if request.method == 'GET': # render a page for user to confirm the authorization return render_template('confirm.html') confirm = request.form.get('confirm', 'no') return confirm == 'yes' @app.route('/oauth/request_token') @oauth.request_token_handler def request_token(): return {} @app.route('/oauth/access_token') @oauth.access_token_handler def access_token(): return {} @app.route('/api/email') @oauth.require_oauth('email') def email_api(): oauth = request.oauth return jsonify(email='me@oauth.net', username=oauth.user.username) @app.route('/api/address/') @oauth.require_oauth('address') def address_api(city): oauth = request.oauth return jsonify(address=city, username=oauth.user.username) @app.route('/api/method', methods=['GET', 'POST', 'PUT', 'DELETE']) @oauth.require_oauth() def method_api(): return jsonify(method=request.method) return app if __name__ == '__main__': from flask import Flask app = Flask(__name__) app.debug = True app.secret_key = 'development' app.config.update({ 'SQLALCHEMY_DATABASE_URI': 'sqlite:///oauth1.sqlite', 'OAUTH1_PROVIDER_ENFORCE_SSL': False, 'OAUTH1_PROVIDER_KEY_LENGTH': (3, 30), 'OAUTH1_PROVIDER_REALMS': ['email', 'address'] }) app = create_server(app) app.run() flask-oauthlib-0.9.5/tests/oauth1/templates/000077500000000000000000000000001327671251000207775ustar00rootroot00000000000000flask-oauthlib-0.9.5/tests/oauth1/templates/confirm.html000066400000000000000000000003361327671251000233240ustar00rootroot00000000000000{% extends "layout.html" %} {% block body %}

Allow?

{% endblock %} flask-oauthlib-0.9.5/tests/oauth1/templates/home.html000066400000000000000000000000341327671251000226120ustar00rootroot00000000000000{% extends "layout.html" %} flask-oauthlib-0.9.5/tests/oauth1/templates/layout.html000066400000000000000000000003521327671251000232020ustar00rootroot00000000000000 Auth Provider {% if g.user %} {{g.user.username}} {% endif %} {% block body %}{% endblock %} flask-oauthlib-0.9.5/tests/oauth1/test_oauth1.py000066400000000000000000000125131327671251000216150ustar00rootroot00000000000000# coding: utf-8 import time from mock import MagicMock from nose.tools import raises from flask import Flask from flask_oauthlib.client import OAuth, OAuthException from .server import create_server, db from .client import create_client from .._base import BaseSuite, clean_url from .._base import to_unicode as u class OAuthSuite(BaseSuite): @property def database(self): return db def create_app(self): app = Flask(__name__) app.debug = True app.testing = True app.secret_key = 'development' return app def setup_app(self, app): self.create_server(app) client = self.create_client(app) client.http_request = MagicMock( side_effect=self.patch_request(app) ) def create_server(self, app): create_server(app) return app def create_client(self, app): return create_client(app) class TestWebAuth(OAuthSuite): def test_full_flow(self): rv = self.client.get('/login') assert 'oauth_token' in rv.location auth_url = clean_url(rv.location) rv = self.client.get(auth_url) assert '' in u(rv.data) rv = self.client.post(auth_url, data={ 'confirm': 'yes' }) assert 'oauth_token' in rv.location token_url = clean_url(rv.location) rv = self.client.get(token_url) assert 'oauth_token_secret' in u(rv.data) rv = self.client.get('/') assert 'email' in u(rv.data) rv = self.client.get('/address') assert rv.status_code == 401 rv = self.client.get('/method/post') assert 'POST' in u(rv.data) rv = self.client.get('/method/put') assert 'PUT' in u(rv.data) rv = self.client.get('/method/delete') assert 'DELETE' in u(rv.data) def test_no_confirm(self): rv = self.client.get('/login') assert 'oauth_token' in rv.location auth_url = clean_url(rv.location) rv = self.client.post(auth_url, data={ 'confirm': 'no' }) assert 'error=denied' in rv.location def test_invalid_request_token(self): rv = self.client.get('/login') assert 'oauth_token' in rv.location loc = rv.location.replace('oauth_token=', 'oauth_token=a') auth_url = clean_url(loc) rv = self.client.get(auth_url) assert 'error' in rv.location rv = self.client.post(auth_url, data={ 'confirm': 'yes' }) assert 'error' in rv.location auth_header = ( u'OAuth realm="%(realm)s",' u'oauth_nonce="97392753692390970531372987366",' u'oauth_timestamp="%(timestamp)d", oauth_version="1.0",' u'oauth_signature_method="%(signature_method)s",' u'oauth_consumer_key="%(key)s",' u'oauth_callback="%(callback)s",' u'oauth_signature="%(signature)s"' ) auth_dict = { 'realm': 'email', 'timestamp': int(time.time()), 'key': 'dev', 'signature_method': 'HMAC-SHA1', 'callback': 'http%3A%2F%2Flocalhost%2Fauthorized', 'signature': 'LngsvwVPnd8vCZ2hr7umJvqb%2Fyw%3D', } class TestInvalid(OAuthSuite): @raises(OAuthException) def test_request(self): self.client.get('/login') def test_request_token(self): rv = self.client.get('/oauth/request_token') assert 'error' in u(rv.data) def test_access_token(self): rv = self.client.get('/oauth/access_token') assert 'error' in u(rv.data) def test_invalid_realms(self): auth_format = auth_dict.copy() auth_format['realm'] = 'profile' headers = { u'Authorization': auth_header % auth_format } rv = self.client.get('/oauth/request_token', headers=headers) assert 'error' in u(rv.data) assert 'realm' in u(rv.data) def test_no_realms(self): auth_format = auth_dict.copy() auth_format['realm'] = '' headers = { u'Authorization': auth_header % auth_format } rv = self.client.get('/oauth/request_token', headers=headers) assert 'secret' in u(rv.data) def test_no_callback(self): auth_format = auth_dict.copy() auth_format['callback'] = '' headers = { u'Authorization': auth_header % auth_format } rv = self.client.get('/oauth/request_token', headers=headers) assert 'error' in u(rv.data) assert 'callback' in u(rv.data) def test_invalid_signature_method(self): auth_format = auth_dict.copy() auth_format['signature_method'] = 'PLAIN' headers = { u'Authorization': auth_header % auth_format } rv = self.client.get('/oauth/request_token', headers=headers) assert 'error' in u(rv.data) assert 'signature' in u(rv.data) def create_client(self, app): oauth = OAuth(app) remote = oauth.remote_app( 'dev', consumer_key='noclient', consumer_secret='dev', request_token_params={'realm': 'email'}, base_url='http://localhost/api/', request_token_url='http://localhost/oauth/request_token', access_token_method='GET', access_token_url='http://localhost/oauth/access_token', authorize_url='http://localhost/oauth/authorize' ) return create_client(app, remote) flask-oauthlib-0.9.5/tests/oauth2/000077500000000000000000000000001327671251000170025ustar00rootroot00000000000000flask-oauthlib-0.9.5/tests/oauth2/__init__.py000066400000000000000000000000001327671251000211010ustar00rootroot00000000000000flask-oauthlib-0.9.5/tests/oauth2/client.py000066400000000000000000000044571327671251000206440ustar00rootroot00000000000000from flask import Flask, redirect, url_for, session, request, jsonify, abort from flask_oauthlib.client import OAuth def create_client(app): oauth = OAuth(app) remote = oauth.remote_app( 'dev', consumer_key='dev', consumer_secret='dev', request_token_params={'scope': 'email'}, base_url='http://127.0.0.1:5000/api/', request_token_url=None, access_token_method='POST', access_token_url='http://127.0.0.1:5000/oauth/token', authorize_url='http://127.0.0.1:5000/oauth/authorize' ) @app.route('/') def index(): if 'dev_token' in session: ret = remote.get('email') return jsonify(ret.data) return redirect(url_for('login')) @app.route('/login') def login(): return remote.authorize(callback=url_for('authorized', _external=True)) @app.route('/logout') def logout(): session.pop('dev_token', None) return redirect(url_for('index')) @app.route('/authorized') def authorized(): resp = remote.authorized_response() if resp is None: return 'Access denied: error=%s' % ( request.args['error'] ) if isinstance(resp, dict) and 'access_token' in resp: session['dev_token'] = (resp['access_token'], '') return jsonify(resp) return str(resp) @app.route('/client') def client_method(): ret = remote.get("client") if ret.status not in (200, 201): return abort(ret.status) return ret.raw_data @app.route('/address') def address(): ret = remote.get('address/hangzhou') if ret.status not in (200, 201): return ret.raw_data, ret.status return ret.raw_data @app.route('/method/') def method(name): func = getattr(remote, name) ret = func('method') return ret.raw_data @remote.tokengetter def get_oauth_token(): return session.get('dev_token') return remote if __name__ == '__main__': import os os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = 'true' # DEBUG=1 python oauth2_client.py app = Flask(__name__) app.debug = True app.secret_key = 'development' create_client(app) app.run(host='localhost', port=8000) flask-oauthlib-0.9.5/tests/oauth2/server.py000066400000000000000000000224721327671251000206710ustar00rootroot00000000000000# coding: utf-8 from datetime import datetime, timedelta from flask import g, render_template, request, jsonify, make_response from flask_sqlalchemy import SQLAlchemy from sqlalchemy.orm import relationship from flask_oauthlib.provider import OAuth2Provider from flask_oauthlib.contrib.oauth2 import bind_sqlalchemy from flask_oauthlib.contrib.oauth2 import bind_cache_grant db = SQLAlchemy() class User(db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(40), unique=True, index=True, nullable=False) def check_password(self, password): return True class Client(db.Model): # id = db.Column(db.Integer, primary_key=True) # human readable name name = db.Column(db.String(40)) client_id = db.Column(db.String(40), primary_key=True) client_secret = db.Column(db.String(55), unique=True, index=True, nullable=False) client_type = db.Column(db.String(20), default='public') _redirect_uris = db.Column(db.Text) default_scope = db.Column(db.Text, default='email address') @property def user(self): return User.query.get(1) @property def redirect_uris(self): if self._redirect_uris: return self._redirect_uris.split() return [] @property def default_redirect_uri(self): return self.redirect_uris[0] @property def default_scopes(self): if self.default_scope: return self.default_scope.split() return [] @property def allowed_grant_types(self): return ['authorization_code', 'password', 'client_credentials', 'refresh_token'] class Grant(db.Model): id = db.Column(db.Integer, primary_key=True) user_id = db.Column( db.Integer, db.ForeignKey('user.id', ondelete='CASCADE') ) user = relationship('User') client_id = db.Column( db.String(40), db.ForeignKey('client.client_id', ondelete='CASCADE'), nullable=False, ) client = relationship('Client') code = db.Column(db.String(255), index=True, nullable=False) redirect_uri = db.Column(db.String(255)) scope = db.Column(db.Text) expires = db.Column(db.DateTime) def delete(self): db.session.delete(self) db.session.commit() return self @property def scopes(self): if self.scope: return self.scope.split() return None class Token(db.Model): id = db.Column(db.Integer, primary_key=True) client_id = db.Column( db.String(40), db.ForeignKey('client.client_id', ondelete='CASCADE'), nullable=False, ) user_id = db.Column( db.Integer, db.ForeignKey('user.id', ondelete='CASCADE') ) user = relationship('User') client = relationship('Client') token_type = db.Column(db.String(40)) access_token = db.Column(db.String(255)) refresh_token = db.Column(db.String(255)) expires = db.Column(db.DateTime) scope = db.Column(db.Text) def __init__(self, **kwargs): expires_in = kwargs.pop('expires_in', None) if expires_in is not None: self.expires = datetime.utcnow() + timedelta(seconds=expires_in) for k, v in kwargs.items(): setattr(self, k, v) @property def scopes(self): if self.scope: return self.scope.split() return [] def delete(self): db.session.delete(self) db.session.commit() return self def current_user(): return g.user def cache_provider(app): oauth = OAuth2Provider(app) bind_sqlalchemy(oauth, db.session, user=User, token=Token, client=Client) app.config.update({'OAUTH2_CACHE_TYPE': 'simple'}) bind_cache_grant(app, oauth, current_user) return oauth def sqlalchemy_provider(app): oauth = OAuth2Provider(app) bind_sqlalchemy(oauth, db.session, user=User, token=Token, client=Client, grant=Grant, current_user=current_user) return oauth def default_provider(app): oauth = OAuth2Provider(app) @oauth.clientgetter def get_client(client_id): return Client.query.filter_by(client_id=client_id).first() @oauth.grantgetter def get_grant(client_id, code): return Grant.query.filter_by(client_id=client_id, code=code).first() @oauth.tokengetter def get_token(access_token=None, refresh_token=None): if access_token: return Token.query.filter_by(access_token=access_token).first() if refresh_token: return Token.query.filter_by(refresh_token=refresh_token).first() return None @oauth.grantsetter def set_grant(client_id, code, request, *args, **kwargs): expires = datetime.utcnow() + timedelta(seconds=100) grant = Grant( client_id=client_id, code=code['code'], redirect_uri=request.redirect_uri, scope=' '.join(request.scopes), user_id=g.user.id, expires=expires, ) db.session.add(grant) db.session.commit() @oauth.tokensetter def set_token(token, request, *args, **kwargs): # In real project, a token is unique bound to user and client. # Which means, you don't need to create a token every time. tok = Token(**token) tok.user_id = request.user.id tok.client_id = request.client.client_id db.session.add(tok) db.session.commit() @oauth.usergetter def get_user(username, password, *args, **kwargs): # This is optional, if you don't need password credential # there is no need to implement this method return User.query.filter_by(username=username).first() return oauth def prepare_app(app): db.init_app(app) db.app = app db.create_all() client1 = Client( name='dev', client_id='dev', client_secret='dev', _redirect_uris=( 'http://localhost:8000/authorized ' 'http://localhost/authorized' ), ) client2 = Client( name='confidential', client_id='confidential', client_secret='confidential', client_type='confidential', _redirect_uris=( 'http://localhost:8000/authorized ' 'http://localhost/authorized' ), ) user = User(username='admin') temp_grant = Grant( user_id=1, client_id='confidential', code='12345', scope='email', expires=datetime.utcnow() + timedelta(seconds=100) ) access_token = Token( user_id=1, client_id='dev', access_token='expired', expires_in=0 ) access_token2 = Token( user_id=1, client_id='dev', access_token='never_expire' ) try: db.session.add(client1) db.session.add(client2) db.session.add(user) db.session.add(temp_grant) db.session.add(access_token) db.session.add(access_token2) db.session.commit() except: db.session.rollback() return app def create_server(app, oauth=None): if not oauth: oauth = default_provider(app) app = prepare_app(app) @app.before_request def load_current_user(): user = User.query.get(1) g.user = user @app.route('/home') def home(): return render_template('home.html') @app.route('/oauth/authorize', methods=['GET', 'POST']) @oauth.authorize_handler def authorize(*args, **kwargs): # NOTICE: for real project, you need to require login if request.method == 'GET': # render a page for user to confirm the authorization return render_template('confirm.html') if request.method == 'HEAD': # if HEAD is supported properly, request parameters like # client_id should be validated the same way as for 'GET' response = make_response('', 200) response.headers['X-Client-ID'] = kwargs.get('client_id') return response confirm = request.form.get('confirm', 'no') return confirm == 'yes' @app.route('/oauth/token', methods=['POST', 'GET']) @oauth.token_handler def access_token(): return {} @app.route('/oauth/revoke', methods=['POST']) @oauth.revoke_handler def revoke_token(): pass @app.route('/api/email') @oauth.require_oauth('email') def email_api(): oauth = request.oauth return jsonify(email='me@oauth.net', username=oauth.user.username) @app.route('/api/client') @oauth.require_oauth() def client_api(): oauth = request.oauth return jsonify(client=oauth.client.name) @app.route('/api/address/') @oauth.require_oauth('address') def address_api(city): oauth = request.oauth return jsonify(address=city, username=oauth.user.username) @app.route('/api/method', methods=['GET', 'POST', 'PUT', 'DELETE']) @oauth.require_oauth() def method_api(): return jsonify(method=request.method) @oauth.invalid_response def require_oauth_invalid(req): return jsonify(message=req.error_message), 401 return app if __name__ == '__main__': from flask import Flask app = Flask(__name__) app.debug = True app.secret_key = 'development' app.config.update({ 'SQLALCHEMY_DATABASE_URI': 'sqlite:///test.sqlite' }) app = create_server(app) app.run() flask-oauthlib-0.9.5/tests/oauth2/templates/000077500000000000000000000000001327671251000210005ustar00rootroot00000000000000flask-oauthlib-0.9.5/tests/oauth2/templates/confirm.html000066400000000000000000000003361327671251000233250ustar00rootroot00000000000000{% extends "layout.html" %} {% block body %}

Allow?

{% endblock %} flask-oauthlib-0.9.5/tests/oauth2/templates/home.html000066400000000000000000000000341327671251000226130ustar00rootroot00000000000000{% extends "layout.html" %} flask-oauthlib-0.9.5/tests/oauth2/templates/layout.html000066400000000000000000000003521327671251000232030ustar00rootroot00000000000000 Auth Provider {% if g.user %} {{g.user.username}} {% endif %} {% block body %}{% endblock %} flask-oauthlib-0.9.5/tests/oauth2/test_oauth2.py000066400000000000000000000306231327671251000216210ustar00rootroot00000000000000# coding: utf-8 import json from flask import Flask from mock import MagicMock from .server import ( create_server, db, cache_provider, sqlalchemy_provider, default_provider, Token ) from .client import create_client from .._base import BaseSuite, clean_url, to_base64 from .._base import to_unicode as u class OAuthSuite(BaseSuite): @property def database(self): return db def create_oauth_provider(app): raise NotImplementedError('Each test class must' 'implement this method.') def create_app(self): app = Flask(__name__) app.debug = True app.testing = True app.secret_key = 'development' return app def setup_app(self, app): oauth = self.create_oauth_provider(app) create_server(app, oauth) client = create_client(app) client.http_request = MagicMock( side_effect=self.patch_request(app) ) self.oauth_client = client return app authorize_url = ( '/oauth/authorize?response_type=code&client_id=dev' '&redirect_uri=http%3A%2F%2Flocalhost%3A8000%2Fauthorized&scope=email' ) auth_code = to_base64('confidential:confidential') class TestWebAuth(OAuthSuite): def create_oauth_provider(self, app): return default_provider(app) def test_login(self): rv = self.client.get('/login') assert 'response_type=code' in rv.location def test_oauth_authorize_invalid_url(self): rv = self.client.get('/oauth/authorize') assert 'Missing+client_id+parameter.' in rv.location def test_oauth_authorize_valid_url(self): rv = self.client.get(authorize_url) assert b'' in rv.data rv = self.client.post(authorize_url, data=dict( confirm='no' )) assert 'access_denied' in rv.location rv = self.client.post(authorize_url, data=dict( confirm='yes' )) # success assert 'code=' in rv.location assert 'state' not in rv.location # test state rv = self.client.post(authorize_url + '&state=foo', data=dict( confirm='yes' )) assert 'code=' in rv.location assert 'state' in rv.location def test_http_head_oauth_authorize_valid_url(self): rv = self.client.head(authorize_url) assert rv.headers['X-Client-ID'] == 'dev' def test_get_access_token(self): rv = self.client.post(authorize_url, data={'confirm': 'yes'}) rv = self.client.get(clean_url(rv.location)) assert b'access_token' in rv.data def test_full_flow(self): rv = self.client.post(authorize_url, data={'confirm': 'yes'}) rv = self.client.get(clean_url(rv.location)) assert b'access_token' in rv.data rv = self.client.get('/') assert b'username' in rv.data rv = self.client.get('/address') assert rv.status_code == 401 assert b'message' in rv.data rv = self.client.get('/method/post') assert b'POST' in rv.data rv = self.client.get('/method/put') assert b'PUT' in rv.data rv = self.client.get('/method/delete') assert b'DELETE' in rv.data def test_no_bear_token(self): @self.oauth_client.tokengetter def get_oauth_token(): return 'foo', '' rv = self.client.get('/method/put') assert b'token not found' in rv.data def test_expires_bear_token(self): @self.oauth_client.tokengetter def get_oauth_token(): return 'expired', '' rv = self.client.get('/method/put') assert b'token is expired' in rv.data def test_never_expiring_bear_token(self): @self.oauth_client.tokengetter def get_oauth_token(): return 'never_expire', '' rv = self.client.get('/method/put') assert rv.status_code == 200 def test_get_client(self): rv = self.client.post(authorize_url, data={'confirm': 'yes'}) rv = self.client.get(clean_url(rv.location)) rv = self.client.get("/client") assert b'dev' in rv.data def test_invalid_response_type(self): authorize_url = ( '/oauth/authorize?response_type=invalid&client_id=dev' '&redirect_uri=http%3A%2F%2Flocalhost%3A8000%2Fauthorized' '&scope=email' ) rv = self.client.post(authorize_url, data={'confirm': 'yes'}) rv = self.client.get(clean_url(rv.location)) assert b'error' in rv.data def test_invalid_scope(self): authorize_url = ( '/oauth/authorize?response_type=code&client_id=dev' '&redirect_uri=http%3A%2F%2Flocalhost%3A8000%2Fauthorized' '&scope=invalid' ) rv = self.client.get(authorize_url) rv = self.client.get(clean_url(rv.location)) assert b'error' in rv.data assert b'invalid_scope' in rv.data class TestWebAuthCached(TestWebAuth): def create_oauth_provider(self, app): return cache_provider(app) class TestWebAuthSQLAlchemy(TestWebAuth): def create_oauth_provider(self, app): return sqlalchemy_provider(app) class TestRefreshToken(OAuthSuite): def create_oauth_provider(self, app): return default_provider(app) def test_refresh_token_in_password_grant(self): url = ('/oauth/token?grant_type=password' '&scope=email+address&username=admin&password=admin') rv = self.client.get(url, headers={ 'Authorization': 'Basic %s' % auth_code, }) assert b'access_token' in rv.data data = json.loads(u(rv.data)) args = (data.get('scope').replace(' ', '+'), data.get('refresh_token')) url = ('/oauth/token?grant_type=refresh_token' '&scope=%s&refresh_token=%s') url = url % args rv = self.client.get(url, headers={ 'Authorization': 'Basic %s' % auth_code, }) assert b'access_token' in rv.data def test_refresh_token_in_authorization_code(self): rv = self.client.post(authorize_url, data={'confirm': 'yes'}) rv = self.client.get(clean_url(rv.location)) data = json.loads(u(rv.data)) args = (data.get('scope').replace(' ', '+'), data.get('refresh_token'), 'dev', 'dev') url = ('/oauth/token?grant_type=refresh_token' '&scope=%s&refresh_token=%s' '&client_id=%s&client_secret=%s') url = url % args rv = self.client.get(url) assert b'access_token' in rv.data class TestRefreshTokenCached(TestRefreshToken): def create_oauth_provider(self, app): return cache_provider(app) class TestRefreshTokenSQLAlchemy(TestRefreshToken): def create_oauth_provider(self, app): return sqlalchemy_provider(app) class TestRevokeToken(OAuthSuite): def create_oauth_provider(self, app): return default_provider(app) def get_token(self): url = ('/oauth/token?grant_type=password' '&scope=email+address&username=admin&password=admin') rv = self.client.get(url, headers={ 'Authorization': 'Basic %s' % auth_code, }) assert b'_token' in rv.data return json.loads(u(rv.data)) def test_revoke_token(self): data = self.get_token() tok = Token.query.filter_by( refresh_token=data['refresh_token']).first() assert tok.refresh_token == data['refresh_token'] revoke_url = '/oauth/revoke' args = {'token': data['refresh_token']} self.client.post(revoke_url, data=args, headers={ 'Authorization': 'Basic %s' % auth_code, }) tok = Token.query.filter_by( refresh_token=data['refresh_token']).first() assert tok is None def test_revoke_token_with_hint(self): data = self.get_token() tok = Token.query.filter_by( access_token=data['access_token']).first() assert tok.access_token == data['access_token'] revoke_url = '/oauth/revoke' args = {'token': data['access_token'], 'token_type_hint': 'access_token'} self.client.post(revoke_url, data=args, headers={ 'Authorization': 'Basic %s' % auth_code, }) tok = Token.query.filter_by( access_token=data['access_token']).first() assert tok is None class TestRevokeTokenCached(TestRefreshToken): def create_oauth_provider(self, app): return cache_provider(app) class TestRevokeTokenSQLAlchemy(TestRefreshToken): def create_oauth_provider(self, app): return sqlalchemy_provider(app) class TestCredentialAuth(OAuthSuite): def create_oauth_provider(self, app): return default_provider(app) def test_get_access_token(self): url = ('/oauth/token?grant_type=client_credentials' '&scope=email+address') rv = self.client.get(url, headers={ 'Authorization': 'Basic %s' % auth_code, }) assert b'access_token' in rv.data def test_invalid_auth_header(self): url = ('/oauth/token?grant_type=client_credentials' '&scope=email+address') rv = self.client.get(url, headers={ 'Authorization': 'Basic foobar' }) assert b'invalid_client' in rv.data def test_no_client(self): auth_code = to_base64('none:confidential') url = ('/oauth/token?grant_type=client_credentials' '&scope=email+address') rv = self.client.get(url, headers={ 'Authorization': 'Basic %s' % auth_code, }) assert b'invalid_client' in rv.data def test_wrong_secret_client(self): auth_code = to_base64('confidential:wrong') url = ('/oauth/token?grant_type=client_credentials' '&scope=email+address') rv = self.client.get(url, headers={ 'Authorization': 'Basic %s' % auth_code, }) assert b'invalid_client' in rv.data class TestCredentialAuthCached(TestCredentialAuth): def create_oauth_provider(self, app): return cache_provider(app) class TestCredentialAuthSQLAlchemy(TestCredentialAuth): def create_oauth_provider(self, app): return sqlalchemy_provider(app) class TestTokenGenerator(OAuthSuite): def create_oauth_provider(self, app): def generator(request): return 'foobar' app.config['OAUTH2_PROVIDER_TOKEN_GENERATOR'] = generator return default_provider(app) def test_get_access_token(self): rv = self.client.post(authorize_url, data={'confirm': 'yes'}) rv = self.client.get(clean_url(rv.location)) data = json.loads(u(rv.data)) assert data['access_token'] == 'foobar' assert data['refresh_token'] == 'foobar' class TestRefreshTokenGenerator(OAuthSuite): def create_oauth_provider(self, app): def at_generator(request): return 'foobar' def rt_generator(request): return 'abracadabra' app.config['OAUTH2_PROVIDER_TOKEN_GENERATOR'] = at_generator app.config['OAUTH2_PROVIDER_REFRESH_TOKEN_GENERATOR'] = rt_generator return default_provider(app) def test_get_access_token(self): rv = self.client.post(authorize_url, data={'confirm': 'yes'}) rv = self.client.get(clean_url(rv.location)) data = json.loads(u(rv.data)) assert data['access_token'] == 'foobar' assert data['refresh_token'] == 'abracadabra' class TestConfidentialClient(OAuthSuite): def create_oauth_provider(self, app): return default_provider(app) def test_get_access_token(self): url = ('/oauth/token?grant_type=authorization_code&code=12345' '&scope=email') rv = self.client.get(url, headers={ 'Authorization': 'Basic %s' % auth_code }) assert b'access_token' in rv.data def test_invalid_grant(self): url = ('/oauth/token?grant_type=authorization_code&code=54321' '&scope=email') rv = self.client.get(url, headers={ 'Authorization': 'Basic %s' % auth_code }) assert b'invalid_grant' in rv.data def test_invalid_client(self): url = ('/oauth/token?grant_type=authorization_code&code=12345' '&scope=email') rv = self.client.get(url, headers={ 'Authorization': 'Basic %s' % ('foo') }) assert b'invalid_client' in rv.data flask-oauthlib-0.9.5/tests/test_client.py000066400000000000000000000146131327671251000204740ustar00rootroot00000000000000from flask import Flask from nose.tools import raises from flask_oauthlib.client import encode_request_data from flask_oauthlib.client import OAuthRemoteApp, OAuth from flask_oauthlib.client import parse_response from oauthlib.common import PY3 try: import urllib2 as http http_urlopen = 'urllib2.urlopen' except ImportError: from urllib import request as http http_urlopen = 'urllib.request.urlopen' from mock import patch class Response(object): def __init__(self, content, headers=None): self.content = content self.headers = headers or {} @property def code(self): return self.headers.get('status-code', 500) @property def status_code(self): return self.code def read(self): return self.content def close(self): return self def test_encode_request_data(): data, _ = encode_request_data('foo', None) assert data == 'foo' data, f = encode_request_data(None, 'json') assert data == '{}' assert f == 'application/json' data, f = encode_request_data(None, 'urlencoded') assert data == '' assert f == 'application/x-www-form-urlencoded' def test_app(): app = Flask(__name__) oauth = OAuth(app) remote = oauth.remote_app( 'dev', consumer_key='dev', consumer_secret='dev', request_token_params={'scope': 'email'}, base_url='http://127.0.0.1:5000/api/', request_token_url=None, access_token_method='POST', access_token_url='http://127.0.0.1:5000/oauth/token', authorize_url='http://127.0.0.1:5000/oauth/authorize' ) client = app.extensions['oauthlib.client'] assert client.dev.name == 'dev' def test_parse_xml(): resp = Response( 'bar', headers={ 'status-code': 200, 'content-type': 'text/xml' } ) parse_response(resp, resp.read()) @raises(AttributeError) def test_raise_app(): app = Flask(__name__) oauth = OAuth(app) client = app.extensions['oauthlib.client'] assert client.demo.name == 'dev' class TestOAuthRemoteApp(object): @raises(TypeError) def test_raise_init(self): OAuthRemoteApp('oauth', 'twitter') def test_not_raise_init(self): OAuthRemoteApp('oauth', 'twitter', app_key='foo') def test_lazy_load(self): oauth = OAuth() twitter = oauth.remote_app( 'twitter', base_url='https://api.twitter.com/1/', app_key='twitter' ) assert twitter.base_url == 'https://api.twitter.com/1/' app = Flask(__name__) app.config.update({ 'twitter': dict( request_token_params={'realms': 'email'}, consumer_key='twitter key', consumer_secret='twitter secret', request_token_url='request url', access_token_url='token url', authorize_url='auth url', ) }) oauth.init_app(app) assert twitter.consumer_key == 'twitter key' assert twitter.consumer_secret == 'twitter secret' assert twitter.request_token_url == 'request url' assert twitter.access_token_url == 'token url' assert twitter.authorize_url == 'auth url' assert twitter.content_type is None assert 'realms' in twitter.request_token_params def test_lazy_load_with_plain_text_config(self): oauth = OAuth() twitter = oauth.remote_app('twitter', app_key='TWITTER') app = Flask(__name__) app.config['TWITTER_CONSUMER_KEY'] = 'twitter key' app.config['TWITTER_CONSUMER_SECRET'] = 'twitter secret' app.config['TWITTER_REQUEST_TOKEN_URL'] = 'request url' app.config['TWITTER_ACCESS_TOKEN_URL'] = 'token url' app.config['TWITTER_AUTHORIZE_URL'] = 'auth url' oauth.init_app(app) assert twitter.consumer_key == 'twitter key' assert twitter.consumer_secret == 'twitter secret' assert twitter.request_token_url == 'request url' assert twitter.access_token_url == 'token url' assert twitter.authorize_url == 'auth url' @patch(http_urlopen) def test_http_request(self, urlopen): urlopen.return_value = Response( b'{"foo": "bar"}', headers={'status-code': 200} ) resp, content = OAuthRemoteApp.http_request('http://example.com') assert resp.code == 200 assert b'foo' in content resp, content = OAuthRemoteApp.http_request( 'http://example.com/', method='GET', data={'wd': 'flask-oauthlib'} ) assert resp.code == 200 assert b'foo' in content resp, content = OAuthRemoteApp.http_request( 'http://example.com/', data={'wd': 'flask-oauthlib'} ) assert resp.code == 200 assert b'foo' in content @patch(http_urlopen) def test_raise_http_request(self, urlopen): error = http.HTTPError( 'http://example.com/', 404, 'Not Found', None, None ) error.read = lambda: b'o' class _Fake(object): def close(self): return 0 class _Faker(object): _closer = _Fake() error.file = _Faker() urlopen.side_effect = error resp, content = OAuthRemoteApp.http_request('http://example.com') assert resp.code == 404 assert b'o' in content def test_token_types(self): oauth = OAuth() remote = oauth.remote_app('remote', consumer_key='remote key', consumer_secret='remote secret') client_token = {'access_token': 'access token'} if not PY3: unicode_token = u'access token' client = remote.make_client(token=unicode_token) assert client.token == client_token str_token = 'access token' client = remote.make_client(token=str_token) assert client.token == client_token list_token = ['access token'] client = remote.make_client(token=list_token) assert client.token == client_token tuple_token = ('access token',) client = remote.make_client(token=tuple_token) assert client.token == client_token dict_token = {'access_token': 'access token'} client = remote.make_client(token=dict_token) assert client.token == client_token flask-oauthlib-0.9.5/tests/test_contrib/000077500000000000000000000000001327671251000202775ustar00rootroot00000000000000flask-oauthlib-0.9.5/tests/test_contrib/__init__.py000066400000000000000000000000001327671251000223760ustar00rootroot00000000000000flask-oauthlib-0.9.5/tests/test_contrib/test_apps.py000066400000000000000000000033261327671251000226570ustar00rootroot00000000000000import unittest from flask import Flask from flask_oauthlib.client import OAuth from flask_oauthlib.contrib.apps import douban, linkedin from nose.tools import assert_raises class RemoteAppFactorySuite(unittest.TestCase): def setUp(self): self.app = Flask(__name__) self.oauth = OAuth(self.app) def test_douban(self): assert 'douban.com' in douban.__doc__ assert ':param scope:' in douban.__doc__ c1 = douban.create(self.oauth) assert 'api.douban.com/v2' in c1.base_url assert c1.request_token_params.get('scope') == 'douban_basic_common' assert_raises(KeyError, lambda: c1.consumer_key) assert_raises(KeyError, lambda: c1.consumer_secret) self.app.config['DOUBAN_CONSUMER_KEY'] = 'douban key' self.app.config['DOUBAN_CONSUMER_SECRET'] = 'douban secret' assert c1.consumer_key == 'douban key' assert c1.consumer_secret == 'douban secret' c2 = douban.register_to(self.oauth, 'doudou', scope=['a', 'b']) assert c2.request_token_params.get('scope') == 'a,b' assert_raises(KeyError, lambda: c2.consumer_key) self.app.config['DOUDOU_CONSUMER_KEY'] = 'douban2 key' assert c2.consumer_key == 'douban2 key' def test_linkedin(self): c1 = linkedin.create(self.oauth) assert c1.name == 'linkedin' assert c1.request_token_params == { 'state': 'RandomString', 'scope': 'r_basicprofile', } c2 = linkedin.register_to(self.oauth, name='l2', scope=['c', 'd']) assert c2.name == 'l2' assert c2.request_token_params == { 'state': 'RandomString', 'scope': 'c,d', }, c2.request_token_params flask-oauthlib-0.9.5/tests/test_oauth2/000077500000000000000000000000001327671251000200415ustar00rootroot00000000000000flask-oauthlib-0.9.5/tests/test_oauth2/__init__.py000066400000000000000000000000001327671251000221400ustar00rootroot00000000000000flask-oauthlib-0.9.5/tests/test_oauth2/base.py000066400000000000000000000216511327671251000213320ustar00rootroot00000000000000# coding: utf-8 import os import unittest from datetime import datetime, timedelta from flask import Flask from flask import g, render_template, request, jsonify, make_response from flask_sqlalchemy import SQLAlchemy from sqlalchemy.orm import relationship from flask_oauthlib.provider import OAuth2Provider from flask_oauthlib.contrib.oauth2 import bind_sqlalchemy from flask_oauthlib.contrib.oauth2 import bind_cache_grant os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = 'true' db = SQLAlchemy() class User(db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(40), unique=True, index=True, nullable=False) def check_password(self, password): return password != 'wrong' class Client(db.Model): # id = db.Column(db.Integer, primary_key=True) # human readable name name = db.Column(db.String(40)) client_id = db.Column(db.String(40), primary_key=True) client_secret = db.Column(db.String(55), unique=True, index=True, nullable=False) _redirect_uris = db.Column(db.Text) default_scope = db.Column(db.Text, default='email address') disallow_grant_type = db.Column(db.String(20)) is_confidential = db.Column(db.Boolean, default=True) @property def user(self): return User.query.get(1) @property def redirect_uris(self): if self._redirect_uris: return self._redirect_uris.split() return [] @property def default_redirect_uri(self): return self.redirect_uris[0] @property def default_scopes(self): if self.default_scope: return self.default_scope.split() return [] @property def allowed_grant_types(self): types = [ 'authorization_code', 'password', 'client_credentials', 'refresh_token', ] if self.disallow_grant_type: types.remove(self.disallow_grant_type) return types class Grant(db.Model): id = db.Column(db.Integer, primary_key=True) user_id = db.Column( db.Integer, db.ForeignKey('user.id', ondelete='CASCADE') ) user = relationship('User') client_id = db.Column( db.String(40), db.ForeignKey('client.client_id', ondelete='CASCADE'), nullable=False, ) client = relationship('Client') code = db.Column(db.String(255), index=True, nullable=False) redirect_uri = db.Column(db.String(255)) scope = db.Column(db.Text) expires = db.Column(db.DateTime) def delete(self): db.session.delete(self) db.session.commit() return self @property def scopes(self): if self.scope: return self.scope.split() return None class Token(db.Model): id = db.Column(db.Integer, primary_key=True) client_id = db.Column( db.String(40), db.ForeignKey('client.client_id', ondelete='CASCADE'), nullable=False, ) user_id = db.Column( db.Integer, db.ForeignKey('user.id', ondelete='CASCADE') ) user = relationship('User') client = relationship('Client') token_type = db.Column(db.String(40)) access_token = db.Column(db.String(255)) refresh_token = db.Column(db.String(255)) expires = db.Column(db.DateTime) scope = db.Column(db.Text) def __init__(self, **kwargs): expires_in = kwargs.pop('expires_in') self.expires = datetime.utcnow() + timedelta(seconds=expires_in) for k, v in kwargs.items(): setattr(self, k, v) @property def scopes(self): if self.scope: return self.scope.split() return [] def delete(self): db.session.delete(self) db.session.commit() return self def current_user(): return g.user def cache_provider(app): oauth = OAuth2Provider(app) bind_sqlalchemy(oauth, db.session, user=User, token=Token, client=Client, current_user=current_user) app.config.update({'OAUTH2_CACHE_TYPE': 'simple'}) bind_cache_grant(app, oauth, current_user) return oauth def sqlalchemy_provider(app): oauth = OAuth2Provider(app) bind_sqlalchemy(oauth, db.session, user=User, token=Token, client=Client, grant=Grant, current_user=current_user) return oauth def default_provider(app): oauth = OAuth2Provider(app) @oauth.clientgetter def get_client(client_id): return Client.query.filter_by(client_id=client_id).first() @oauth.grantgetter def get_grant(client_id, code): return Grant.query.filter_by(client_id=client_id, code=code).first() @oauth.tokengetter def get_token(access_token=None, refresh_token=None): if access_token: return Token.query.filter_by(access_token=access_token).first() if refresh_token: return Token.query.filter_by(refresh_token=refresh_token).first() return None @oauth.grantsetter def set_grant(client_id, code, request, *args, **kwargs): expires = datetime.utcnow() + timedelta(seconds=100) grant = Grant( client_id=client_id, code=code['code'], redirect_uri=request.redirect_uri, scope=' '.join(request.scopes), user_id=g.user.id, expires=expires, ) db.session.add(grant) db.session.commit() @oauth.tokensetter def set_token(token, request, *args, **kwargs): # In real project, a token is unique bound to user and client. # Which means, you don't need to create a token every time. tok = Token(**token) if request.response_type == 'token': tok.user_id = g.user.id else: tok.user_id = request.user.id tok.client_id = request.client.client_id db.session.add(tok) db.session.commit() @oauth.usergetter def get_user(username, password, *args, **kwargs): # This is optional, if you don't need password credential # there is no need to implement this method user = User.query.filter_by(username=username).first() if user and user.check_password(password): return user return None return oauth def create_server(app, oauth=None): if not oauth: oauth = default_provider(app) @app.before_request def load_current_user(): user = User.query.get(1) g.user = user @app.route('/home') def home(): return render_template('home.html') @app.route('/oauth/authorize', methods=['GET', 'POST']) @oauth.authorize_handler def authorize(*args, **kwargs): # NOTICE: for real project, you need to require login if request.method == 'GET': # render a page for user to confirm the authorization return 'confirm page' if request.method == 'HEAD': # if HEAD is supported properly, request parameters like # client_id should be validated the same way as for 'GET' response = make_response('', 200) response.headers['X-Client-ID'] = kwargs.get('client_id') return response confirm = request.form.get('confirm', 'no') return confirm == 'yes' @app.route('/oauth/token', methods=['POST', 'GET']) @oauth.token_handler def access_token(): return {} @app.route('/oauth/revoke', methods=['POST']) @oauth.revoke_handler def revoke_token(): return {} @app.route('/api/email') @oauth.require_oauth('email') def email_api(): oauth = request.oauth return jsonify(email='me@oauth.net', username=oauth.user.username) @app.route('/api/client') @oauth.require_oauth() def client_api(): oauth = request.oauth return jsonify(client=oauth.client.name) @app.route('/api/address/') @oauth.require_oauth('address') def address_api(city): oauth = request.oauth return jsonify(address=city, username=oauth.user.username) @app.route('/api/method', methods=['GET', 'POST', 'PUT', 'DELETE']) @oauth.require_oauth() def method_api(): return jsonify(method=request.method) @oauth.invalid_response def require_oauth_invalid(req): return jsonify(message=req.error_message), 401 return app class TestCase(unittest.TestCase): def setUp(self): app = self.create_app() app.testing = True self._ctx = app.app_context() self._ctx.push() db.init_app(app) db.create_all() self.app = app self.client = app.test_client() self.prepare_data() def tearDown(self): db.drop_all() self._ctx.pop() def prepare_data(self): return True def create_app(self): app = Flask(__name__) app.debug = True app.secret_key = 'testing' app.config.update({ 'SQLALCHEMY_DATABASE_URI': 'sqlite://', 'SQLALCHEMY_TRACK_MODIFICATIONS': False }) return app flask-oauthlib-0.9.5/tests/test_oauth2/test_client_credential.py000066400000000000000000000031551327671251000251260ustar00rootroot00000000000000# coding: utf-8 from .._base import to_base64 from .base import TestCase from .base import create_server, sqlalchemy_provider, cache_provider from .base import db, Client, User class TestDefaultProvider(TestCase): def create_server(self): create_server(self.app) def prepare_data(self): self.create_server() oauth_client = Client( name='ios', client_id='client', client_secret='secret', _redirect_uris='http://localhost/authorized', ) db.session.add(User(username='foo')) db.session.add(oauth_client) db.session.commit() self.oauth_client = oauth_client def test_get_token(self): rv = self.client.post('/oauth/token', data={ 'grant_type': 'client_credentials', 'client_id': self.oauth_client.client_id, 'client_secret': self.oauth_client.client_secret, }) assert b'access_token' in rv.data rv = self.client.post('/oauth/token', data={ 'grant_type': 'client_credentials' }, headers={ 'authorization': 'Basic ' + to_base64( '%s:%s' % ( self.oauth_client.client_id, self.oauth_client.client_secret ) ) }) assert b'access_token' in rv.data class TestSQLAlchemyProvider(TestDefaultProvider): def create_server(self): create_server(self.app, sqlalchemy_provider(self.app)) class TestCacheProvider(TestDefaultProvider): def create_server(self): create_server(self.app, cache_provider(self.app)) flask-oauthlib-0.9.5/tests/test_oauth2/test_code.py000066400000000000000000000122561327671251000223720ustar00rootroot00000000000000# coding: utf-8 from datetime import datetime, timedelta from .._base import to_base64 from .base import TestCase from .base import create_server, sqlalchemy_provider, cache_provider from .base import db, Client, User, Grant class TestDefaultProvider(TestCase): def create_server(self): create_server(self.app) def prepare_data(self): self.create_server() oauth_client = Client( name='ios', client_id='code-client', client_secret='code-secret', _redirect_uris='http://localhost/authorized', ) db.session.add(User(username='foo')) db.session.add(oauth_client) db.session.commit() self.oauth_client = oauth_client self.authorize_url = ( '/oauth/authorize?response_type=code&client_id=%s' ) % oauth_client.client_id def test_get_authorize(self): rv = self.client.get('/oauth/authorize') assert 'client_id' in rv.location rv = self.client.get('/oauth/authorize?client_id=no') assert 'client_id' in rv.location url = '/oauth/authorize?client_id=%s' % self.oauth_client.client_id rv = self.client.get(url) assert 'error' in rv.location rv = self.client.get(self.authorize_url) assert b'confirm' in rv.data def test_post_authorize(self): url = self.authorize_url + '&scope=foo' rv = self.client.post(url, data={'confirm': 'yes'}) assert 'invalid_scope' in rv.location url = self.authorize_url + '&scope=email' rv = self.client.post(url, data={'confirm': 'yes'}) assert 'code' in rv.location url = self.authorize_url + '&scope=' rv = self.client.post(url, data={'confirm': 'yes'}) assert 'error=Scopes+must+be+set' in rv.location def test_invalid_token(self): rv = self.client.get('/oauth/token') assert b'unsupported_grant_type' in rv.data rv = self.client.get('/oauth/token?grant_type=authorization_code') assert b'error' in rv.data assert b'code' in rv.data url = ( '/oauth/token?grant_type=authorization_code' '&code=nothing&client_id=%s' ) % self.oauth_client.client_id rv = self.client.get(url) assert b'invalid_client' in rv.data url += '&client_secret=' + self.oauth_client.client_secret rv = self.client.get(url) assert b'invalid_client' not in rv.data assert rv.status_code == 401 def test_invalid_redirect_uri(self): authorize_url = ( '/oauth/authorize?response_type=code&client_id=code-client' '&redirect_uri=http://localhost:8000/authorized' '&scope=invalid' ) rv = self.client.get(authorize_url) assert 'error=' in rv.location assert 'Mismatching+redirect+URI' in rv.location def test_get_token(self): expires = datetime.utcnow() + timedelta(seconds=100) grant = Grant( user_id=1, client_id=self.oauth_client.client_id, scope='email', redirect_uri='http://localhost/authorized', code='test-get-token', expires=expires, ) db.session.add(grant) db.session.commit() url = '/oauth/token?grant_type=authorization_code&code=test-get-token' rv = self.client.get( url + '&client_id=%s' % (self.oauth_client.client_id) ) assert b'invalid_client' in rv.data rv = self.client.get( url + '&client_id=%s&client_secret=%s' % ( self.oauth_client.client_id, self.oauth_client.client_secret ) ) assert b'access_token' in rv.data grant = Grant( user_id=1, client_id=self.oauth_client.client_id, scope='email', redirect_uri='http://localhost/authorized', code='test-get-token', expires=expires, ) db.session.add(grant) db.session.commit() rv = self.client.get(url, headers={ 'authorization': 'Basic ' + to_base64( '%s:%s' % ( self.oauth_client.client_id, self.oauth_client.client_secret ) ) }) assert b'access_token' in rv.data class TestSQLAlchemyProvider(TestDefaultProvider): def create_server(self): create_server(self.app, sqlalchemy_provider(self.app)) class TestCacheProvider(TestDefaultProvider): def create_server(self): create_server(self.app, cache_provider(self.app)) def test_get_token(self): url = self.authorize_url + '&scope=email' rv = self.client.post(url, data={'confirm': 'yes'}) assert 'code' in rv.location code = rv.location.split('code=')[1] url = ( '/oauth/token?grant_type=authorization_code' '&code=%s&client_id=%s' ) % (code, self.oauth_client.client_id) rv = self.client.get(url) assert b'invalid_client' in rv.data url += '&client_secret=' + self.oauth_client.client_secret rv = self.client.get(url) assert b'access_token' in rv.data flask-oauthlib-0.9.5/tests/test_oauth2/test_implicit.py000066400000000000000000000023701327671251000232660ustar00rootroot00000000000000# coding: utf-8 from .base import TestCase from .base import create_server, sqlalchemy_provider, cache_provider from .base import db, Client, User class TestDefaultProvider(TestCase): def create_server(self): create_server(self.app) def prepare_data(self): self.create_server() oauth_client = Client( name='ios', client_id='imp-client', client_secret='imp-secret', _redirect_uris='http://localhost/authorized', ) db.session.add(User(username='foo')) db.session.add(oauth_client) db.session.commit() self.oauth_client = oauth_client def test_implicit(self): rv = self.client.post('/oauth/authorize', data={ 'response_type': 'token', 'confirm': 'yes', 'scope': 'email', 'client_id': self.oauth_client.client_id, 'client_secret': self.oauth_client.client_secret, }) assert 'access_token' in rv.location class TestSQLAlchemyProvider(TestDefaultProvider): def create_server(self): create_server(self.app, sqlalchemy_provider(self.app)) class TestCacheProvider(TestDefaultProvider): def create_server(self): create_server(self.app, cache_provider(self.app)) flask-oauthlib-0.9.5/tests/test_oauth2/test_password.py000066400000000000000000000060411327671251000233150ustar00rootroot00000000000000# coding: utf-8 from .base import TestCase from .base import create_server, sqlalchemy_provider, cache_provider from .base import db, Client, User class TestDefaultProvider(TestCase): def create_server(self): create_server(self.app) def prepare_data(self): self.create_server() oauth_client = Client( name='ios', client_id='pass-client', client_secret='pass-secret', _redirect_uris='http://localhost/authorized', ) db.session.add(User(username='foo')) db.session.add(oauth_client) db.session.commit() self.oauth_client = oauth_client def test_invalid_username(self): rv = self.client.post('/oauth/token', data={ 'grant_type': 'password', 'username': 'notfound', 'password': 'right', 'client_id': self.oauth_client.client_id, 'client_secret': self.oauth_client.client_secret, }) assert b'error' in rv.data def test_invalid_password(self): rv = self.client.post('/oauth/token', data={ 'grant_type': 'password', 'username': 'foo', 'password': 'wrong', 'client_id': self.oauth_client.client_id, 'client_secret': self.oauth_client.client_secret, }) assert b'error' in rv.data def test_missing_client_secret(self): rv = self.client.post('/oauth/token', data={ 'grant_type': 'password', 'username': 'foo', 'password': 'wrong', 'client_id': self.oauth_client.client_id, }) assert b'error' in rv.data def test_get_token(self): rv = self.client.post('/oauth/token', data={ 'grant_type': 'password', 'username': 'foo', 'password': 'right', 'client_id': self.oauth_client.client_id, 'client_secret': self.oauth_client.client_secret, }) assert b'access_token' in rv.data # in Authorization auth = 'cGFzcy1jbGllbnQ6cGFzcy1zZWNyZXQ=' rv = self.client.post('/oauth/token', data={ 'grant_type': 'password', 'username': 'foo', 'password': 'right', }, headers={'Authorization': 'Basic %s' % auth}) assert b'access_token' in rv.data def test_disallow_grant_type(self): self.oauth_client.disallow_grant_type = 'password' db.session.add(self.oauth_client) db.session.commit() rv = self.client.post('/oauth/token', data={ 'grant_type': 'password', 'username': 'foo', 'password': 'right', 'client_id': self.oauth_client.client_id, 'client_secret': self.oauth_client.client_secret, }) assert b'error' in rv.data class TestSQLAlchemyProvider(TestDefaultProvider): def create_server(self): create_server(self.app, sqlalchemy_provider(self.app)) class TestCacheProvider(TestDefaultProvider): def create_server(self): create_server(self.app, cache_provider(self.app)) flask-oauthlib-0.9.5/tests/test_oauth2/test_refresh.py000066400000000000000000000066521327671251000231210ustar00rootroot00000000000000# coding: utf-8 import json from .._base import to_base64, to_unicode as u from .base import TestCase from .base import create_server, sqlalchemy_provider, cache_provider from .base import db, Client, User, Token class TestDefaultProvider(TestCase): def create_server(self): create_server(self.app) def prepare_data(self): self.create_server() normal_client = Client( name='normal_client', client_id='normal_client', client_secret='normal_secret', is_confidential=False, _redirect_uris='http://localhost/authorized', ) confidential_client = Client( name='confidential_client', client_id='confidential_client', client_secret='confidential_secret', is_confidential=True, _redirect_uris='http://localhost/authorized', ) db.session.add(User(username='foo')) db.session.add(normal_client) db.session.add(confidential_client) db.session.commit() self.normal_client = normal_client self.confidential_client = confidential_client def test_normal_get_token(self): user = User.query.first() token = Token( user_id=user.id, client_id=self.normal_client.client_id, access_token='foo', refresh_token='bar', expires_in=1000, ) db.session.add(token) db.session.commit() rv = self.client.post('/oauth/token', data={ 'grant_type': 'refresh_token', 'refresh_token': token.refresh_token, 'client_id': self.normal_client.client_id, }) assert b'access_token' in rv.data def test_confidential_get_token(self): user = User.query.first() token = Token( user_id=user.id, client_id=self.confidential_client.client_id, access_token='foo', refresh_token='bar', expires_in=1000, ) db.session.add(token) db.session.commit() rv = self.client.post('/oauth/token', data={ 'grant_type': 'refresh_token', 'refresh_token': token.refresh_token, 'client_id': self.confidential_client.client_id, }) assert b'error' in rv.data rv = self.client.post('/oauth/token', data={ 'grant_type': 'refresh_token', 'refresh_token': token.refresh_token, 'client_id': self.confidential_client.client_id, 'client_secret': self.confidential_client.client_secret, }) assert b'access_token' in rv.data token.refresh_token = json.loads(u(rv.data))['refresh_token'] rv = self.client.post('/oauth/token', data={ 'grant_type': 'refresh_token', 'refresh_token': token.refresh_token, }, headers={ 'authorization': 'Basic ' + to_base64( '%s:%s' % ( self.confidential_client.client_id, self.confidential_client.client_secret ) ) }) assert b'access_token' in rv.data class TestSQLAlchemyProvider(TestDefaultProvider): def create_server(self): create_server(self.app, sqlalchemy_provider(self.app)) class TestCacheProvider(TestDefaultProvider): def create_server(self): create_server(self.app, cache_provider(self.app)) flask-oauthlib-0.9.5/tests/test_utils.py000066400000000000000000000030661327671251000203560ustar00rootroot00000000000000import unittest import wsgiref.util from contextlib import contextmanager import mock import werkzeug.wrappers from flask_oauthlib.utils import extract_params from oauthlib.common import Request @contextmanager def set_flask_request(wsgi_environ): """ Test helper context manager that mocks the flask request global I didn't need the whole request context just to test the functions in helpers and I wanted to be able to set the raw WSGI environment """ environ = {} environ.update(wsgi_environ) wsgiref.util.setup_testing_defaults(environ) r = werkzeug.wrappers.Request(environ) with mock.patch.dict(extract_params.__globals__, {'request': r}): yield class UtilsTestSuite(unittest.TestCase): def test_extract_params(self): with set_flask_request({'QUERY_STRING': 'test=foo&foo=bar'}): uri, http_method, body, headers = extract_params() self.assertEquals(uri, 'http://127.0.0.1/?test=foo&foo=bar') self.assertEquals(http_method, 'GET') self.assertEquals(body, {}) self.assertEquals(headers, {'Host': '127.0.0.1'}) def test_extract_params_with_urlencoded_json(self): wsgi_environ = { 'QUERY_STRING': 'state=%7B%22t%22%3A%22a%22%2C%22i%22%3A%22l%22%7D' } with set_flask_request(wsgi_environ): uri, http_method, body, headers = extract_params() # Request constructor will try to urldecode the querystring, make # sure this doesn't fail. Request(uri, http_method, body, headers) flask-oauthlib-0.9.5/tox.ini000066400000000000000000000001701327671251000157470ustar00rootroot00000000000000[tox] envlist = py27,py33,py34,py35,py36,pypy [testenv] deps = nose -rrequirements.txt commands = nosetests -s