#
# Copyright (c) 2012, Red Hat, Inc.
#
# 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 the Red Hat, Inc. 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 REGENTS AND CONTRIBUTORS \(aq\(aqAS IS\(aq\(aq 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 REGENTS 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.
# This is a sample application. In addition to using Flask\-FAS, it uses
# Flask\-WTF (WTForms) to handle the login form. Use of Flask\-WTF is highly
# recommended because of its CSRF checking.
import flask
from flask.ext import wtf
from flask.ext.fas import FAS, fas_login_required
# Set up Flask application
app = flask.Flask(__name__)
# Set up FAS extension
fas = FAS(app)
# Application configuration
# SECRET_KEY is necessary to CSRF in WTForms. It nees to be secret to
# make the csrf tokens unguessable but if you have multiple servers behind
# a load balancer, the key needs to be the same on each.
app.config[\(aqSECRET_KEY\(aq] = \(aqchange me!\(aq
# Other configuration options for Flask\-FAS:
# FAS_BASE_URL: the base URL for the accounts system
# (default https://admin.fedoraproject.org/accounts/)
# FAS_CHECK_CERT: check the SSL certificate of FAS (default True)
# FAS_FLASK_COOKIE_REQUIRES_HTTPS: send the \(aqsecure\(aq option with
# the login cookie (default True)
# You should use these options\(aq defaults for production applications!
app.config[\(aqFAS_BASE_URL\(aq] = \(aqhttps://fakefas.fedoraproject.org/accounts/\(aq
app.config[\(aqFAS_CHECK_CERT\(aq] = False
app.config[\(aqFAS_FLASK_COOKIE_REQUIRES_HTTPS\(aq] = False
# A basic login form
class LoginForm(wtf.Form):
username = wtf.TextField(\(aqUsername\(aq, [wtf.validators.Required()])
password = wtf.PasswordField(\(aqPassword\(aq, [wtf.validators.Required()])
# Inline templates keep this test application all in one file. Don\(aqt do this in
# a real application. Please.
TEMPLATE_START = """
Flask\-FAS test app
{% if g.fas_user %}
Hello, {{ g.fas_user.username }} —
Log out
{% else %}
You are not logged in —
Log in
{% endif %}
— Main page
"""
@app.route(\(aq/\(aq)
def index():
data = TEMPLATE_START
data += \(aqCheck if you are cla+1
\(aq % \e
flask.url_for(\(aqclaplusone\(aq)
data += \(aqSee a secret message (requires login)
\(aq % \e
flask.url_for(\(aqsecret\(aq)
return flask.render_template_string(data)
@app.route(\(aq/login\(aq, methods=[\(aqGET\(aq, \(aqPOST\(aq])
def auth_login():
# Your application should probably do some checking to make sure the URL
# given in the next request argument is sane. (For example, having next set
# to the login page will cause a redirect loop.) Some more information:
# http://flask.pocoo.org/snippets/62/
if \(aqnext\(aq in flask.request.args:
next_url = flask.request.args[\(aqnext\(aq]
else:
next_url = flask.url_for(\(aqindex\(aq)
# If user is already logged in, return them to where they were last
if flask.g.fas_user:
return flask.redirect(next_url)
# Init login form
form = LoginForm()
# Init template
data = TEMPLATE_START
data += (\(aqLog into the \(aq
\(aqFedora Accounts System:\(aq)
# If this is POST, process the form
if form.validate_on_submit():
if fas.login(form.username.data, form.password.data):
# Login successful, return
return flask.redirect(next_url)
else:
# Login unsuccessful
data += \(aq
Invalid login
\(aq
data += """
"""
return flask.render_template_string(data, form=form)
@app.route(\(aq/logout\(aq)
def logout():
if flask.g.fas_user:
fas.logout()
return flask.redirect(flask.url_for(\(aqindex\(aq))
# This demonstrates the use of the fas_login_required decorator. The
# secret message can only be viewed by those who are logged in.
@app.route(\(aq/secret\(aq)
@fas_login_required
def secret():
data = TEMPLATE_START + \(aqBe sure to drink your Ovaltine
\(aq
return flask.render_template_string(data)
# This demonstrates checking for group membership inside of a function.
# The flask_fas adapter also provides a cla_plus_one_required decorator that
# can restrict a url so that you can only access it from an account that has
# cla +1.
@app.route(\(aq/claplusone\(aq)
def claplusone():
data = TEMPLATE_START
if not flask.g.fas_user:
# Not logged in
return flask.render_template_string(data +
\(aqYou must log in to check your cla +1 status
\(aq)
non_cla_groups = [x.name for x in flask.g.fas_user.approved_memberships
if x.group_type != \(aqcla\(aq]
if len(non_cla_groups) > 0:
data += \(aqYour account is cla+1.
\(aq
else:
data += \(aqYour account is not cla+1.
\(aq
return flask.render_template_string(data)
if __name__ == \(aq__main__\(aq:
app.run(debug=True)
.ft P
.fi
.UNINDENT
.UNINDENT
.SS Flask FAS OpenId Auth Plugin
.sp
The flask_openid provider is an alternative to the flask_fas auth plugin. It
leverages our FAS\-OpenID server to do authn and authz (group memberships).
Note that not every feature is available with a generic OpenID provider \-\- the
plugin depends on the OpenID provider having certain extensions in order to
provide more than basic OpenID auth.
.INDENT 0.0
.IP \(bu 2
Any compliant OpenID server should allow you to use the basic authn features of OpenID
OpenID authentication core: \fI\%http://openid.net/specs/openid\-authentication\-2_0.html\fP
.IP \(bu 2
Retrieving simple information about the user such as username, human name, email
is done with sreg: \fI\%http://openid.net/specs/openid\-simple\-registration\-extension\-1_0.html\fP
which is an extension supported by many providers.
.IP \(bu 2
Advanced security features such as requiring a user to re\-login to the OpenID
provider or specifying that the user login with a hardware token requires
the PAPE extension:
\fI\%http://openid.net/specs/openid\-provider\-authentication\-policy\-extension\-1_0.html\fP
.IP \(bu 2
To get groups information, the provider must implement the
\fI\%https://dev.launchpad.net/OpenIDTeams\fP extension.
.INDENT 2.0
.IP \(bu 2
We have extended the teams extension so you can request a team name of
\fB_FAS_ALL_GROUPS_\fP to retrieve all the groups that a user belongs to.
Without this addition to the teams extension you will need to manually
configure which groups you are interested in knowing about. See the
documentation for how to do so.
.UNINDENT
.IP \(bu 2
Retrieving information about whether a user has signed a CLA (For Fedora,
this is the Fedora Project Contributor Agreement).
\fI\%http://fedoraproject.org/specs/open_id/cla\fP
.UNINDENT
.sp
If the provider you use does not support one of these extensions, the plugin
should still work but naturally, it will return empty values for the
information that the extension would have provided.
.SS FAS Flask OpenID Auth Plugin
.INDENT 0.0
.TP
.B Authors
Patrick Uiterwjk
.TP
.B Date
18 February 2013
.TP
.B For Version
0.3.x
.UNINDENT
.sp
The Fedora\-Account\-System has a OpenID provider that applications
can use to authenticate users in web apps. For our Flask applications
we have an identity provider that uses this OpenID service to authenticate users.
It is almost completely compatible with flask_fas except that it does not
use the username/password provided by the client application (it is silently
ignored). It can be configured to use any OpenID authentication service that
implements the OpenID Teams Extension, Simple Registration Extension and
CLA Extension.
.SS Configuration
.sp
The FAS OpenID auth plugin has several config values that can be used to control
how the auth plugin functions. You can set these in your application\(aqs config
file.
.INDENT 0.0
.TP
.B FAS_OPENID_ENDPOINT
Set this to the OpenID endpoint url you are authenticating against.
Default is "\fI\%http://id.fedoraproject.org/\fP"
.TP
.B FAS_CHECK_CERT
When set, this will check the SSL Certificate for the FAS server to make
sure that it is who it claims to be. This is useful to set to False when
testing against a local FAS server but should always be set to True in
production. Default: True
.UNINDENT
.SS Sample Application
.sp
The following is a sample, minimal flask application that uses fas_flask for
authentication:
.INDENT 0.0
.INDENT 3.5
.sp
.nf
.ft C
#!/usr/bin/python \-tt
# Flask\-FAS\-OpenID \- A Flask extension for authorizing users with OpenID
# Primary maintainer: Patrick Uiterwijk
#
# Copyright (c) 2012\-2013, Red Hat, Inc., Patrick Uiterwijk
#
# 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 the Red Hat, Inc. 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 REGENTS AND CONTRIBUTORS \(aq\(aqAS IS\(aq\(aq 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 REGENTS 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.
# This is a sample application.
import flask
from flask_fas_openid import fas_login_required, cla_plus_one_required, FAS
# Set up Flask application
app = flask.Flask(__name__)
# Set up FAS extension
fas = FAS(app)
# Application configuration
# SECRET_KEY is necessary for the Flask session system. It nees to be secret to
# make the sessions secret but if you have multiple servers behind
# a load balancer, the key needs to be the same on each.
app.config[\(aqSECRET_KEY\(aq] = \(aqchange me!\(aq
# Other configuration options for Flask\-FAS\-OpenID:
# FAS_OPENID_ENDPOINT: the OpenID endpoint URL
# (default http://id.fedoraproject.org/)
# FAS_CHECK_CERT: check the SSL certificate of FAS (default True)
# You should use these options\(aq defaults for production applications!
app.config[\(aqFAS_OPENID_ENDPOINT\(aq] = \(aqhttp://id.fedoraproject.org/\(aq
app.config[\(aqFAS_CHECK_CERT\(aq] = True
# Inline templates keep this test application all in one file. Don\(aqt do this in
# a real application. Please.
TEMPLATE_START = """
Flask\-FAS\-OpenID test app
{% if g.fas_user %}
Hello, {{ g.fas_user.username }} —
Log out
{% else %}
You are not logged in —
Log in
{% endif %}
— Main page
"""
@app.route(\(aq/\(aq)
def index():
data = TEMPLATE_START
data += \(aqCheck if you are cla+1
\(aq % \e
flask.url_for(\(aqclaplusone\(aq)
data += \(aqSee a secret message (requires login)
\(aq % \e
flask.url_for(\(aqsecret\(aq)
return flask.render_template_string(data)
@app.route(\(aq/login\(aq, methods=[\(aqGET\(aq, \(aqPOST\(aq])
def auth_login():
# Your application should probably do some checking to make sure the URL
# given in the next request argument is sane. (For example, having next set
# to the login page will cause a redirect loop.) Some more information:
# http://flask.pocoo.org/snippets/62/
if \(aqnext\(aq in flask.request.args:
next_url = flask.request.args[\(aqnext\(aq]
else:
next_url = flask.url_for(\(aqindex\(aq)
# If user is already logged in, return them to where they were last
if flask.g.fas_user:
return flask.redirect(next_url)
return fas.login(return_url=next_url)
@app.route(\(aq/logout\(aq)
def logout():
if flask.g.fas_user:
fas.logout()
return flask.redirect(flask.url_for(\(aqindex\(aq))
# This demonstrates the use of the fas_login_required decorator. The
# secret message can only be viewed by those who are logged in.
@app.route(\(aq/secret\(aq)
@fas_login_required
def secret():
data = TEMPLATE_START + \(aqBe sure to drink your Ovaltine
\(aq
return flask.render_template_string(data)
# This demonstrates checking for group membership inside of a function.
# The flask_fas adapter also provides a cla_plus_one_required decorator that
# can restrict a url so that you can only access it from an account that has
# cla +1.
@app.route(\(aq/claplusone\(aq)
@cla_plus_one_required
def claplusone():
data = TEMPLATE_START
data += \(aqYour account is cla+1.
\(aq
return flask.render_template_string(data)
if __name__ == \(aq__main__\(aq:
app.run(debug=True)
.ft P
.fi
.UNINDENT
.UNINDENT
.SS FAS Who Plugin for TurboGears2
.SS FASWho Plugin
.INDENT 0.0
.TP
.B Authors
Luke Macken
Toshio Kuratomi
.TP
.B Date
3 September 2011
.UNINDENT
.sp
This plugin provides authentication to the Fedora Account System using the
\fIrepoze.who\fP WSGI middleware. It is designed for use with TurboGears2
but it may be used with any \fIrepoze.who\fP using application. Like
jsonfas2, faswho has builtin CSRF protection. This protection
is implemented as a second piece of middleware and may be used with other
\fIrepoze.who\fP authentication schemes.
.SS Authenticating against FAS with TurboGears2
.sp
Setting up authentication against FAS in TurboGears2 is very easy. It
requires one change to be made to \fBapp/config/app_cfg.py\fP\&. This change
will take care of registering faswho as the authentication provider, enabling
CSRF protection, switching \fBtg.url()\fP to use
\fBfedora.ta2g.utils.url()\fP instead, and allowing the \fI_csrf_token\fP
parameter to be given to any URL.
.SS Using CSRF middleware with other Auth Methods
.sp
This section needs to be made clearer so that apps like mirrormanager can be
ported to use this.
.SS Templates
.sp
The \fBfedora.tg2.utils\fP module contains some templates to help you
write CSRF aware login forms and buttons. You can use the
\fBfedora_template()\fP function to integrate them into your
templates:
.sp
The templates themselves come in two flavors. One set for use with mako and
one set for use with genshi.
.SS Mako
.sp
Mako version of templates to make adding certain Fedora widgets easier.
.SS \fI\%fedora.tg2.templates.mako.login.mak\fP
.sp
\fIModule author: Toshio Kuratomi <\fI\%tkuratom@redhat.com\fP>\fP
.sp
New in version 0.3.25.
.sp
Include this using:
.INDENT 0.0
.INDENT 3.5
.sp
.nf
.ft C
<%namespace name="fedora" file="${context[\(aqfedora_template\(aq](\(aqlogin.mak\(aq)}" />
.ft P
.fi
.UNINDENT
.UNINDENT
.INDENT 0.0
.TP
.B fedora.tg2.templates.mako.login.mak.loginform(message=\(aq\(aq)
.UNINDENT
.INDENT 0.0
.TP
.B kwarg message
Any text or elements contained by the tag will be shown
as a message to the user. This is generally used to show status of the
last login attempt ("Please provide your credentials", "Supplied
credentials were not correct", etc)
.UNINDENT
.sp
A function for generating the main login form. This is a CSRF
token\-aware login form that will prompt for username and password when no
session identity is present and ask the user to click a link if they merely
lack a token.
.sp
Typical usage, given the above import of the \fBlogin.mak\fP template would
be:
.INDENT 0.0
.INDENT 3.5
.sp
.nf
.ft C
${fedora.loginform()}
.ft P
.fi
.UNINDENT
.UNINDENT
.INDENT 0.0
.TP
.B fedora.tg2.templates.mako.login.mak.logintoolitem(href=None)
.UNINDENT
.INDENT 0.0
.TP
.B kwarg href
If an href is given, when a user is logged in, their username or
display_name will be a link to the URL.
.UNINDENT
.sp
This function creates an entry into a toolbar for logging into a web app.
The entry will contain the user\(aqs username and a logout button if the user is
logged in, a verify login button if the user has a session cookie but not
a CSRF token, or a login button if the user doesn\(aqt have a session
cookie.
.sp
Typical usage looks like this:
.INDENT 0.0
.INDENT 3.5
.sp
.nf
.ft C
.ft P
.fi
.UNINDENT
.UNINDENT
.SS \fI\%fedora.tg2.templates.mako.jsglobals.mak\fP
.sp
\fIModule author: Toshio Kuratomi <\fI\%tkuratom@redhat.com\fP>\fP
.sp
New in version 0.3.25.
.sp
Include this using:
.INDENT 0.0
.INDENT 3.5
.sp
.nf
.ft C
<%namespace name="jsglobals" file="${context[\(aqfedora_template\(aq](\(aqjsglobals.mak\(aq)}" />
.ft P
.fi
.UNINDENT
.UNINDENT
.INDENT 0.0
.TP
.B fedora.tg2.templates.mako.jsglobals.mak.jsglobals()
.UNINDENT
.sp
A function to add global variables to a page. Typically, you\(aqd include
this in your \fBmaster.mak\fP template and let the javascript variables
defined there propogate to every page on your site (since they all should
inherit from \fBmaster.mak\fP). This adds the following variables in the
fedora namespace for other scripts to access:
.INDENT 0.0
.INDENT 3.5
.INDENT 0.0
.TP
.B fedora.baseurl
URL fragment to prepend to any calls to the application.
In a TurboGears application, this is the scheme, host, and
server.webpath. Example: \fI\%https://admin.fedoraproject.org/pkgdb/\fP\&.
This may be a relative link.
.TP
.B fedora.identity.anonymous
If \fBtrue\fP, there will be no other variables
in the \fIfedora.identity\fP namespace. If \fBfalse\fP, these variables are
defined:
.TP
.B fedora.identity.userid
Numeric, unique identifier for the user
.TP
.B fedora.identity.username
Publically visible unique identifier for the
user
.TP
.B fedora.identity.display_name
Common human name for the user
.TP
.B fedora.identity.token
csrf token for this user\(aqs session to be added to
urls that query the server.
.UNINDENT
.UNINDENT
.UNINDENT
.sp
Typical usage would be:
.INDENT 0.0
.INDENT 3.5
.sp
.nf
.ft C
${jsglobals.jsglobals()}
.ft P
.fi
.UNINDENT
.UNINDENT
.SS Genshi
.sp
Genshi version of templates to make adding certain Fedora widgets easier.
.SS \fI\%fedora.tg2.templates.genshi.login.html\fP
.sp
\fIModule author: Toshio Kuratomi <\fI\%tkuratom@redhat.com\fP>\fP
.sp
New in version 0.3.26.
.INDENT 0.0
.TP
.B Include this using::
.UNINDENT
.INDENT 0.0
.TP
.B fedora.tg2.templates.genshi.login.html.loginform([message])
.UNINDENT
.INDENT 0.0
.TP
.B message
Any text or elements contained by the tag will be shown
as a message to the user. This is generally used to show status of the
last login attempt ("Please provide your credentials", "Supplied
credentials were not correct", etc)
.UNINDENT
.sp
A match template for the main login form. This is a CSRF token\-aware
login form that will prompt for username and password when no session identity
is present and ask the user to click a link if they merely lack a token.
.sp
Typical usage would be:
.INDENT 0.0
.INDENT 3.5
.sp
.nf
.ft C
${message}
.ft P
.fi
.UNINDENT
.UNINDENT
.INDENT 0.0
.TP
.B fedora.tg2.templates.genshi.login.html.logintoolitem(@href=URL)
.UNINDENT
.INDENT 0.0
.TP
.B @href
If an href attribute is present for this tag, when a user is
logged in, their username or display_name will be a link to the URL.
.UNINDENT
.sp
A match template to add an entry to a toolbar. The entry will contain the
user\(aqs username and a logout button if the user is logged in, a verify login
button if the user has a session cookie but not a CSRF token, or a
login button if the user doesn\(aqt have a session cookie.
.sp
Typical usage looks like this:
.INDENT 0.0
.INDENT 3.5
.sp
.nf
.ft C
.ft P
.fi
.UNINDENT
.UNINDENT
.SS \fI\%fedora.tg2.templates.genshi.jsglobals.html\fP
.sp
\fIModule author: Toshio Kuratomi <\fI\%tkuratom@redhat.com\fP>\fP
.sp
New in version 0.3.26.
.INDENT 0.0
.TP
.B Include this using::
.UNINDENT
.INDENT 0.0
.TP
.B fedora.tg2.templates.genshi.jsglobals.html.jsglobals()
.UNINDENT
.sp
A match template to add global variables to a page. Typically, you\(aqd include
this in your \fBmaster.html\fP template and let it be added to every other
page from there. This adds the following variables in the fedora namespace
for other scripts to access:
.INDENT 0.0
.INDENT 3.5
.INDENT 0.0
.TP
.B fedora.baseurl
URL fragment to prepend to any calls to the application.
In a TurboGears application, this is the scheme, host, and
server.webpath. Example: \fI\%https://admin.fedoraproject.org/pkgdb/\fP\&.
This may be a relative link.
.TP
.B fedora.identity.anonymous
If \fBtrue\fP, there will be no other variables
in the \fIfedora.identity\fP namespace. If \fBfalse\fP, these variables are
defined:
.TP
.B fedora.identity.userid
Numeric, unique identifier for the user
.TP
.B fedora.identity.username
Publically visible unique identifier for the
user
.TP
.B fedora.identity.display_name
Common human name for the user
.TP
.B fedora.identity.token
csrf token for this user\(aqs session to be added to
urls that query the server.
.UNINDENT
.UNINDENT
.UNINDENT
.sp
Typical usage would be:
.INDENT 0.0
.INDENT 3.5
.sp
.nf
.ft C
.ft P
.fi
.UNINDENT
.UNINDENT
.SH JAVASCRIPT
.INDENT 0.0
.TP
.B Authors
Toshio Kuratomi
.TP
.B Date
26 February 2009
.TP
.B For Version
0.3.x
.UNINDENT
.sp
python\-fedora currently provides some JavaScript files to make coding
Fedora\-Services easier. In the future we may move these to their own
package.
.SS \fI\%fedora.dojo\fP
.sp
\fIModule author: Toshio Kuratomi <\fI\%tkuratom@redhat.com\fP>\fP
.sp
New in version 0.3.10.
.sp
Dojo is one of several JavaScript Toolkits. It aims to be a standard
library for JavaScript. The \fI\%fedora.dojo\fP module has JavaScript code
that make use of Dojo to do their work. It is most appropriate to use
when the Dojo libraries are being used as the JavaScript library for
the app. However, it is well namespaced and nothing should prevent it from
being used in other apps as well.
.SH API DOCUMENTATION
.sp
This API Documentation is currently a catch\-all. We\(aqre going to merge the API
docs into the hand created docs as we have time to integrate them.
.SS Client
.sp
fedora.client is used to interact with Fedora Services.
.sp
Changed in version 0.3.21: Deprecate DictContainer in favor of bunch.Bunch
.sp
Changed in version 0.3.35: Add the openid clients
.sp
\fIModule author: Ricky Zhou <\fI\%ricky@fedoraproject.org\fP>\fP
.sp
\fIModule author: Luke Macken <\fI\%lmacken@redhat.com\fP>\fP
.sp
\fIModule author: Toshio Kuratomi <\fI\%tkuratom@redhat.com\fP>\fP
.INDENT 0.0
.TP
.B exception fedora.client.FedoraServiceError
Base Exception for any problem talking with the Service.
.sp
When the Client gets an error talking to the server, an exception of this
type is raised. This can be anything in the networking layer up to an
error returned from the server itself.
.UNINDENT
.INDENT 0.0
.TP
.B exception fedora.client.ServerError(url, status, msg)
Unable to talk to the server properly.
.sp
This includes network errors and 500 response codes. If the error was
generated from an http response, \fBcode\fP is the HTTP response code.
Otherwise, \fBcode\fP will be \-1.
.UNINDENT
.INDENT 0.0
.TP
.B exception fedora.client.AuthError
Error during authentication. For instance, invalid password.
.UNINDENT
.INDENT 0.0
.TP
.B exception fedora.client.AppError(name, message, extras=None)
Error condition that the server is passing back to the client.
.UNINDENT
.INDENT 0.0
.TP
.B exception fedora.client.FedoraClientError
Base Exception for problems which originate within the Clients.
.sp
This should be the base class for any exceptions that the Client generates
generate. For instance, if the client performs validation before passing
the data on to the Fedora Service.
.sp
Problems returned while talking to the Services should be returned via a
\fIFedoraServiceError\fP instead.
.UNINDENT
.INDENT 0.0
.TP
.B exception fedora.client.FASError
FAS Error
.UNINDENT
.INDENT 0.0
.TP
.B exception fedora.client.CLAError
CLA Error
.UNINDENT
.SS Generic Clients
.SS BaseClient
.INDENT 0.0
.TP
.B class fedora.client.BaseClient(base_url, useragent=None, debug=False, insecure=False, username=None, password=None, httpauth=None, session_cookie=None, session_id=None, session_name=\(aqtg\-visit\(aq, cache_session=True, retries=None, timeout=None)
A client for interacting with web services.
.INDENT 7.0
.TP
.B logout()
Logout from the server.
.UNINDENT
.INDENT 7.0
.TP
.B send_request(method, req_params=None, file_params=None, auth=False, retries=None, timeout=None, **kwargs)
Make an HTTP request to a server method.
.sp
The given method is called with any parameters set in req_params. If
auth is True, then the request is made with an authenticated session
cookie.
.INDENT 7.0
.TP
.B Parameters
.INDENT 7.0
.IP \(bu 2
\fBmethod\fP \-\- Method to call on the server. It\(aqs a url fragment that
comes after the base_url set in __init__().
.IP \(bu 2
\fBreq_params\fP \-\- Extra parameters to send to the server.
.IP \(bu 2
\fBfile_params\fP \-\- dict of files where the key is the name of the
file field used in the remote method and the value is the local
path of the file to be uploaded. If you want to pass multiple
files to a single file field, pass the paths as a list of paths.
.IP \(bu 2
\fBauth\fP \-\- If True perform auth to the server, else do not.
.IP \(bu 2
\fBretries\fP \-\- if we get an unknown or possibly transient error from
the server, retry this many times. Setting this to a negative
number makes it try forever. Default to use the \fBretries\fP
value set on the instance or in \fB__init__()\fP (which defaults
to zero, no retries).
.IP \(bu 2
\fBtimeout\fP \-\- A float describing the timeout of the connection. The
timeout only affects the connection process itself, not the
downloading of the response body. Default to use the
\fBtimeout\fP value set on the instance or in \fB__init__()\fP
(which defaults to 120s).
.UNINDENT
.TP
.B Return type
Bunch
.TP
.B Returns
The data from the server
.UNINDENT
.sp
Changed in version 0.3.21: * Return data as a Bunch instead of a DictContainer
* Add file_params to allow uploading files
.sp
Changed in version 0.3.33: * Added the timeout kwarg
.UNINDENT
.INDENT 7.0
.TP
.B session_cookie
\fIDeprecated\fP, use session_id instead.
.sp
The session cookie is saved in a file in case it is needed in
consecutive runs of BaseClient.
.UNINDENT
.INDENT 7.0
.TP
.B session_id
The session_id.
.sp
The session id is saved in a file in case it is needed in consecutive
runs of BaseClient.
.UNINDENT
.UNINDENT
.SS ProxyClient
.INDENT 0.0
.TP
.B class fedora.client.ProxyClient(base_url, useragent=None, session_name=\(aqtg\-visit\(aq, session_as_cookie=True, debug=False, insecure=False, retries=None, timeout=None)
A client to a Fedora Service. This class is optimized to proxy multiple
users to a service. ProxyClient is designed to be threadsafe so that
code can instantiate one instance of the class and use it for multiple
requests for different users from different threads.
.sp
If you want something that can manage one user\(aqs connection to a Fedora
Service, then look into using BaseClient instead.
.sp
This class has several attributes. These may be changed after
instantiation however, please note that this class is intended to be
threadsafe. Changing these values when another thread may affect more
than just the thread that you are making the change in. (For instance,
changing the debug option could cause other threads to start logging debug
messages in the middle of a method.)
.INDENT 7.0
.TP
.B base_url
Initial portion of the url to contact the server. It is highly
recommended not to change this value unless you know that no other
threads are accessing this \fI\%ProxyClient\fP instance.
.UNINDENT
.INDENT 7.0
.TP
.B useragent
Changes the useragent string that is reported to the web server.
.UNINDENT
.INDENT 7.0
.TP
.B session_name
Name of the cookie that holds the authentication value.
.UNINDENT
.INDENT 7.0
.TP
.B session_as_cookie
If \fBTrue\fP, then the session information is saved locally as
a cookie. This is here for backwards compatibility. New code should
set this to \fBFalse\fP when constructing the \fI\%ProxyClient\fP\&.
.UNINDENT
.INDENT 7.0
.TP
.B debug
If \fBTrue\fP, then more verbose logging is performed to aid in
debugging issues.
.UNINDENT
.INDENT 7.0
.TP
.B insecure
If \fBTrue\fP then the connection to the server is not checked to be
sure that any SSL certificate information is valid. That means that
a remote host can lie about who it is. Useful for development but
should not be used in production code.
.UNINDENT
.INDENT 7.0
.TP
.B retries
Setting this to a positive integer will retry failed requests to the
web server this many times. Setting to a negative integer will retry
forever.
.UNINDENT
.INDENT 7.0
.TP
.B timeout
.TP
.B A float describing the timeout of the connection. The timeout only
.TP
.B affects the connection process itself, not the downloading of the
.TP
.B response body. Defaults to 120 seconds.
.UNINDENT
.sp
Changed in version 0.3.33: Added the timeout attribute
.INDENT 7.0
.TP
.B debug
When True, we log extra debugging statements. When False, we only log
errors.
.UNINDENT
.INDENT 7.0
.TP
.B log =
.UNINDENT
.INDENT 7.0
.TP
.B send_request(method, req_params=None, auth_params=None, file_params=None, retries=None, timeout=None)
Make an HTTP request to a server method.
.sp
The given method is called with any parameters set in \fBreq_params\fP\&.
If auth is True, then the request is made with an authenticated session
cookie. Note that path parameters should be set by adding onto the
method, not via \fBreq_params\fP\&.
.INDENT 7.0
.TP
.B Parameters
.INDENT 7.0
.IP \(bu 2
\fBmethod\fP \-\- Method to call on the server. It\(aqs a url fragment that
comes after the base_url set in __init__(). Note that any
parameters set as extra path information should be listed here,
not in \fBreq_params\fP\&.
.IP \(bu 2
\fBreq_params\fP \-\- dict containing extra parameters to send to the
server
.IP \(bu 2
\fBauth_params\fP \-\-
.sp
dict containing one or more means of authenticating
to the server. Valid entries in this dict are:
.INDENT 2.0
.TP
.B cookie
\fBDeprecated\fP Use \fBsession_id\fP instead. If both
\fBcookie\fP and \fBsession_id\fP are set, only \fBsession_id\fP will
be used. A \fBCookie.SimpleCookie\fP to send as a session cookie
to the server
.TP
.B session_id
Session id to put in a cookie to construct an identity
for the server
.TP
.B username
Username to send to the server
.TP
.B password
Password to use with username to send to the server
.TP
.B httpauth
If set to \fBbasic\fP then use HTTP Basic Authentication
to send the username and password to the server. This may be
extended in the future to support other httpauth types than
\fBbasic\fP\&.
.UNINDENT
.sp
Note that cookie can be sent alone but if one of username or
password is set the other must as well. Code can set all of these
if it wants and all of them will be sent to the server. Be careful
of sending cookies that do not match with the username in this
case as the server can decide what to do in this case.
.IP \(bu 2
\fBfile_params\fP \-\- dict of files where the key is the name of the
file field used in the remote method and the value is the local
path of the file to be uploaded. If you want to pass multiple
files to a single file field, pass the paths as a list of paths.
.IP \(bu 2
\fBretries\fP \-\- if we get an unknown or possibly transient error from
the server, retry this many times. Setting this to a negative
number makes it try forever. Default to use the \fI\%retries\fP
value set on the instance or in \fB__init__()\fP\&.
.IP \(bu 2
\fBtimeout\fP \-\- A float describing the timeout of the connection. The
timeout only affects the connection process itself, not the
downloading of the response body. Defaults to the \fI\%timeout\fP
value set on the instance or in \fB__init__()\fP\&.
.UNINDENT
.TP
.B Returns
If ProxyClient is created with session_as_cookie=True (the
default), a tuple of session cookie and data from the server.
If ProxyClient was created with session_as_cookie=False, a tuple
of session_id and data instead.
.TP
.B Return type
tuple of session information and data from server
.UNINDENT
.sp
Changed in version 0.3.17: No longer send tg_format=json parameter. We rely solely on the
Accept: application/json header now.
.sp
Changed in version 0.3.21: * Return data as a Bunch instead of a DictContainer
* Add file_params to allow uploading files
.sp
Changed in version 0.3.33: Added the timeout kwarg
.UNINDENT
.UNINDENT
.SS OpenIdBaseClient
.INDENT 0.0
.TP
.B class fedora.client.OpenIdBaseClient(base_url, login_url=None, useragent=None, debug=False, insecure=False, openid_insecure=False, username=None, cache_session=True, retries=None, timeout=None, retry_backoff_factor=0)
A client for interacting with web services relying on openid auth.
.INDENT 7.0
.TP
.B has_cookies()
.UNINDENT
.INDENT 7.0
.TP
.B login(username, password, otp=None)
Open a session for the user.
.sp
Log in the user with the specified username and password
against the FAS OpenID server.
.INDENT 7.0
.TP
.B Parameters
.INDENT 7.0
.IP \(bu 2
\fBusername\fP \-\- the FAS username of the user that wants to log in
.IP \(bu 2
\fBpassword\fP \-\- the FAS password of the user that wants to log in
.IP \(bu 2
\fBotp\fP \-\- currently unused. Eventually a way to send an otp to the
API that the API can use.
.UNINDENT
.UNINDENT
.UNINDENT
.INDENT 7.0
.TP
.B send_request(method, auth=False, verb=\(aqPOST\(aq, **kwargs)
Make an HTTP request to a server method.
.sp
The given method is called with any parameters set in req_params. If
auth is True, then the request is made with an authenticated session
cookie.
.INDENT 7.0
.TP
.B Parameters
.INDENT 7.0
.IP \(bu 2
\fBmethod\fP \-\- Method to call on the server. It\(aqs a url fragment that
comes after the \fBbase_url\fP set in \fB__init__()\fP\&.
.IP \(bu 2
\fBauth\fP \-\- If True perform auth to the server, else do not.
.IP \(bu 2
\fBreq_params\fP \-\- Extra parameters to send to the server.
.IP \(bu 2
\fBfile_params\fP \-\- dict of files where the key is the name of the
file field used in the remote method and the value is the local
path of the file to be uploaded. If you want to pass multiple
files to a single file field, pass the paths as a list of paths.
.IP \(bu 2
\fBverb\fP \-\- HTTP verb to use. GET and POST are currently supported.
POST is the default.
.UNINDENT
.UNINDENT
.UNINDENT
.INDENT 7.0
.TP
.B session_key
.UNINDENT
.UNINDENT
.INDENT 0.0
.TP
.B fedora.client.openidbaseclient.requires_login(func)
Decorator function for get or post requests requiring login.
.sp
Decorate a controller method that requires the user to be authenticated.
Example:
.INDENT 7.0
.INDENT 3.5
.sp
.nf
.ft C
from fedora.client.openidbaseclient import requires_login
@requires_login
def rename_user(new_name):
user = new_name
# [...]
.ft P
.fi
.UNINDENT
.UNINDENT
.UNINDENT
.SS OpenIdProxyClient
.INDENT 0.0
.TP
.B class fedora.client.OpenIdProxyClient(base_url, login_url=None, useragent=None, session_name=\(aqsession\(aq, debug=False, insecure=False, openid_insecure=False, retries=None, timeout=None)
A client to a Fedora Service. This class is optimized to proxy multiple
users to a service. OpenIdProxyClient is designed to be usable by code
that creates a single instance of this class and uses it in multiple
threads. However it is not completely threadsafe. See the information
on setting attributes below.
.sp
If you want something that can manage one user\(aqs connection to a Fedora
Service, then look into using \fI\%OpenIdBaseClient\fP
instead.
.sp
This class has several attributes. These may be changed after
instantiation. Please note, however, that changing these values when
another thread is utilizing the same instance may affect more than just
the thread that you are making the change in. (For instance, changing
the debug option could cause other threads to start logging debug
messages in the middle of a method.)
.INDENT 7.0
.TP
.B base_url
Initial portion of the url to contact the server. It is highly
recommended not to change this value unless you know that no other
threads are accessing this \fI\%OpenIdProxyClient\fP instance.
.UNINDENT
.INDENT 7.0
.TP
.B useragent
Changes the useragent string that is reported to the web server.
.UNINDENT
.INDENT 7.0
.TP
.B session_name
Name of the cookie that holds the authentication value.
.UNINDENT
.INDENT 7.0
.TP
.B debug
If \fBTrue\fP, then more verbose logging is performed to aid in
debugging issues.
.UNINDENT
.INDENT 7.0
.TP
.B insecure
If \fBTrue\fP then the connection to the server is not checked to be
sure that any SSL certificate information is valid. That means that
a remote host can lie about who it is. Useful for development but
should not be used in production code.
.UNINDENT
.INDENT 7.0
.TP
.B retries
Setting this to a positive integer will retry failed requests to the
web server this many times. Setting to a negative integer will retry
forever.
.UNINDENT
.INDENT 7.0
.TP
.B timeout
A float describing the timeout of the connection. The timeout only
affects the connection process itself, not the downloading of the
response body. Defaults to 120 seconds.
.UNINDENT
.INDENT 7.0
.TP
.B debug
When True, we log extra debugging statements. When False, we only log
errors.
.UNINDENT
.INDENT 7.0
.TP
.B login(username, password, otp=None)
Open a session for the user.
.sp
Log in the user with the specified username and password
against the FAS OpenID server.
.INDENT 7.0
.TP
.B Parameters
.INDENT 7.0
.IP \(bu 2
\fBusername\fP \-\- the FAS username of the user that wants to log in
.IP \(bu 2
\fBpassword\fP \-\- the FAS password of the user that wants to log in
.IP \(bu 2
\fBotp\fP \-\- currently unused. Eventually a way to send an otp to the
API that the API can use.
.UNINDENT
.TP
.B Returns
a tuple containing both the response from the OpenID
provider and the session used to by this provider.
.UNINDENT
.UNINDENT
.INDENT 7.0
.TP
.B send_request(method, verb=\(aqPOST\(aq, req_params=None, auth_params=None, file_params=None, retries=None, timeout=None, headers=None)
Make an HTTP request to a server method.
.sp
The given method is called with any parameters set in \fBreq_params\fP\&.
If auth is True, then the request is made with an authenticated
session cookie. Note that path parameters should be set by adding
onto the method, not via \fBreq_params\fP\&.
.INDENT 7.0
.TP
.B Parameters
.INDENT 7.0
.IP \(bu 2
\fBmethod\fP \-\- Method to call on the server. It\(aqs a url fragment that
comes after the base_url set in __init__(). Note that any
parameters set as extra path information should be listed here,
not in \fBreq_params\fP\&.
.IP \(bu 2
\fBreq_params\fP \-\- dict containing extra parameters to send to the
server
.IP \(bu 2
\fBauth_params\fP \-\-
.sp
dict containing one or more means of
authenticating to the server. Valid entries in this dict are:
.INDENT 2.0
.TP
.B cookie
\fBDeprecated\fP Use \fBsession_id\fP instead. If both
\fBcookie\fP and \fBsession_id\fP are set, only \fBsession_id\fP
will be used. A \fBCookie.SimpleCookie\fP to send as a
session cookie to the server
.TP
.B session_id
Session id to put in a cookie to construct an
identity for the server
.TP
.B username
Username to send to the server
.TP
.B password
Password to use with username to send to the server
.TP
.B httpauth
If set to \fBbasic\fP then use HTTP Basic Authentication
to send the username and password to the server. This may
be extended in the future to support other httpauth types
than \fBbasic\fP\&.
.UNINDENT
.sp
Note that cookie can be sent alone but if one of username or
password is set the other must as well. Code can set all of
these if it wants and all of them will be sent to the server.
Be careful of sending cookies that do not match with the
username in this case as the server can decide what to do in
this case.
.IP \(bu 2
\fBfile_params\fP \-\- dict of files where the key is the name of the
file field used in the remote method and the value is the local
path of the file to be uploaded. If you want to pass multiple
files to a single file field, pass the paths as a list of paths.
.IP \(bu 2
\fBretries\fP \-\- if we get an unknown or possibly transient error
from the server, retry this many times. Setting this to a
negative number makes it try forever. Default to use the
\fI\%retries\fP value set on the instance or in \fB__init__()\fP\&.
.IP \(bu 2
\fBtimeout\fP \-\- A float describing the timeout of the connection.
The timeout only affects the connection process itself, not the
downloading of the response body. Defaults to the \fI\%timeout\fP
value set on the instance or in \fB__init__()\fP\&.
.IP \(bu 2
\fBheaders\fP \-\- A dictionary containing specific headers to add to
the request made.
.UNINDENT
.TP
.B Returns
A tuple of session_id and data.
.TP
.B Return type
tuple of session information and data from server
.UNINDENT
.UNINDENT
.UNINDENT
.SS Clients for Specific Services
.SS Wiki
.INDENT 0.0
.TP
.B class fedora.client.Wiki(base_url=\(aqhttps://fedoraproject.org/w/\(aq, *args, **kwargs)
.INDENT 7.0
.TP
.B api_high_limits = False
.UNINDENT
.INDENT 7.0
.TP
.B check_api_limits()
Checks whether you have the \(aqapihighlimits\(aq right or not.
.UNINDENT
.INDENT 7.0
.TP
.B fetch_all_revisions(start=1, flags=True, timestamp=True, user=True, size=False, comment=True, content=False, title=True, ignore_imported_revs=True, ignore_wikibot=False, callback=None)
Fetch data for all revisions. This could take a long time. You can
start at a specific revision by modifying the \(aqstart\(aq keyword argument.
.sp
To ignore revisions made by "ImportUser" and "Admin" set
ignore_imported_revs to True (this is the default). To ignore edits
made by Wikibot set ignore_wikibot to True (False is the default).
.sp
Modifying the remainder of the keyword arguments will return less/more
data.
.UNINDENT
.INDENT 7.0
.TP
.B get_recent_changes(now, then, limit=500)
Get recent wiki changes from \fInow\fP until \fIthen\fP
.UNINDENT
.INDENT 7.0
.TP
.B login(username, password)
.UNINDENT
.INDENT 7.0
.TP
.B print_recent_changes(days=7, show=10)
.UNINDENT
.UNINDENT
.SS Service
.SS Transforming SQLAlchemy Objects into JSON
.SH GLOSSARY
.INDENT 0.0
.TP
.B controller
In MVC design, the controller is in charge of things. It takes
processes events and decides what data to ask the \fI\%model\fP for,
manipulates the data according to the information in the event, and
decides which \fI\%view\fP to send the results to to be rendered.
.TP
.B CSRF
\fI\%Cross\-site request forgery\fP is a
technique where a malicious website can gain access to another web
site by hijaacking a currently open session that the user has open to
the site. This technique can also affect identification via SSL
Certificates or anything else that the browser sends to the server
automatically when a request is made.
.sp
\fBSEE ALSO:\fP
.INDENT 7.0
.INDENT 3.5
CSRF\-Protection
.UNINDENT
.UNINDENT
.TP
.B Dojo
Dojo is a JavaScript toolkit that aims to be a standard library for
JavaScript. It provides a small core library with useful functions
and an expanded set of scripts that can be added that provide widgets
and other features.
.sp
\fBSEE ALSO:\fP
.INDENT 7.0
.INDENT 3.5
\fI\%http://www.dojotoolkit.org\fP
.UNINDENT
.UNINDENT
.TP
.B double submit
A strategy to foil \fI\%CSRF\fP attacks. This strategy involves
sending the value of the authentication cookie (or something derivable
only from knowing the value of the authentication cookie) in the body
of the request. Since the \fI\%Same Origin Policy\fP prevents a web
site other than the one originating the cookie from reading what\(aqs in
the cookie, the server can be reasonably assured that the request does
not originate from an unknown request on another website. Note that
this and other anti\-CSRF measures do not protect against spoofing or
getting a user to actively click on a link on an attacked website by
mistake.
.TP
.B flask
A simple Python web framework that we\(aqre using in parts of Fedora
Infrastructure. It provides good documentation and simplicity in its
design.
.sp
\fBSEE ALSO:\fP
.INDENT 7.0
.INDENT 3.5
\fI\%http://flask.pocoo.org/docs/\fP
.UNINDENT
.UNINDENT
.TP
.B JSON
\fI\%JavaScript Object Notation\fP is a format for
marshalling data. It is based on a subset of JavaScript that is used
to declare objects. Compared to xml, JSON is a lightweight, easily
parsed format.
.sp
\fBSEE ALSO:\fP
.INDENT 7.0
.INDENT 3.5
\fI\%Wikipedia\(aqs JSON Entry\fP
.UNINDENT
.UNINDENT
.TP
.B model
In MVC design, the layer that deals directly with the data.
.TP
.B OpenID
A specification for single sign on to web services where the
authentication server and the application seeking to have the user
authenticated do not need to have complete trust in each other.
.sp
\fBSEE ALSO:\fP
.INDENT 7.0
.INDENT 3.5
\fI\%http://openid.net/get\-an\-openid/what\-is\-openid/\fP
.UNINDENT
.UNINDENT
.TP
.B Same Origin Policy
A web browser security policy that prevents one website from reading:
1) the cookies from another website
2) the response body from another website
.sp
\fBSEE ALSO:\fP
.INDENT 7.0
.INDENT 3.5
\fI\%http://en.wikipedia.org/wiki/Same_origin_policy\fP
.UNINDENT
.UNINDENT
.TP
.B single sign\-on
A feature that allows one login to authenticate a user for multiple
applications. So logging into one application will authenticate you
for all the applications that support the same single\-sign\-on
infrastructure.
.TP
.B TurboGears
A Python web framework that most of Fedora Infrastructure\(aqs apps are
built on.
.sp
\fBSEE ALSO:\fP
.INDENT 7.0
.INDENT 3.5
\fI\%http://www.turbogears.org/\fP
.UNINDENT
.UNINDENT
.TP
.B TurboGears2
The successor to \fI\%TurboGears\fP, TurboGears2 provides a very
similar framework to coders but has some notable differences. It is
based on pylons and paste so it is much more tightly integrated with
\fI\%WSGI\fP\&. The differences with :ref\(gaTurboGears\(ga1 are largely with
the organization of code and how to configure the application.
.sp
\fBSEE ALSO:\fP
.INDENT 7.0
.INDENT 3.5
\fI\%http://www.turbogears.org/\fP
.UNINDENT
.UNINDENT
.TP
.B view
In MVC design, the layer that takes care of formatting and rendering
data for the consumer. This could be displaying the data as an html
page or marshalling it into \fI\%JSON\fP objects.
.TP
.B WSGI
WSGI is an interface between web servers and web frameworks that
originated in the Python community. WSGI lets different components
embed each other even if they were originally written for different
python web frameworks.
.sp
\fBSEE ALSO:\fP
.INDENT 7.0
.INDENT 3.5
\fI\%http://en.wikipedia.org/wiki/Web_Server_Gateway_Interface\fP
.UNINDENT
.UNINDENT
.UNINDENT
.INDENT 0.0
.IP \(bu 2
glossary
.IP \(bu 2
genindex
.IP \(bu 2
modindex
.IP \(bu 2
search
.UNINDENT
.SH COPYRIGHT
2007-2016 Red Hat, Inc.
.\" Generated by docutils manpage writer.
.