├── .gitignore
├── .gitmodules
├── LICENSE
├── README.rst
├── docs
├── Makefile
├── conf.py
├── index.rst
└── make.bat
├── example
├── facebook.py
├── google.py
├── static
│ ├── openid.png
│ ├── sign-in.png
│ └── style.css
├── templates
│ ├── index.html
│ └── layout.html
└── tweet.py
├── flask_rauth.py
├── setup.cfg
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.pyc
3 | *.pyo
4 | docs/_build
5 | dist
6 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "docs/_themes"]
2 | path = docs/_themes
3 | url = git://github.com/mitsuhiko/flask-sphinx-themes.git
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Flask-Rauth is a fork of Armin Ronacher's Flask-OAuth.
2 | Copyright (c) 2010 by Armin Ronacher.
3 | Copyright (c) 2012 by Joel Verhagen.
4 |
5 | Some rights reserved.
6 |
7 | Redistribution and use in source and binary forms, with or without
8 | modification, are permitted provided that the following conditions are
9 | met:
10 |
11 | * Redistributions of source code must retain the above copyright
12 | notice, this list of conditions and the following disclaimer.
13 |
14 | * Redistributions in binary form must reproduce the above
15 | copyright notice, this list of conditions and the following
16 | disclaimer in the documentation and/or other materials provided
17 | with the distribution.
18 |
19 | * The names of the contributors may not be used to endorse or
20 | promote products derived from this software without specific
21 | prior written permission.
22 |
23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
30 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
31 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
33 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | Flask-Rauth
2 | ===========
3 |
4 | Adds OAuth 1.0/a, 2.0, and Ofly consumer support for `Flask`__, using the
5 | `rauth`__ library.
6 |
7 | __ http://flask.pocoo.org/
8 | __ http://rauth.readthedocs.org/en/latest/
9 |
10 | Flask-Rauth is a fork of Armin Ronacher's `Flask-OAuth`__.
11 |
12 | __ https://github.com/mitsuhiko/flask-oauth
13 |
14 | Documentation
15 | ~~~~~~~~~~~~~
16 |
17 | Current documentation is available at the following URL:
18 |
19 | http://flask-rauth.readthedocs.org/en/latest/
20 |
21 | Documentation hosting is provided by `Read the Docs
22 | `_.
23 |
24 | Examples
25 | ~~~~~~~~~~~~~~
26 |
27 | Some people learn better by example, that's why I've provided the following
28 | examples for your edification:
29 |
30 | https://github.com/joelverhagen/flask-rauth/tree/master/example
31 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = _build
9 |
10 | # Internal variables.
11 | PAPEROPT_a4 = -D latex_paper_size=a4
12 | PAPEROPT_letter = -D latex_paper_size=letter
13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
14 | # the i18n builder cannot share the environment and doctrees with the others
15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
16 |
17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
18 |
19 | help:
20 | @echo "Please use \`make ' where is one of"
21 | @echo " html to make standalone HTML files"
22 | @echo " dirhtml to make HTML files named index.html in directories"
23 | @echo " singlehtml to make a single large HTML file"
24 | @echo " pickle to make pickle files"
25 | @echo " json to make JSON files"
26 | @echo " htmlhelp to make HTML files and a HTML help project"
27 | @echo " qthelp to make HTML files and a qthelp project"
28 | @echo " devhelp to make HTML files and a Devhelp project"
29 | @echo " epub to make an epub"
30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
31 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
32 | @echo " text to make text files"
33 | @echo " man to make manual pages"
34 | @echo " texinfo to make Texinfo files"
35 | @echo " info to make Texinfo files and run them through makeinfo"
36 | @echo " gettext to make PO message catalogs"
37 | @echo " changes to make an overview of all changed/added/deprecated items"
38 | @echo " linkcheck to check all external links for integrity"
39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
40 |
41 | clean:
42 | -rm -rf $(BUILDDIR)/*
43 |
44 | html:
45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
46 | @echo
47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
48 |
49 | dirhtml:
50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
51 | @echo
52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
53 |
54 | singlehtml:
55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
56 | @echo
57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
58 |
59 | pickle:
60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
61 | @echo
62 | @echo "Build finished; now you can process the pickle files."
63 |
64 | json:
65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
66 | @echo
67 | @echo "Build finished; now you can process the JSON files."
68 |
69 | htmlhelp:
70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
71 | @echo
72 | @echo "Build finished; now you can run HTML Help Workshop with the" \
73 | ".hhp project file in $(BUILDDIR)/htmlhelp."
74 |
75 | qthelp:
76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
77 | @echo
78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Flask-Rauth.qhcp"
81 | @echo "To view the help file:"
82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Flask-Rauth.qhc"
83 |
84 | devhelp:
85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
86 | @echo
87 | @echo "Build finished."
88 | @echo "To view the help file:"
89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Flask-Rauth"
90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Flask-Rauth"
91 | @echo "# devhelp"
92 |
93 | epub:
94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
95 | @echo
96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
97 |
98 | latex:
99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
100 | @echo
101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
103 | "(use \`make latexpdf' here to do that automatically)."
104 |
105 | latexpdf:
106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
107 | @echo "Running LaTeX files through pdflatex..."
108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
110 |
111 | text:
112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
113 | @echo
114 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
115 |
116 | man:
117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
118 | @echo
119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
120 |
121 | texinfo:
122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
123 | @echo
124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
125 | @echo "Run \`make' in that directory to run these through makeinfo" \
126 | "(use \`make info' here to do that automatically)."
127 |
128 | info:
129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
130 | @echo "Running Texinfo files through makeinfo..."
131 | make -C $(BUILDDIR)/texinfo info
132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
133 |
134 | gettext:
135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
136 | @echo
137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
138 |
139 | changes:
140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
141 | @echo
142 | @echo "The overview file is in $(BUILDDIR)/changes."
143 |
144 | linkcheck:
145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
146 | @echo
147 | @echo "Link check complete; look for any errors in the above output " \
148 | "or in $(BUILDDIR)/linkcheck/output.txt."
149 |
150 | doctest:
151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
152 | @echo "Testing of doctests in the sources finished, look at the " \
153 | "results in $(BUILDDIR)/doctest/output.txt."
154 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # Flask-Rauth documentation build configuration file, created by
4 | # sphinx-quickstart on Tue Aug 21 20:35:08 2012.
5 | #
6 | # This file is execfile()d with the current directory set to its containing dir.
7 | #
8 | # Note that not all possible configuration values are present in this
9 | # autogenerated file.
10 | #
11 | # All configuration values have a default; values that are commented out
12 | # serve to show the default.
13 |
14 | import sys, os
15 |
16 | # If extensions (or modules to document with autodoc) are in another directory,
17 | # add these directories to sys.path here. If the directory is relative to the
18 | # documentation root, use os.path.abspath to make it absolute, like shown here.
19 | #sys.path.insert(0, os.path.abspath('.'))
20 | sys.path.append(os.path.join(os.path.dirname(__file__), "_themes"))
21 |
22 | # -- General configuration -----------------------------------------------------
23 |
24 | # If your documentation needs a minimal Sphinx version, state it here.
25 | #needs_sphinx = '1.0'
26 |
27 | # Add any Sphinx extension module names here, as strings. They can be extensions
28 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
29 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode']
30 |
31 | # Add any paths that contain templates here, relative to this directory.
32 | templates_path = ['_templates']
33 |
34 | # The suffix of source filenames.
35 | source_suffix = '.rst'
36 |
37 | # The encoding of source files.
38 | #source_encoding = 'utf-8-sig'
39 |
40 | # The master toctree document.
41 | master_doc = 'index'
42 |
43 | # General information about the project.
44 | project = u'Flask-Rauth'
45 | copyright = u'2012, Joel Verhagen'
46 |
47 | # The version info for the project you're documenting, acts as replacement for
48 | # |version| and |release|, also used in various other places throughout the
49 | # built documents.
50 | #
51 | # The short X.Y version.
52 | version = '0.1'
53 | # The full version, including alpha/beta/rc tags.
54 | release = '0.1'
55 |
56 | # The language for content autogenerated by Sphinx. Refer to documentation
57 | # for a list of supported languages.
58 | #language = None
59 |
60 | # There are two options for replacing |today|: either, you set today to some
61 | # non-false value, then it is used:
62 | #today = ''
63 | # Else, today_fmt is used as the format for a strftime call.
64 | #today_fmt = '%B %d, %Y'
65 |
66 | # List of patterns, relative to source directory, that match files and
67 | # directories to ignore when looking for source files.
68 | exclude_patterns = ['_build']
69 |
70 | # The reST default role (used for this markup: `text`) to use for all documents.
71 | #default_role = None
72 |
73 | # If true, '()' will be appended to :func: etc. cross-reference text.
74 | #add_function_parentheses = True
75 |
76 | # If true, the current module name will be prepended to all description
77 | # unit titles (such as .. function::).
78 | #add_module_names = True
79 |
80 | # If true, sectionauthor and moduleauthor directives will be shown in the
81 | # output. They are ignored by default.
82 | #show_authors = False
83 |
84 | # The name of the Pygments (syntax highlighting) style to use.
85 | #pygments_style = 'sphinx'
86 |
87 | # A list of ignored prefixes for module index sorting.
88 | #modindex_common_prefix = []
89 |
90 | # -- Options for HTML output ---------------------------------------------------
91 |
92 | # The theme to use for HTML and HTML Help pages. See the documentation for
93 | # a list of builtin themes.
94 | html_theme = 'flask_small'
95 | html_theme_options = {
96 | 'github_fork': 'joelverhagen/flask-rauth',
97 | 'index_logo': False
98 | }
99 |
100 | # Theme options are theme-specific and customize the look and feel of a theme
101 | # further. For a list of options available for each theme, see the
102 | # documentation.
103 | #html_theme_options = {}
104 |
105 | # Add any paths that contain custom themes here, relative to this directory.
106 | html_theme_path = ['_themes']
107 |
108 | # The name for this set of Sphinx documents. If None, it defaults to
109 | # " v documentation".
110 | #html_title = None
111 |
112 | # A shorter title for the navigation bar. Default is the same as html_title.
113 | #html_short_title = None
114 |
115 | # The name of an image file (relative to this directory) to place at the top
116 | # of the sidebar.
117 | #html_logo = None
118 |
119 | # The name of an image file (within the static path) to use as favicon of the
120 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
121 | # pixels large.
122 | #html_favicon = None
123 |
124 | # Add any paths that contain custom static files (such as style sheets) here,
125 | # relative to this directory. They are copied after the builtin static files,
126 | # so a file named "default.css" will overwrite the builtin "default.css".
127 | #html_static_path = ['_static']
128 |
129 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
130 | # using the given strftime format.
131 | #html_last_updated_fmt = '%b %d, %Y'
132 |
133 | # If true, SmartyPants will be used to convert quotes and dashes to
134 | # typographically correct entities.
135 | #html_use_smartypants = True
136 |
137 | # Custom sidebar templates, maps document names to template names.
138 | #html_sidebars = {}
139 |
140 | # Additional templates that should be rendered to pages, maps page names to
141 | # template names.
142 | #html_additional_pages = {}
143 |
144 | # If false, no module index is generated.
145 | #html_domain_indices = True
146 |
147 | # If false, no index is generated.
148 | #html_use_index = True
149 |
150 | # If true, the index is split into individual pages for each letter.
151 | #html_split_index = False
152 |
153 | # If true, links to the reST sources are added to the pages.
154 | #html_show_sourcelink = True
155 |
156 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
157 | #html_show_sphinx = True
158 |
159 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
160 | #html_show_copyright = True
161 |
162 | # If true, an OpenSearch description file will be output, and all pages will
163 | # contain a tag referring to it. The value of this option must be the
164 | # base URL from which the finished HTML is served.
165 | #html_use_opensearch = ''
166 |
167 | # This is the file name suffix for HTML files (e.g. ".xhtml").
168 | #html_file_suffix = None
169 |
170 | # Output file base name for HTML help builder.
171 | htmlhelp_basename = 'Flask-Rauthdoc'
172 |
173 |
174 | # -- Options for LaTeX output --------------------------------------------------
175 |
176 | latex_elements = {
177 | # The paper size ('letterpaper' or 'a4paper').
178 | #'papersize': 'letterpaper',
179 |
180 | # The font size ('10pt', '11pt' or '12pt').
181 | #'pointsize': '10pt',
182 |
183 | # Additional stuff for the LaTeX preamble.
184 | #'preamble': '',
185 | }
186 |
187 | # Grouping the document tree into LaTeX files. List of tuples
188 | # (source start file, target name, title, author, documentclass [howto/manual]).
189 | latex_documents = [
190 | ('index', 'Flask-Rauth.tex', u'Flask-Rauth Documentation',
191 | u'Joel Verhagen', 'manual'),
192 | ]
193 |
194 | # The name of an image file (relative to this directory) to place at the top of
195 | # the title page.
196 | #latex_logo = None
197 |
198 | # For "manual" documents, if this is true, then toplevel headings are parts,
199 | # not chapters.
200 | #latex_use_parts = False
201 |
202 | # If true, show page references after internal links.
203 | #latex_show_pagerefs = False
204 |
205 | # If true, show URL addresses after external links.
206 | #latex_show_urls = False
207 |
208 | # Documents to append as an appendix to all manuals.
209 | #latex_appendices = []
210 |
211 | # If false, no module index is generated.
212 | #latex_domain_indices = True
213 |
214 |
215 | # -- Options for manual page output --------------------------------------------
216 |
217 | # One entry per manual page. List of tuples
218 | # (source start file, name, description, authors, manual section).
219 | man_pages = [
220 | ('index', 'flask-rauth', u'Flask-Rauth Documentation',
221 | [u'Joel Verhagen'], 1)
222 | ]
223 |
224 | # If true, show URL addresses after external links.
225 | #man_show_urls = False
226 |
227 |
228 | # -- Options for Texinfo output ------------------------------------------------
229 |
230 | # Grouping the document tree into Texinfo files. List of tuples
231 | # (source start file, target name, title, author,
232 | # dir menu entry, description, category)
233 | texinfo_documents = [
234 | ('index', 'Flask-Rauth', u'Flask-Rauth Documentation',
235 | u'Joel Verhagen', 'Flask-Rauth', 'One line description of project.',
236 | 'Miscellaneous'),
237 | ]
238 |
239 | # Documents to append as an appendix to all manuals.
240 | #texinfo_appendices = []
241 |
242 | # If false, no module index is generated.
243 | #texinfo_domain_indices = True
244 |
245 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
246 | #texinfo_show_urls = 'footnote'
247 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | Flask-Rauth
2 | ===========
3 |
4 | .. currentmodule:: flask.ext.rauth
5 |
6 | Adds OAuth 1.0/a, 2.0, and Ofly consumer support for `Flask`__, using the
7 | `rauth`__ library.
8 |
9 | __ http://flask.pocoo.org/
10 | __ http://rauth.readthedocs.org/en/latest/
11 |
12 | Flask-Rauth is a fork of Armin Ronacher's `Flask-OAuth`__.
13 |
14 | __ https://github.com/mitsuhiko/flask-oauth
15 |
16 | .. contents::
17 | :local:
18 | :backlinks: none
19 |
20 | Introduction
21 | ------------
22 |
23 | Flask-Rauth is a Flask extensions that allows you to easily interact with OAuth
24 | 2.0, OAuth 1.0a, and Ofly enabled applications. Please note that Flask-Rauth is
25 | meant to only provide *consumer* support. This means that Flask-Rauth will
26 | allow users on your Flask website to sign in to external web services (i.e. the
27 | `Twitter API `_, `Facebook Graph API
28 | `_, `GitHub
29 | `_, etc).
30 |
31 | Once a user has authenticated with the external service, your server back-end
32 | execute calls on the external API on behalf of the user via a secure token
33 | process. This means that your application never has to deal with securing and
34 | transferring Twitter password, for example. *This is a good thing!*
35 |
36 | As mentioned before, Flask-Rauth supports the following protocols as a
37 | consumer:
38 |
39 | - OAuth 2.0 (`2.0 spec `_)
40 | - OAuth 1.0a (`1.0a spec `_)
41 | - Ofly (i.e.
42 | `Shutterfly `_)
43 |
44 | Tutorial
45 | --------
46 |
47 | This tutorial should be able to help you get started with using OAuth 2.0 or
48 | OAuth 1.0a with your Flask application.
49 |
50 | Sign up with the external service
51 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
52 |
53 | **Note:** you can skip this section if you already have a consumer_key and
54 | consumer_secret.
55 |
56 | Many social networking websites have OAuth 1.0a or 2.0 capabilities. Find
57 | the developer documentation for an OAuth endpoint, and follow the documentation
58 | to get two important pieces of information: a **consumer key** and a **consumer
59 | secret**. You, as the web developer, are the consumer. The consumer key and
60 | consumer secret uniquely identify your website when you interact with the
61 | external web service.
62 |
63 | .. _consumer-label:
64 |
65 | Get a consumer key and secret
66 | '''''''''''''''''''''''''''''
67 |
68 | To get the consumer key and consumer secret, you normally need to create an
69 | "app" entry using some developer interface. This normally includes providing
70 | a name, description, website and other fluffy information so that when your
71 | users authenticate into the external web service they see a message like
72 | "Joel's Awesome Flask App wants access to your Twitter information."
73 |
74 | For the lazy, here's a list of a few OAuth web services and their
75 | interfaces to get a consumer key and consumer secret.
76 |
77 | **Note:** you will need to log in with credentials for each respective web
78 | service for you to get a consumer key and consumer secret.
79 |
80 | - Facebook, `Apps`__
81 | - GitHub, `Developer applications`__
82 | - Google, `APIs Console`__
83 | - LinkedIn, `List of Applications`__
84 | - Twitter, `Apps`__
85 |
86 | __ https://developers.facebook.com/apps
87 | __ https://github.com/settings/applications
88 | __ https://code.google.com/apis/console/
89 | __ https://www.linkedin.com/secure/developer
90 | __ https://dev.twitter.com/apps
91 |
92 | .. _oauth2-note:
93 |
94 | OAuth 2.0 Note
95 | ''''''''''''''
96 |
97 | Most OAuth 2.0 web services not only require you to specify a name,
98 | description, etc. to get a consumer key and consumer secret, but they
99 | also require you to specify one or more static ``redirect_uri`` values. These
100 | values form a white list of URLs that a user can be redirected to after
101 | authentication. The value you set should match the :ref:`callback URL
102 | `.
103 |
104 | Determine the protocol
105 | ~~~~~~~~~~~~~~~~~~~~~~
106 |
107 | Depending on the external web service you are using, you will need to use a
108 | different Flask-Rauth class. In the developer documentation, there will be some
109 | indication whether it uses OAuth 1.0a or OAuth 2.0. Use the table below to map
110 | the protocol to a Flask-Rauth service class.
111 |
112 | +------------+----------------+
113 | | Protocol | Class |
114 | +============+================+
115 | | OAuth 1.0a | `RauthOAuth1`_ |
116 | +------------+----------------+
117 | | OAuth 2.0 | `RauthOAuth2`_ |
118 | +------------+----------------+
119 |
120 | Enough with the talk, let's look at some code!
121 |
122 | Initialize the service object
123 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
124 |
125 | To get started, you will need to initialize a Flask-Rauth
126 | :ref:`service object `.
127 |
128 | .. _oauth2-init:
129 |
130 | OAuth 2.0
131 | '''''''''
132 |
133 | Initialize a `RauthOAuth2`_ object.
134 |
135 | .. code-block:: python
136 |
137 | github = RauthOAuth2(
138 | name='github',
139 | base_url='https://api.github.com/',
140 | authorize_url='https://github.com/login/oauth/authorize',
141 | access_token_url='https://github.com/login/oauth/access_token'
142 | )
143 |
144 | The `authorize_url` and `access_token_url` parameters are
145 | specific to the endpoint you are working with.
146 |
147 | See `Both Protocols`_ for information about the other keys.
148 |
149 | OAuth 1.0a
150 | ''''''''''
151 |
152 | Initialize a `RauthOAuth1`_ object:
153 |
154 | .. code-block:: python
155 |
156 | twitter = RauthOAuth1(
157 | name='twitter',
158 | base_url='https://api.twitter.com/1/',
159 | request_token_url='https://api.twitter.com/oauth/request_token',
160 | authorize_url='https://api.twitter.com/oauth/authorize',
161 | access_token_url='https://api.twitter.com/oauth/access_token'
162 | )
163 |
164 | The `request_token_url`, `authorize_url`, and `access_token_url`
165 | parameters are specific to the endpoint you are working with. Notice the
166 | additional `request_token_url` parameter, compared to :ref:`OAuth 2.0
167 | `.
168 |
169 | See `Both Protocols`_ for information about the other keys.
170 |
171 | Both Protocols
172 | ''''''''''''''
173 |
174 | The `base_url` is **optional**, but can be provided so that
175 | :ref:`making requests ` is a bit easier. The
176 | `name` parameter is very important! This value will be used to
177 | determine the Flask configuration keys that contain the associated **consumer
178 | key** and **private key**.
179 |
180 | Set the consumer key and secret
181 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
182 |
183 | Assuming you've gotten a :ref:`consumer key and secret ` from
184 | the web service you are working with, you can provide them to your service
185 | object in a couple different ways.
186 |
187 | In your app config
188 | ''''''''''''''''''
189 |
190 | **This is the recommended method**, since it works very well when using an
191 | application factory to generate your Flask application object or when you may
192 | have multiple sets of consumer keys and secrets and you want to keep them all
193 | in one place. One use-case is if you would like a separate consumer key and
194 | secret for a development vs. testing vs. production environment.
195 |
196 | .. code-block:: python
197 | :emphasize-lines: 6-7, 9-10
198 |
199 | # create your application object
200 | # ...
201 |
202 | # set config values
203 | app.config.update(
204 | GITHUB_CONSUMER_KEY='',
205 | GITHUB_CONSUMER_SECRET='',
206 |
207 | TWITTER_CONSUMER_KEY='',
208 | TWITTER_CONSUMER_SECRET='',
209 |
210 | # other keys
211 | SECRET_KEY='just a secret key, to confound the bad guys',
212 | DEBUG=True
213 | # ...
214 | )
215 |
216 | This setup should beg the following question: *how does Flask-Rauth know about
217 | these keys and secrets?*
218 |
219 | Well, you can register the service object that you initialized above
220 | as an extension with your app object, like this:
221 |
222 | .. code-block:: python
223 |
224 | # github is the RauthOAuth2 object, from above
225 | github.init_app(app)
226 |
227 | Or, you can simply let Flask-Rauth use Flask's super-useful ``current_app`` to
228 | get the currently active Flask application object, and look for the consumer
229 | key and secret in its configuration.
230 |
231 | Whether or not you call :func:`init_app`, the `name` parameter you pass to the
232 | service object's constructor is extremely important. When Flask-Rauth is
233 | looking for a consumer key or consumer secret, the name is upper cased (using
234 | ``name.upper()``) and appended with ``_CONSUMER_KEY`` and ``_CONSUMER_SECRET``,
235 | respectively.
236 |
237 | When initializing the service object
238 | ''''''''''''''''''''''''''''''''''''
239 |
240 | Alternatively, you can pass the consumer key and consumer secret when
241 | initializing your service object.
242 |
243 | .. code-block:: python
244 | :emphasize-lines: 6-7, 16-17
245 |
246 | github = RauthOAuth2(
247 | name='github',
248 | base_url='https://api.github.com/',
249 | authorize_url='https://github.com/login/oauth/authorize',
250 | access_token_url='https://github.com/login/oauth/access_token',
251 | consumer_key='',
252 | consumer_secret=''
253 | )
254 |
255 | twitter = RauthOAuth1(
256 | name='twitter',
257 | base_url='https://api.twitter.com/1/',
258 | request_token_url='https://api.twitter.com/oauth/request_token',
259 | authorize_url='https://api.twitter.com/oauth/authorize'
260 | access_token_url='https://api.twitter.com/oauth/access_token',
261 | consumer_key='',
262 | consumer_secret=''
263 | )
264 |
265 | This works just fine for applications that never need to worry about different
266 | keys for different running environments. However, :ref:`as mentioned above
267 | `, OAuth 2.0 requires you to predefine an absolute URL of where
268 | users can be redirected after authentication. If you have a test environment
269 | and production environment with different callback URLs (i.e.
270 | `http://test.example.com/github/authorized` and
271 | `http://www.example.com/github/authorized`), you may be forced to use a
272 | different consumer key and secret for each environment.
273 |
274 | .. _callback-label:
275 |
276 | Redirect the user
277 | ~~~~~~~~~~~~~~~~~
278 |
279 | Now that you've initialized everything, it's time to hook the service object
280 | up. Both OAuth 2.0 and OAuth 1.0a have a step where the user is redirected from
281 | the consumer's website (your Flask web app) to the external web service (i.e.
282 | GitHub, Twitter, etc) for user authentication. Not only does the user log in on
283 | the external website, but they also choose whether your app is allowed to
284 | access their information.
285 |
286 | To kick off the authentication process, call the :func:`authorize` method on
287 | your service object, which will return a Flask `redirect` response.
288 |
289 | .. code-block:: python
290 | :emphasize-lines: 9
291 |
292 | # initialize the Flask application object
293 | # ...
294 |
295 | # initialize the GitHub OAuth 2.0 service
296 | # ...
297 |
298 | @app.route('/redirect')
299 | def redirect():
300 | return github.authorize(callback=url_for('authorized', _external=True))
301 |
302 | @app.route('/authorized')
303 | @github.authorized_handler()
304 | def authorized(...):
305 | # handle authorization
306 |
307 | .. _making-request-label:
308 |
309 | For both OAuth 1.0a and 2.0, the `callback` parameter is required. This tells
310 | OAuth server where to redirect the user after they have authenticated. If you
311 | use :func:`url_for` to generate the URL, make sure to generate an absolute URL
312 | using ``_external=True``.
313 |
314 | For OAuth 2.0, this `callback` parameter is mapped to the `redirect_uri` passed
315 | to the the external web service.
316 |
317 | Handle the authorization response
318 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
319 |
320 | So far, everything is (hopefully) pretty straightforward. When the user comes
321 | back from the external web service (after authorization), things get a bit more
322 | complex.
323 |
324 | In the previous step, you set the `callback` parameter for :func:`authorize` to
325 | be the absolute URL to a another route (in the example above, the route was
326 | ``/authorized``). This route will be hit by the user after authentication.
327 |
328 | This is a special route marked by the :func:`authorized_handler` decorator.
329 | This route will receive two parameters upon successful authorization:
330 | a special `RauthResponse` object and the token required for making requests on
331 | behalf of the authenticated user. If the first parameter is `None` or
332 | ``access_denied``, the authorization step failed (see `Handle if the user
333 | denies`_).
334 |
335 | When you declare your authorized handler, the top of it should look a lot like
336 | this:
337 |
338 | .. code-block:: python
339 |
340 | @app.route('/authorized')
341 | @github.authorized_handler()
342 | def authorized(response, access_token):
343 | # ...
344 |
345 | As you can see, you're expecting the two parameters that are mentioned above.
346 |
347 | Suppose your endpoint insists that a `GET` request must be issued when fetching
348 | the access token. Well, simply pass a `method` keyword argument to your
349 | :func:`authorized_handler` decorator.
350 |
351 | .. code-block:: python
352 |
353 | @app.route('/authorized')
354 | @acme.authorized_handler(method='GET')
355 | def authorized(response, access_token):
356 | # ...
357 |
358 | By default, a `POST` request is used.
359 |
360 | OAuth 2.0
361 | '''''''''
362 |
363 | The second parameter will be an `access_token`. This is a single secret string
364 | used as a password, specific to your application, to make requests on behalf of
365 | your user. The string can have any length, so if you're storing it in a
366 | database, use a `Text` data type (unless you're very sure of the size, in which
367 | case you can get by with a CHAR/VARCHAR).
368 |
369 | If you're working with SQLAlchemy and declarative models (i.e.
370 | `Flask-SQLAlchemy`__), you're code might look a bit like this:
371 |
372 | __ http://packages.python.org/Flask-SQLAlchemy/
373 |
374 | .. code-block:: python
375 |
376 | @app.route('/authorized')
377 | @github.authorized_handler()
378 | def authorized(resp, access_token):
379 | # save the access token to the database
380 | current_user.access_token = access_token
381 | db.session.commit()
382 |
383 | return redirect(url_for('index'))
384 |
385 | OAuth 1.0a
386 | ''''''''''
387 |
388 | The second parameter will be an `oauth_token`. This is different from OAuth 2.0
389 | because the token is actually a 2-tuple of an `oauth_token` and
390 | `oauth_token_secret`. Both of these are strings of any length and BOTH are used
391 | when making external web service calls on behalf of the user.
392 |
393 | If you're working with SQLAlchemy and declarative models (i.e.
394 | `Flask-SQLAlchemy`__), you're code might look a bit like this:
395 |
396 | __ http://packages.python.org/Flask-SQLAlchemy/
397 |
398 | .. code-block:: python
399 |
400 | @app.route('/authorized')
401 | @linkedin.authorized_handler()
402 | def authorized(resp, oauth_token):
403 | # save the OAuth token to the database
404 | current_user.oauth_token = oauth_token[0]
405 | current_user.oauth_token_secret = oauth_token[1]
406 | db.session.commit()
407 |
408 | return redirect(url_for('index'))
409 |
410 | Handle if the user denies
411 | ~~~~~~~~~~~~~~~~~~~~~~~~~
412 |
413 | If you've worked with OAuth before, you'll know that there's the possibility
414 | that the user denies access to their information.
415 |
416 | OAuth 2.0
417 | '''''''''
418 |
419 | This case is clearly defined in the OAuth 2.0 spec. The `redirect_uri` will
420 | have the query parameter ``error=access_denied`` added to it.
421 |
422 | With Flask-Rauth, all you need to do is check whether the first argument in
423 | your `authorized_handler` is equal to the string ``access_denied``.
424 |
425 | .. code-block:: python
426 | :emphasize-lines: 4-5
427 |
428 | @app.route('/authorized')
429 | @github.authorized_handler()
430 | def authorized(resp, access_token):
431 | if resp == 'access_denied':
432 | return 'You denied access, meanie.'
433 |
434 | flash('You have been logged in to GitHub successfully.')
435 | session['access_token'] = access_token
436 |
437 | return redirect(url_for('index'))
438 |
439 | OAuth 1.0a
440 | ''''''''''
441 |
442 | OAuth 1.0a, however, does not clearly define what the server should do if a
443 | user denies the consumer application's access to his or her information.
444 | Naturally, there is no common consensus in practice and many web APIs do it
445 | differently.
446 |
447 | Most OAuth 1.0a-enabled web services either do not have a `Deny` button at all
448 | (assuming the user will simply close the window or tab, thus cutting the OAuth
449 | process short) or have the `Deny` button redirect the user to the home page
450 | of the external web service. This makes life difficult for us consumers!
451 |
452 | Since the functionality isn't standard, you pretty much have to try it for each
453 | external web service that you want to work with. Whenever the first argument to
454 | your authorized handler is `None`, then we pretty much have to assume that the
455 | user denied access.
456 |
457 | For example, LinkedIn's `new authorization flow`__ does not indicate at all
458 | that the user denied access. They just redirect back to your `callback`
459 | without an `oauth_verifier` (which is a token that Flask-Rauth uses to fetch
460 | the OAuth token which can be used to make calls on behalf of the user).
461 |
462 | __ https://developer.linkedin.com/blog/making-it-easier-you-develop-linkedin
463 |
464 | .. code-block:: python
465 | :emphasize-lines: 5
466 |
467 | @app.route('/authorized')
468 | @linkedin.authorized_handler()
469 | def authorized(resp, oauth_token):
470 | if resp is None:
471 | return 'You denied access, meanie.'
472 |
473 | flash('You have been logged in to Twitter successfully.')
474 | session['oauth_token'] = oauth_token
475 |
476 | return redirect(url_for('index'))
477 |
478 | For every OAuth 1.0a endpoint that you hook up to, I *highly* recommend that
479 | you check the query parameters after you deny access to see if there is any
480 | explicit indication of the deny.
481 |
482 | In Twitter's case, if the user denies access to their Twitter account, then
483 | a "denied" query parameter will be tacked on the end of your callback. Thanks
484 | Twitter!
485 |
486 | .. code-block:: python
487 | :emphasize-lines: 5
488 |
489 | @app.route('/authorized')
490 | @twitter.authorized_handler()
491 | def authorized(resp, oauth_token):
492 | # check for the Twitter-specific "access_denied" indicator
493 | if resp is None and 'denied' in request.args:
494 | return 'You denied access, meanie.'
495 | elif resp is None:
496 | return 'Hey developer, something unexpected happened.'
497 |
498 | flash('You have been logged in to Twitter successfully.')
499 | session['oauth_token'] = oauth_token
500 |
501 | return redirect(url_for('index'))
502 |
503 | Make a request
504 | ~~~~~~~~~~~~~~
505 |
506 | Now that you have a valid token, you can make requests on behalf of your user.
507 | All you need to do is call the :func:`get`, :func:`post`, :func:`put`, or
508 | :func:`delete` functions on your service object. The optional arguments for
509 | each request are outlined in the `Rauth documention`__.
510 |
511 | __ http://rauth.readthedocs.org/en/latest/#rauth.service.OAuth2Service.request
512 |
513 | Every API call requires that you provide the token that you aquired during user
514 | authorization. There are two ways to do this.
515 |
516 | Explicitly, by passing the token
517 | ''''''''''''''''''''''''''''''''
518 |
519 | You can pass the token as a keyword argument to one of the aforementioned
520 | request functions. When using OAuth 2.0, use the keyword `access_token`.
521 |
522 | .. code-block:: python
523 |
524 | # github is an OAuth 2.0 service object
525 | r = github.get('user', access_token=my_access_token)
526 |
527 | When using OAuth 1.0a, use the keyword `oauth_token`.
528 |
529 | .. code-block:: python
530 |
531 | # twitter is an OAuth 1.0a service object
532 | r = twitter.get('account/verify_credentials.json', oauth_token=token)
533 |
534 | This method of passing a token is most useful when you have to use multiple
535 | tokens at the same time (i.e. you are fetching repository information for more
536 | than one authorized GitHub user in a single request).
537 |
538 | Implicitly, by defining a token getter function
539 | '''''''''''''''''''''''''''''''''''''''''''''''
540 |
541 | If you would like to tightly associate a specific token source (i.e. database,
542 | session, cookies) with each user, declaring a token getter is probably the
543 | cleanest solution.
544 |
545 | When using OAuth 2.0, return the `access_token` recieved after authorization.
546 |
547 | .. code-block:: python
548 |
549 | # github is an OAuth 2.0 service object
550 | @github.tokengetter
551 | def get_github_token():
552 | return session.get('access_token')
553 |
554 | When using OAuth 1.0a, return the 2-tuple `oauth_token` recieved after
555 | authorization.
556 |
557 | .. code-block:: python
558 |
559 | # twitter is an OAuth 1.0a service object
560 | @twitter.tokengetter
561 | def get_twitter_token():
562 | # g is Flask's global object
563 | user = g.user
564 | if user is not None:
565 | return user.oauth_token
566 |
567 | If no access token is available, just return `None`.
568 |
569 | After the request completes, a :ref:`RauthResponse ` object is
570 | returned.
571 |
572 | Examples
573 | --------
574 |
575 | Make sure to check out the `example` directory if you're still confused about
576 | the API.
577 |
578 | `Examples`__, easily viewable in the GitHub source browser.
579 |
580 | __ https://github.com/joelverhagen/flask-rauth/tree/master/example
581 |
582 | API Reference
583 | -------------
584 |
585 | .. module:: flask_rauth
586 |
587 | .. _services-label:
588 |
589 | Services
590 | ~~~~~~~~
591 |
592 | RauthOAuth2
593 | '''''''''''
594 | .. autoclass:: RauthOAuth2
595 | :members:
596 |
597 | RauthOAuth1
598 | '''''''''''
599 | .. autoclass:: RauthOAuth1
600 | :members:
601 |
602 | RauthOfly
603 | '''''''''
604 | .. autoclass:: RauthOfly
605 | :members:
606 |
607 | Helpers
608 | ~~~~~~~~~~~~~~
609 |
610 | .. _response-label:
611 |
612 | .. autoclass:: RauthResponse
613 | :members:
614 |
615 | Internals
616 | ~~~~~~~~~
617 |
618 | .. autoclass:: RauthServiceMixin
619 | :members:
620 | :exclude-members: consumer_secret_setter, consumer_key_setter
621 |
622 | .. autoexception:: RauthException
623 |
624 | .. autofunction:: get_etree
625 |
626 | .. autofunction:: parse_response
627 |
628 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | REM Command file for Sphinx documentation
4 |
5 | if "%SPHINXBUILD%" == "" (
6 | set SPHINXBUILD=sphinx-build
7 | )
8 | set BUILDDIR=_build
9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
10 | set I18NSPHINXOPTS=%SPHINXOPTS% .
11 | if NOT "%PAPER%" == "" (
12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
14 | )
15 |
16 | if "%1" == "" goto help
17 |
18 | if "%1" == "help" (
19 | :help
20 | echo.Please use `make ^` where ^ is one of
21 | echo. html to make standalone HTML files
22 | echo. dirhtml to make HTML files named index.html in directories
23 | echo. singlehtml to make a single large HTML file
24 | echo. pickle to make pickle files
25 | echo. json to make JSON files
26 | echo. htmlhelp to make HTML files and a HTML help project
27 | echo. qthelp to make HTML files and a qthelp project
28 | echo. devhelp to make HTML files and a Devhelp project
29 | echo. epub to make an epub
30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
31 | echo. text to make text files
32 | echo. man to make manual pages
33 | echo. texinfo to make Texinfo files
34 | echo. gettext to make PO message catalogs
35 | echo. changes to make an overview over all changed/added/deprecated items
36 | echo. linkcheck to check all external links for integrity
37 | echo. doctest to run all doctests embedded in the documentation if enabled
38 | goto end
39 | )
40 |
41 | if "%1" == "clean" (
42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
43 | del /q /s %BUILDDIR%\*
44 | goto end
45 | )
46 |
47 | if "%1" == "html" (
48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
49 | if errorlevel 1 exit /b 1
50 | echo.
51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html.
52 | goto end
53 | )
54 |
55 | if "%1" == "dirhtml" (
56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
57 | if errorlevel 1 exit /b 1
58 | echo.
59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
60 | goto end
61 | )
62 |
63 | if "%1" == "singlehtml" (
64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
65 | if errorlevel 1 exit /b 1
66 | echo.
67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
68 | goto end
69 | )
70 |
71 | if "%1" == "pickle" (
72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
73 | if errorlevel 1 exit /b 1
74 | echo.
75 | echo.Build finished; now you can process the pickle files.
76 | goto end
77 | )
78 |
79 | if "%1" == "json" (
80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
81 | if errorlevel 1 exit /b 1
82 | echo.
83 | echo.Build finished; now you can process the JSON files.
84 | goto end
85 | )
86 |
87 | if "%1" == "htmlhelp" (
88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
89 | if errorlevel 1 exit /b 1
90 | echo.
91 | echo.Build finished; now you can run HTML Help Workshop with the ^
92 | .hhp project file in %BUILDDIR%/htmlhelp.
93 | goto end
94 | )
95 |
96 | if "%1" == "qthelp" (
97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
98 | if errorlevel 1 exit /b 1
99 | echo.
100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^
101 | .qhcp project file in %BUILDDIR%/qthelp, like this:
102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Flask-Rauth.qhcp
103 | echo.To view the help file:
104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Flask-Rauth.ghc
105 | goto end
106 | )
107 |
108 | if "%1" == "devhelp" (
109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
110 | if errorlevel 1 exit /b 1
111 | echo.
112 | echo.Build finished.
113 | goto end
114 | )
115 |
116 | if "%1" == "epub" (
117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
118 | if errorlevel 1 exit /b 1
119 | echo.
120 | echo.Build finished. The epub file is in %BUILDDIR%/epub.
121 | goto end
122 | )
123 |
124 | if "%1" == "latex" (
125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
126 | if errorlevel 1 exit /b 1
127 | echo.
128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
129 | goto end
130 | )
131 |
132 | if "%1" == "text" (
133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
134 | if errorlevel 1 exit /b 1
135 | echo.
136 | echo.Build finished. The text files are in %BUILDDIR%/text.
137 | goto end
138 | )
139 |
140 | if "%1" == "man" (
141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
142 | if errorlevel 1 exit /b 1
143 | echo.
144 | echo.Build finished. The manual pages are in %BUILDDIR%/man.
145 | goto end
146 | )
147 |
148 | if "%1" == "texinfo" (
149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
150 | if errorlevel 1 exit /b 1
151 | echo.
152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
153 | goto end
154 | )
155 |
156 | if "%1" == "gettext" (
157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
158 | if errorlevel 1 exit /b 1
159 | echo.
160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
161 | goto end
162 | )
163 |
164 | if "%1" == "changes" (
165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
166 | if errorlevel 1 exit /b 1
167 | echo.
168 | echo.The overview file is in %BUILDDIR%/changes.
169 | goto end
170 | )
171 |
172 | if "%1" == "linkcheck" (
173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
174 | if errorlevel 1 exit /b 1
175 | echo.
176 | echo.Link check complete; look for any errors in the above output ^
177 | or in %BUILDDIR%/linkcheck/output.txt.
178 | goto end
179 | )
180 |
181 | if "%1" == "doctest" (
182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
183 | if errorlevel 1 exit /b 1
184 | echo.
185 | echo.Testing of doctests in the sources finished, look at the ^
186 | results in %BUILDDIR%/doctest/output.txt.
187 | goto end
188 | )
189 |
190 | :end
191 |
--------------------------------------------------------------------------------
/example/facebook.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, redirect, url_for, session, request, Response
2 | from flask.ext.rauth import RauthOAuth2
3 |
4 | app = Flask(__name__)
5 | app.config.update(
6 | SECRET_KEY='just a secret key, to confound the bad guys',
7 | DEBUG=True
8 | )
9 |
10 | # you can specify your consumer key and consumer secret when constructing
11 | # the Rauth service, like this:
12 | facebook = RauthOAuth2(
13 | name='facebook',
14 | base_url='https://graph.facebook.com/',
15 | access_token_url='https://graph.facebook.com/oauth/access_token',
16 | authorize_url='https://www.facebook.com/dialog/oauth',
17 | consumer_key='your_consumer_key',
18 | consumer_secret='your_consumer_secret'
19 | )
20 |
21 |
22 | @app.route('/')
23 | def index():
24 | return redirect(url_for('login'))
25 |
26 |
27 | @app.route('/login')
28 | def login():
29 | return facebook.authorize(callback=url_for('authorized',
30 | next=request.args.get('next') or request.referrer or None,
31 | _external=True))
32 |
33 |
34 | @app.route('/login/authorized')
35 | @facebook.authorized_handler()
36 | def authorized(resp, access_token):
37 | if resp == 'access_denied':
38 | return 'You denied access, meanie. Click here to try again.' % (url_for('login'),)
39 |
40 | session['access_token'] = access_token
41 |
42 | me = facebook.get('me')
43 |
44 | from pprint import pformat
45 | return Response(pformat(me.content), mimetype='text/plain')
46 |
47 |
48 | @facebook.tokengetter
49 | def get_facebook_oauth_token():
50 | return session.get('access_token')
51 |
52 |
53 | if __name__ == '__main__':
54 | app.run()
55 |
--------------------------------------------------------------------------------
/example/google.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, redirect, url_for, session, Response
2 | from flask.ext.rauth import RauthOAuth2
3 |
4 | app = Flask(__name__)
5 | # you can specify the consumer key and consumer secret in the application,
6 | # like this:
7 | app.config.update(
8 | GOOGLE_CONSUMER_KEY='your_conumser_key',
9 | GOOGLE_CONSUMER_SECRET='your_conumser_secret',
10 | SECRET_KEY='just a secret key, to confound the bad guys',
11 | DEBUG=True
12 | )
13 |
14 | google = RauthOAuth2(
15 | name='google',
16 | base_url='https://www.googleapis.com/oauth2/v1/',
17 | access_token_url='https://accounts.google.com/o/oauth2/token',
18 | authorize_url='https://accounts.google.com/o/oauth2/auth'
19 | )
20 |
21 | # the Rauth service detects the consumer_key and consumer_secret using
22 | # `current_app`.
23 |
24 | @app.route('/')
25 | def index():
26 | access_token = session.get('access_token')
27 | if access_token is None:
28 | return redirect(url_for('login'))
29 |
30 | userinfo = google.get('userinfo', access_token=access_token)
31 |
32 | from pprint import pformat
33 | return Response(pformat(userinfo.content), mimetype='text/plain')
34 |
35 |
36 | @app.route('/login')
37 | def login():
38 | return google.authorize(
39 | callback=url_for('authorized', _external=True),
40 | scope='https://www.googleapis.com/auth/userinfo.profile')
41 |
42 |
43 | @app.route('/authorized')
44 | @google.authorized_handler()
45 | def authorized(resp, access_token):
46 | if resp == 'access_denied':
47 | return 'You denied access, meanie. Click here to try again.' % (url_for('login'),)
48 |
49 | session['access_token'] = access_token
50 |
51 | return redirect(url_for('index'))
52 |
53 |
54 | if __name__ == '__main__':
55 | app.run()
56 |
--------------------------------------------------------------------------------
/example/static/openid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joelverhagen/flask-rauth/50e1af457b81bd7122be19ca6a9edf8940e096b5/example/static/openid.png
--------------------------------------------------------------------------------
/example/static/sign-in.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joelverhagen/flask-rauth/50e1af457b81bd7122be19ca6a9edf8940e096b5/example/static/sign-in.png
--------------------------------------------------------------------------------
/example/static/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: 'Georgia', serif;
3 | font-size: 16px;
4 | margin: 30px;
5 | padding: 0;
6 | }
7 |
8 | img {
9 | border: none;
10 | }
11 |
12 | a {
13 | color: #335E79;
14 | }
15 |
16 | p.message {
17 | color: #335E79;
18 | padding: 10px;
19 | background: #CADEEB;
20 | }
21 |
22 | p.error {
23 | color: #783232;
24 | padding: 10px;
25 | background: #EBCACA;
26 | }
27 |
28 | input {
29 | font-family: 'Georgia', serif;
30 | font-size: 16px;
31 | border: 1px solid black;
32 | color: #335E79;
33 | padding: 2px;
34 | }
35 |
36 | input[type="submit"] {
37 | background: #CADEEB;
38 | color: #335E79;
39 | border-color: #335E79;
40 | }
41 |
42 | input[name="openid"] {
43 | background: url(openid.png) 4px no-repeat;
44 | padding-left: 24px;
45 | }
46 |
47 | h1, h2 {
48 | font-weight: normal;
49 | }
50 |
--------------------------------------------------------------------------------
/example/templates/index.html:
--------------------------------------------------------------------------------
1 | {% extends "layout.html" %}
2 | {% block body %}
3 |
14 | {% for message in get_flashed_messages() %}
15 |
{{ message }}
16 | {% endfor %}
17 | {% block body %}{% endblock %}
18 |
--------------------------------------------------------------------------------
/example/tweet.py:
--------------------------------------------------------------------------------
1 | '''
2 | Instructions:
3 |
4 | 1. Make sure you have Flask, Flask-Rauth, and SQLAlchemy installed.
5 |
6 | $ pip install Flask Flask-Rauth SQLAlchemy
7 |
8 | 2. Open a Python shell in this directory and execute the following:
9 |
10 | $ python
11 | >>> from tweet import init_db
12 | >>> init_db()
13 | >>> exit()
14 |
15 | This will initialize the SQLite database.
16 |
17 | 3. Start the application.
18 |
19 | $ python tweet.py
20 |
21 | 4. Navigate your web browser to where this app is being served (localhost,
22 | by default).
23 | '''
24 | from flask import Flask, request, redirect, url_for, session, flash, g, render_template
25 | from flask.ext.rauth import RauthOAuth1
26 |
27 | from sqlalchemy import create_engine, Column, Integer, String, Text
28 | from sqlalchemy.orm import scoped_session, sessionmaker
29 | from sqlalchemy.ext.declarative import declarative_base
30 |
31 | # setup flask
32 | app = Flask(__name__)
33 | # you can specify the consumer key and consumer secret in the application,
34 | # like this:
35 | app.config.update(
36 | TWITTER_CONSUMER_KEY='your_consumer_key',
37 | TWITTER_CONSUMER_SECRET='your_consumer_secret',
38 | SECRET_KEY='just a secret key, to confound the bad guys',
39 | DEBUG = True
40 | )
41 |
42 |
43 | # setup the twitter endpoint
44 | twitter = RauthOAuth1(
45 | name='twitter',
46 | base_url='https://api.twitter.com/1/',
47 | request_token_url='https://api.twitter.com/oauth/request_token',
48 | access_token_url='https://api.twitter.com/oauth/access_token',
49 | authorize_url='https://api.twitter.com/oauth/authorize'
50 | )
51 |
52 | # this call simply initializes default an empty consumer key and secret in the app
53 | # config if none exist.
54 | # I've included it to match the "look" of Flask extensions
55 | twitter.init_app(app)
56 |
57 | # setup sqlalchemy
58 | engine = create_engine('sqlite:////tmp/tweet.db')
59 | db_session = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine))
60 | Base = declarative_base()
61 | Base.query = db_session.query_property()
62 |
63 |
64 | def init_db():
65 | Base.metadata.create_all(bind=engine)
66 |
67 |
68 | class User(Base):
69 | __tablename__ = 'users'
70 | id = Column('user_id', Integer, primary_key=True)
71 | name = Column(String(60))
72 | oauth_token = Column(Text)
73 | oauth_secret = Column(Text)
74 |
75 | def __init__(self, name):
76 | self.name = name
77 |
78 |
79 | @app.before_request
80 | def before_request():
81 | g.user = None
82 | if 'user_id' in session:
83 | g.user = User.query.get(session['user_id'])
84 |
85 |
86 | @app.after_request
87 | def after_request(response):
88 | db_session.remove()
89 | return response
90 |
91 |
92 | @twitter.tokengetter
93 | def get_twitter_token():
94 | '''
95 | This is used by the API to look for the auth token and secret that are used
96 | for Twitter API calls. If you don't want to store this in the database,
97 | consider putting it into the session instead.
98 |
99 | Since the Twitter API is OAuth 1.0a, the `tokengetter` must return a
100 | 2-tuple: (oauth_token, oauth_secret).
101 | '''
102 | user = g.user
103 | if user is not None:
104 | return user.oauth_token, user.oauth_secret
105 |
106 |
107 | @app.route('/')
108 | def index():
109 | tweets = None
110 | if g.user is not None:
111 | resp = twitter.get('statuses/home_timeline.json')
112 | if resp.status == 200:
113 | tweets = resp.content
114 | else:
115 | flash('Unable to load tweets from Twitter. Maybe out of '
116 | 'API calls or Twitter is overloaded.')
117 | return render_template('index.html', tweets=tweets)
118 |
119 |
120 | @app.route('/tweet', methods=['POST'])
121 | def tweet():
122 | '''
123 | Calls the remote twitter API to create a new status update.
124 | '''
125 | if g.user is None:
126 | return redirect(url_for('login', next=request.url))
127 | status = request.form['tweet']
128 | if not status:
129 | return redirect(url_for('index'))
130 | resp = twitter.post('statuses/update.json', data={
131 | 'status': status
132 | })
133 | if resp.status == 403:
134 | flash('Your tweet was too long.')
135 | elif resp.status == 401:
136 | flash('Authorization error with Twitter.')
137 | else:
138 | flash('Successfully tweeted your tweet (ID: #%s)' % resp.content['id'])
139 | return redirect(url_for('index'))
140 |
141 |
142 | @app.route('/login')
143 | def login():
144 | '''
145 | Calling into `authorize` will cause the OAuth 1.0a machinery to kick
146 | in. If all has worked out as expected or if the user denied access to
147 | his/her information, the remote application will redirect back to the callback URL
148 | provided.
149 |
150 | Int our case, the 'authorized/' route handles the interaction after the redirect.
151 | '''
152 | return twitter.authorize(callback=url_for('authorized',
153 | _external=True,
154 | next=request.args.get('next') or request.referrer or None))
155 |
156 |
157 | @app.route('/logout')
158 | def logout():
159 | session.pop('user_id', None)
160 | flash('You were signed out')
161 | return redirect(request.referrer or url_for('index'))
162 |
163 |
164 | @app.route('/authorized')
165 | @twitter.authorized_handler()
166 | def authorized(resp, oauth_token):
167 | '''
168 | Called after authorization. After this function finished handling,
169 | the tokengetter from above is used to retrieve the 2-tuple containing the
170 | oauth_token and oauth_token_secret.
171 |
172 | Because reauthorization often changes any previous
173 | oauth_token/oauth_token_secret values, then we must update them in the
174 | database.
175 |
176 | If the application redirected back after denying, the `resp` passed
177 | to the function will be `None`. Unfortunately, OAuth 1.0a (the version
178 | that Twitter, LinkedIn, etc use) does not specify exactly what should
179 | happen when the user denies access. In the case of Twitter, a query
180 | parameter `denied=(some hash)` is appended to the redirect URL.
181 | '''
182 | next_url = request.args.get('next') or url_for('index')
183 |
184 | # check for the Twitter-specific "access_denied" indicator
185 | if resp is None and 'denied' in request.args:
186 | flash(u'You denied the request to sign in.')
187 | return redirect(next_url)
188 |
189 | # pull out the nicely parsed response content.
190 | content = resp.content
191 |
192 | user = User.query.filter_by(name=content['screen_name']).first()
193 |
194 | # this if the first time signing in for this user
195 | if user is None:
196 | user = User(content['screen_name'])
197 | db_session.add(user)
198 |
199 | # we now update the oauth_token and oauth_token_secret
200 | # this involves destructuring the 2-tuple that is passed back from the
201 | # Twitter API, so it can be easily stored in the SQL database
202 | user.oauth_token = oauth_token[0]
203 | user.oauth_secret = oauth_token[1]
204 | db_session.commit()
205 |
206 | session['user_id'] = user.id
207 | flash('You were signed in')
208 | return redirect(next_url)
209 |
210 |
211 | if __name__ == '__main__':
212 | app.run()
213 |
--------------------------------------------------------------------------------
/flask_rauth.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | '''
3 | flask.ext.rauth
4 | ~~~~~~~~~~~~~~~
5 |
6 | Adds OAuth 1.0/a, 2.0, and Ofly consumer support for Flask.
7 |
8 | Flask-Rauth is a fork of Armin Ronacher's Flask-OAuth.
9 | :copyright: (c) 2010 by Armin Ronacher.
10 | :copyright: (c) 2012 by Joel Verhagen.
11 | :license: BSD, see LICENSE for more details.
12 | '''
13 | from functools import wraps
14 | from urlparse import urljoin
15 | from flask import request, session, redirect, current_app
16 | from werkzeug import parse_options_header
17 | from rauth.service import OAuth2Service, OAuth1Service, OflyService, Response, parse_utf8_qsl
18 |
19 | # specified by the OAuth 2.0 spec
20 | # http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-4.1.4
21 | ACCESS_DENIED = 'access_denied'
22 |
23 | _etree = None
24 | def get_etree():
25 | '''
26 | Returns an elementtree implementation. Searches for `lxml.etree`, then
27 | `xml.etree.cElementTree`, then `xml.etree.ElementTree`.
28 | '''
29 | global _etree
30 | if _etree is None:
31 | try:
32 | from lxml import etree
33 | _etree = etree
34 | except ImportError:
35 | try:
36 | from xml.etree import cElementTree
37 | _etree = cElementTree
38 | except ImportError:
39 | try:
40 | from xml.etree import ElementTree
41 | _etree = ElementTree
42 | except ImportError:
43 | pass
44 | return _etree
45 |
46 | def parse_response(resp):
47 | '''
48 | Inspects a :class:`requests.Response` object and returns the content in a
49 | more usable form. The following parsing checks are done:
50 |
51 | 1. JSON, using the `json` attribute.
52 | 2. XML, using :func:`get_etree`.
53 | 3. RSS or Atom, using :mod:`feedparser`, if available.
54 | 4. Query string, using :func:`parse_utf8_qsl`.
55 | 5. If all else fails the plain-text `content` is returned.
56 |
57 | :param resp: A `requests.Response` object.
58 | '''
59 | if resp.json is not None:
60 | return resp.json
61 |
62 | ct, _ = parse_options_header(resp.headers.get('content-type'))
63 |
64 | if ct in ('application/xml', 'text/xml'):
65 | etree = get_etree()
66 | if etree is not None:
67 | return etree.fromstring(resp.content)
68 |
69 | if ct in ('application/atom+xml', 'application/rss+xml'):
70 | try:
71 | import feedparser
72 | return feedparser.parse(resp.content)
73 | except:
74 | pass
75 |
76 | if isinstance(resp.content, basestring):
77 | return parse_utf8_qsl(resp.content)
78 |
79 | return resp.content
80 |
81 | class RauthException(RuntimeError):
82 | '''
83 | Raised if authorization fails for some reason.
84 |
85 | :param message: A helpful error message for debugging.
86 | :param response: The :class:`requests.Response` object associated with the
87 | failure, if available.
88 | '''
89 | message = None
90 |
91 | def __init__(self, message, response=None):
92 | # a helpful error message for debugging
93 | self.message = message
94 |
95 | # if available, the associate response object
96 | self.response = response
97 |
98 | def __str__(self):
99 | return self.message.encode('utf-8')
100 |
101 | def __unicode__(self):
102 | return self.message
103 |
104 | class RauthResponse(Response):
105 | '''
106 | This class inherits :class:`rauth.service.Response`.
107 |
108 | :param resp: A :class:`rauth.service.Response`, whose `response` attribute
109 | will be re-wrapped, with better content parsing.
110 | '''
111 | def __init__(self, resp):
112 | # the original response
113 | self.response = resp.response
114 |
115 | self._cached_content = None
116 |
117 | @property
118 | def content(self):
119 | '''
120 | The content associated with the response. The content is parsed into a
121 | more useful format, if possible, using :func:`parse_response`.
122 |
123 | The content is cached, so that :func:`parse_response` is only run once.
124 | '''
125 | if self._cached_content is None:
126 | # the parsed content from the server
127 | self._cached_content = parse_response(self.response)
128 | return self._cached_content
129 |
130 | @property
131 | def status(self):
132 | '''
133 | The status code of the response.
134 | '''
135 | return self.response.status_code
136 |
137 | @property
138 | def content_type(self):
139 | '''
140 | The Content-Type of the response.
141 | '''
142 | return self.response.headers.get('content-type')
143 |
144 | class RauthServiceMixin(object):
145 | '''
146 | A mixin used to help glue Flask and `rauth` together. **You should not
147 | initialize this class on your own.** Instead, it will be initialized by one
148 | of the service objects above.
149 |
150 | :param app: An Flask application object to tie this extension to.
151 | :param base_url: A base URL value which, if provided, will be joined
152 | with the URL passed to requests made on this object.
153 | '''
154 | def __init__(self, app, base_url):
155 | self.app = app
156 | if app is not None:
157 | self.init_app(app)
158 |
159 | self.base_url = base_url
160 | self.tokengetter_f = None
161 |
162 | def init_app(self, app):
163 | '''
164 | Initializes the application with this object as an extension.
165 |
166 | This simply ensures that there are `config` entries for keys generated
167 | by :func:`_consumer_key_config` and :func:`_consumer_secret_config`,
168 | i.e. ``(NAME)_CONSUMER_KEY`` and ``(NAME)_CONSUMER_SECRET``.
169 |
170 | :param app: A Flask application object.
171 | '''
172 | # the name attribute will be set by a rauth service
173 | app.config.setdefault(self._consumer_key_config())
174 | app.config.setdefault(self._consumer_secret_config())
175 |
176 | def tokengetter(self, f):
177 | '''
178 | The tokengetter decorator used to provide a function that will return
179 | the required token before making a request.
180 | '''
181 | self.tokengetter_f = f
182 | return f
183 |
184 | def _expand_url(self, url):
185 | # prepend the base base_url, if we have it
186 | if self.base_url is not None:
187 | url = urljoin(self.base_url, url)
188 | return url
189 |
190 | def _session_key(self, suffix):
191 | return '%s_%s_%s' % (self.name, self.__class__.__name__, suffix)
192 |
193 | @property
194 | def consumer_key(self):
195 | '''
196 | Returns the consumer_key for this object. The following method is used
197 | to determine what the consumer_key is:
198 |
199 | 1. A `static_consumer_key`, set by passing a `consumer_key` to the
200 | constructor.
201 | 2. The `consumer_key` set in the config of an app passed to the
202 | constructor. The application config key is based on the name
203 | passed to the constructor. See :func:`init_app` for more
204 | information.
205 | 3. The `consumer_key` set in the config of the Flask `current_app`.
206 | '''
207 | if self.static_consumer_key is not None:
208 | # if a consumer key was provided in the constructor, default to that
209 | return self.static_consumer_key
210 | elif self.app is not None and self._consumer_key_config() in self.app.config:
211 | # if an app was provided in the constructor, search its config first
212 | return self.app.config[self._consumer_key_config()]
213 |
214 | # otherwise, search in the current_app config
215 | return current_app.config.get(self._consumer_key_config(), None)
216 |
217 | @consumer_key.setter
218 | def consumer_key(self, consumer_key):
219 | self.static_consumer_key = consumer_key
220 |
221 | @property
222 | def consumer_secret(self):
223 | '''
224 | Returns the consumer_secret for this object. A method analogous to that
225 | of `consumer_key` is used to find the value.
226 | '''
227 | if self.static_consumer_secret is not None:
228 | # if a consumer secret was provided in the constructor, default to that
229 | return self.static_consumer_secret
230 | elif self.app is not None and self._consumer_secret_config() in self.app.config:
231 | # if an app was provided in the constructor, search its config first
232 | return self.app.config[self._consumer_secret_config()]
233 |
234 | # otherwise, search in the current_app config
235 | return current_app.config.get(self._consumer_secret_config(), None)
236 |
237 | @consumer_secret.setter
238 | def consumer_secret(self, consumer_secret):
239 | self.static_consumer_secret = consumer_secret
240 |
241 | def _consumer_key_config(self):
242 | return '%s_CONSUMER_KEY' % (self.name.upper(),)
243 |
244 | def _consumer_secret_config(self):
245 | return '%s_CONSUMER_SECRET' % (self.name.upper(),)
246 |
247 | class RauthOAuth2(OAuth2Service, RauthServiceMixin):
248 | '''
249 | Encapsulates OAuth 2.0 interaction to be easily integrated with Flask.
250 |
251 | This class inherits :class:`rauth.service.OAuth2Service` and
252 | :class:`RauthServiceMixin`.
253 |
254 | :param app: See :class:`RauthServiceMixin`.
255 | :param base_url: See :class:`RauthServiceMixin`.
256 | :param consumer_key: A static consumer key to use with this service.
257 | Supplying this argument will mean any consumer keys found in Flask
258 | application config will be ignored.
259 | :param consumer_secret: A static consumer secret to use with this service.
260 | Supplying this argument will mean any consumer secrets found in Flask
261 | application config will be ignored.
262 | :param kwargs: Any arguments that can be passed to
263 | :class:`rauth.OAuth2Service`.
264 | '''
265 | def __init__(self, app=None, base_url=None, consumer_key=None, consumer_secret=None, **kwargs):
266 | RauthServiceMixin.__init__(self, app=app, base_url=base_url)
267 | OAuth2Service.__init__(self, consumer_key=consumer_key, consumer_secret=consumer_secret, **kwargs)
268 |
269 | def authorize(self, callback, **authorize_params):
270 | '''
271 | Begins the OAuth 2.0 authorization process for this service.
272 |
273 | :param callback: The **required** absolute URL that will be
274 | redirected to by the OAuth 2.0 endpoint after authorization is
275 | complete.
276 | :param authorize_params: Query parameters to be passed to authorization,
277 | prompt, addition to the `redirect_uri`. One common example is
278 | `scope`.
279 | '''
280 | # save the redirect_uri in the session
281 | session[self._session_key('redirect_uri')] = callback
282 |
283 | return redirect(self.get_authorize_url(redirect_uri=callback, **authorize_params))
284 |
285 | def authorized_handler(self, method='POST'):
286 | '''
287 | The decorator to assign a function that will be called after
288 | authorization is complete. By default, a `POST` request is used to
289 | fetch the access token. If you need to send a `GET` request, use the
290 | ``authorized_handler(method='GET')`` to do so.
291 |
292 | It should be a route that takes two parameters: `response` and
293 | `access_token`.
294 |
295 | If `response` is ``access_denied``, then the user denied access to
296 | his/her information.
297 | '''
298 | def create_authorized_handler(f):
299 | @wraps(f)
300 | def decorated(*args, **kwargs):
301 | resp = access_token = None
302 | if 'error' in request.args and request.args['error'] == ACCESS_DENIED:
303 | resp = ACCESS_DENIED
304 | elif 'code' in request.args:
305 | resp = RauthResponse(self.get_access_token(method=method, data={
306 | 'code': request.args['code'],
307 | 'redirect_uri': session.pop(self._session_key('redirect_uri'), None)
308 | }))
309 |
310 | if resp.status != 200:
311 | raise RauthException('An error occurred while getting the OAuth 2.0 access_token', resp)
312 |
313 | access_token = resp.content.get('access_token')
314 |
315 | return f(*((resp, access_token) + args), **kwargs)
316 | return decorated
317 | return create_authorized_handler
318 |
319 | def request(self, method, url, access_token=None, **kwargs):
320 | '''
321 | Make a request using an `access_token` obtained via the
322 | :func:`authorized_handler`.
323 |
324 | If no access_token is provided and a
325 | :func:`RauthServiceMixin.tokengetter` **was** provided, the
326 | :func:`RauthServiceMixin.tokengetter` will be called.
327 |
328 | :param method: Same as :func:`rauth.OAuth2Service.request`.
329 | :param url: Same as :func:`rauth.OAuth2Service.request`, except when a
330 | `base_url` was provided to the constructor, in which case the URL
331 | should be any valid endpoint after being :func:`urljoin` ed with
332 | the `base_url`.
333 | :param access_token: The `access_token` required to make requests
334 | against this service.
335 | :param kwargs: Any `kwargs` that can be passed to
336 | :func:`OAuth2Service.request`.
337 | '''
338 | url = self._expand_url(url)
339 |
340 | if access_token is None and self.tokengetter_f is not None:
341 | access_token = self.tokengetter_f()
342 |
343 | # add in the access_token
344 | if 'params' not in kwargs:
345 | kwargs['params'] = {'access_token': access_token}
346 | elif 'access_token' not in kwargs['params']:
347 | # TODO: handle if the user sends bytes -> properly append 'access_token'
348 | kwargs['params']['access_token'] = access_token
349 |
350 | # call the parent implementation
351 | return RauthResponse(OAuth2Service.request(self, method, url, **kwargs))
352 |
353 | class RauthOAuth1(OAuth1Service, RauthServiceMixin):
354 | '''
355 | Encapsulates OAuth 1.0a interaction to be easily integrated with Flask.
356 |
357 | This class inherits :class:`rauth.service.OAuth1Service` and
358 | :class:`RauthServiceMixin`.
359 |
360 | See :class:`RauthOAuth2` for analogous details.
361 | '''
362 | def __init__(self, app=None, base_url=None, consumer_key=None, consumer_secret=None, **kwargs):
363 | RauthServiceMixin.__init__(self, app=app, base_url=base_url)
364 | OAuth1Service.__init__(self, consumer_key=consumer_key, consumer_secret=consumer_secret, **kwargs)
365 |
366 | def authorize(self, callback, **request_params):
367 | '''
368 | Begins the OAuth 1.0a authorization process for this service.
369 |
370 | :param callback: The **required** absolute URL that will be
371 | redirected to by the OAuth 1.0 endpoint after authorization is
372 | complete.
373 | :param request_params: Query parameters to be passed to the request,
374 | token endpoint, in addition to the `callback`. One common example
375 | is `scope`.
376 | '''
377 | # fetch the request_token (token and secret 2-tuple) and convert it to a dict
378 | request_token = self.get_request_token(oauth_callback=callback, **request_params)
379 | request_token = {'request_token': request_token[0], 'request_token_secret': request_token[1]}
380 |
381 | # save the request_token in the session
382 | session[self._session_key('request_token')] = request_token
383 |
384 | # pass the token and any user-provided parameters
385 | return redirect(self.get_authorize_url(request_token['request_token']))
386 |
387 | def authorized_handler(self, method='POST'):
388 | '''
389 | The handler should expect two arguments: `response` and `oauth_token`.
390 | By default, a `POST` request is used to fetch the access token. If you
391 | need to send a `GET` request, use the
392 | ``authorized_handler(method='GET')`` to do so.
393 |
394 | If `response` is ``None`` then the user *most-likely* denied access
395 | to his/her information. Since OAuth 1.0a does not specify a
396 | standard query parameter to specify that the user denied the
397 | authorization, you will need to figure out how the endpoint that
398 | your are interacting with delineates this edge-case.
399 | '''
400 | def create_authorized_handler(f):
401 | @wraps(f)
402 | def decorated(*args, **kwargs):
403 | resp = oauth_token = None
404 | if 'oauth_verifier' in request.args:
405 | resp = RauthResponse(self.get_access_token(
406 | method=method,
407 | data={'oauth_verifier': request.args['oauth_verifier']},
408 | **session.pop(self._session_key('request_token'), {}))
409 | )
410 |
411 | if resp.status != 200:
412 | raise RauthException('An error occurred during OAuth 1.0a authorization', resp)
413 |
414 | oauth_token = (resp.content.get('oauth_token'), resp.content.get('oauth_token_secret'))
415 |
416 | return f(*((resp, oauth_token) + args), **kwargs)
417 | return decorated
418 | return create_authorized_handler
419 |
420 | def request(self, method, url, oauth_token=None, **kwargs):
421 | '''
422 | Make a request using an `oauth_token` obtained via the
423 | :func:`authorized_handler`.
424 | '''
425 | url = self._expand_url(url)
426 |
427 | if oauth_token is None and self.tokengetter_f is not None:
428 | oauth_token = self.tokengetter_f()
429 |
430 | # take apart the 2-tuple
431 | if oauth_token is not None:
432 | oauth_token, oauth_token_secret = oauth_token
433 | else:
434 | oauth_token_secret = None
435 |
436 | # call the parent implementation
437 | return RauthResponse(OAuth1Service.request(self, method, url, access_token=oauth_token, access_token_secret=oauth_token_secret, **kwargs))
438 |
439 | class RauthOfly(OflyService, RauthServiceMixin):
440 | '''
441 | Encapsulates Ofly interaction to be easily integrated with Flask.
442 |
443 | This class inherits :class:`rauth.service.OflyService` and
444 | :class:`RauthServiceMixin`.
445 |
446 | See :class:`RauthOAuth2` for analogous details.
447 | '''
448 | def __init__(self, app=None, base_url=None, consumer_key=None, consumer_secret=None, **kwargs):
449 | RauthServiceMixin.__init__(self, app=app, base_url=base_url)
450 | OflyService.__init__(self, consumer_key=consumer_key, consumer_secret=consumer_secret, **kwargs)
451 |
452 | def authorize(self, callback, **authorize_params):
453 | '''
454 | Begins the Ofly authorization process for this service.
455 |
456 | :param callback: The **required** absolute URL that will be
457 | redirected to by the Ofly endpoint after authorization is
458 | complete.
459 | :param authorize_params: Query parameters to be passed to the request,
460 | token endpoint, in addition to the `callback`.
461 | '''
462 | # Ofly web authentication (== "app authentication" == "seamless sign-in") requires a redirect_uri value
463 |
464 | # pass the callback and any user-provided parameters
465 | return redirect(self.get_authorize_url(redirect_uri=callback, **authorize_params))
466 |
467 | def authorized_handler(self, method='POST'):
468 | '''
469 | The handler should expect two arguments: `response` and `oflyUserid`.
470 | The `method` parameter is unused.
471 |
472 | If `response` is ``access_denied``, then the user denied access to
473 | his/her information.
474 | '''
475 | def create_authorized_handler(f):
476 | @wraps(f)
477 | def decorated(*args, **kwargs):
478 | resp = oflyUserid = None
479 | if 'oflyUserid' in request.args:
480 | if request.args['oflyUserid'] == 'no-grant':
481 | resp = ACCESS_DENIED
482 | else:
483 | resp = {
484 | 'oflyUserid': request.args['oflyUserid'],
485 | 'oflyAppId': request.args.get('oflyAppId'),
486 | 'oflyUserEmail': request.args.get('oflyUserEmail')
487 | }
488 |
489 | oflyUserid = request.args['oflyUserid']
490 |
491 | return f(*((resp, oflyUserid) + args), **kwargs)
492 | return decorated
493 | return create_authorized_handler
494 |
495 | def request(self, method, url, oflyUserid=None, **kwargs):
496 | '''
497 | Make a request using an `oflyUserid` obtained via the
498 | :func:`authorized_handler`.
499 | '''
500 | url = self._expand_url(url)
501 |
502 | if oflyUserid is None and self.tokengetter_f is not None:
503 | oflyUserid = self.tokengetter_f()
504 |
505 | # add in the access_token
506 | if 'params' not in kwargs:
507 | kwargs['params'] = {'oflyUserid': oflyUserid}
508 | elif 'oflyUserid' not in kwargs['params']:
509 | # TODO: handle if the user sends bytes -> properly append 'oflyUserid'
510 | kwargs['params']['oflyUserid'] = oflyUserid
511 |
512 | # call the parent implementation
513 | return RauthResponse(OflyService.request(self, method, url, **kwargs))
514 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [build_sphinx]
2 | source-dir = docs/
3 | build-dir = docs/_build
4 | all_files = 1
5 |
6 | [upload_sphinx]
7 | upload-dir = docs/_build/html
8 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | """
2 | Flask-Rauth
3 | -----------
4 |
5 | Adds OAuth 1.0/a, 2.0, and Ofly consumer support for Flask.
6 |
7 | Links
8 | `````
9 |
10 | * `development version `_
11 | * `rauth `_
12 | """
13 | from setuptools import setup
14 |
15 |
16 | setup(
17 | name='Flask-Rauth',
18 | version='0.3.2',
19 | url='https://github.com/joelverhagen/flask-rauth',
20 | license='BSD',
21 | author='Joel Verhagen',
22 | author_email='joel.verhagen@gmail.com',
23 | description='Adds OAuth 1.0/a, 2.0, and Ofly consumer support for Flask.',
24 | long_description=__doc__,
25 | py_modules=['flask_rauth'],
26 | zip_safe=False,
27 | platforms='any',
28 | install_requires=[
29 | 'Flask',
30 | 'rauth'
31 | ],
32 | classifiers=[
33 | 'Development Status :: 4 - Beta',
34 | 'Environment :: Web Environment',
35 | 'Intended Audience :: Developers',
36 | 'License :: OSI Approved :: BSD License',
37 | 'Operating System :: OS Independent',
38 | 'Programming Language :: Python',
39 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
40 | 'Topic :: Software Development :: Libraries :: Python Modules'
41 | ]
42 | )
43 |
--------------------------------------------------------------------------------