Remove the files implementing python-fedora hotfixes. They're all in the latest packages
This commit is contained in:
parent
c3ed8a0145
commit
741ee1ce5f
3 changed files with 0 additions and 936 deletions
|
@ -1,496 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright (C) 2009-2013 Red Hat, Inc.
|
|
||||||
# This file is part of python-fedora
|
|
||||||
#
|
|
||||||
# python-fedora is free software; you can redistribute it and/or
|
|
||||||
# modify it under the terms of the GNU Lesser General Public
|
|
||||||
# License as published by the Free Software Foundation; either
|
|
||||||
# version 2.1 of the License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# python-fedora is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
# Lesser General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Lesser General Public
|
|
||||||
# License along with python-fedora; if not, see <http://www.gnu.org/licenses/>
|
|
||||||
#
|
|
||||||
'''Implement a class that sets up simple communication to a Fedora Service.
|
|
||||||
|
|
||||||
.. moduleauthor:: Luke Macken <lmacken@redhat.com>
|
|
||||||
.. moduleauthor:: Toshio Kuratomi <tkuratom@redhat.com>
|
|
||||||
'''
|
|
||||||
|
|
||||||
import Cookie
|
|
||||||
import copy
|
|
||||||
import urllib
|
|
||||||
import httplib
|
|
||||||
import logging
|
|
||||||
# For handling an exception that's coming from requests:
|
|
||||||
import ssl
|
|
||||||
import time
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
try:
|
|
||||||
from urlparse import urljoin
|
|
||||||
from urlparse import urlparse
|
|
||||||
except ImportError:
|
|
||||||
# Python3 support
|
|
||||||
from urllib.parse import urljoin
|
|
||||||
from urllib.parse import urlparse
|
|
||||||
|
|
||||||
try:
|
|
||||||
from hashlib import sha1 as sha_constructor
|
|
||||||
except ImportError:
|
|
||||||
from sha import new as sha_constructor
|
|
||||||
|
|
||||||
from bunch import bunchify
|
|
||||||
from kitchen.text.converters import to_bytes
|
|
||||||
import requests
|
|
||||||
# For handling an exception that's coming from requests:
|
|
||||||
import urllib3
|
|
||||||
|
|
||||||
from fedora import __version__, b_
|
|
||||||
from fedora.client import AppError, AuthError, ServerError
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class ProxyClient(object):
|
|
||||||
# pylint: disable-msg=R0903
|
|
||||||
'''
|
|
||||||
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.
|
|
||||||
|
|
||||||
If you want something that can manage one user's connection to a Fedora
|
|
||||||
Service, then look into using BaseClient instead.
|
|
||||||
|
|
||||||
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.)
|
|
||||||
|
|
||||||
.. attribute:: 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 :class:`ProxyClient` instance.
|
|
||||||
|
|
||||||
.. attribute:: useragent
|
|
||||||
|
|
||||||
Changes the useragent string that is reported to the web server.
|
|
||||||
|
|
||||||
.. attribute:: session_name
|
|
||||||
|
|
||||||
Name of the cookie that holds the authentication value.
|
|
||||||
|
|
||||||
.. attribute:: session_as_cookie
|
|
||||||
|
|
||||||
If :data:`True`, then the session information is saved locally as
|
|
||||||
a cookie. This is here for backwards compatibility. New code should
|
|
||||||
set this to :data:`False` when constructing the :class:`ProxyClient`.
|
|
||||||
|
|
||||||
.. attribute:: debug
|
|
||||||
|
|
||||||
If :data:`True`, then more verbose logging is performed to aid in
|
|
||||||
debugging issues.
|
|
||||||
|
|
||||||
.. attribute:: insecure
|
|
||||||
|
|
||||||
If :data:`True` 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.
|
|
||||||
|
|
||||||
.. attribute:: 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.
|
|
||||||
|
|
||||||
.. attribute:: 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.
|
|
||||||
|
|
||||||
.. versionchanged:: 0.3.33
|
|
||||||
Added the timeout attribute
|
|
||||||
'''
|
|
||||||
log = log
|
|
||||||
|
|
||||||
def __init__(self, base_url, useragent=None, session_name='tg-visit',
|
|
||||||
session_as_cookie=True, debug=False, insecure=False, retries=None,
|
|
||||||
timeout=None):
|
|
||||||
'''Create a client configured for a particular service.
|
|
||||||
|
|
||||||
:arg base_url: Base of every URL used to contact the server
|
|
||||||
|
|
||||||
:kwarg useragent: useragent string to use. If not given, default to
|
|
||||||
"Fedora ProxyClient/VERSION"
|
|
||||||
:kwarg session_name: name of the cookie to use with session handling
|
|
||||||
:kwarg session_as_cookie: If set to True, return the session as a
|
|
||||||
SimpleCookie. If False, return a session_id. This flag allows us
|
|
||||||
to maintain compatibility for the 0.3 branch. In 0.4, code will
|
|
||||||
have to deal with session_id's instead of cookies.
|
|
||||||
:kwarg debug: If True, log debug information
|
|
||||||
:kwarg insecure: If True, do not check server certificates against
|
|
||||||
their CA's. This means that man-in-the-middle attacks are
|
|
||||||
possible against the `BaseClient`. You might turn this option on
|
|
||||||
for testing against a local version of a server with a self-signed
|
|
||||||
certificate but it should be off in production.
|
|
||||||
:kwarg retries: 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. Defaults to zero, no retries.
|
|
||||||
:kwarg 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.
|
|
||||||
|
|
||||||
.. versionchanged:: 0.3.33
|
|
||||||
Added the timeout kwarg
|
|
||||||
'''
|
|
||||||
# Setup our logger
|
|
||||||
self._log_handler = logging.StreamHandler()
|
|
||||||
self.debug = debug
|
|
||||||
format = logging.Formatter("%(message)s")
|
|
||||||
self._log_handler.setFormatter(format)
|
|
||||||
self.log.addHandler(self._log_handler)
|
|
||||||
|
|
||||||
# When we are instantiated, go ahead and silence the python-requests
|
|
||||||
# log. It is kind of noisy in our app server logs.
|
|
||||||
if not debug:
|
|
||||||
requests_log = logging.getLogger("requests")
|
|
||||||
requests_log.setLevel(logging.WARN)
|
|
||||||
|
|
||||||
self.log.debug(b_('proxyclient.__init__:entered'))
|
|
||||||
if base_url[-1] != '/':
|
|
||||||
base_url = base_url +'/'
|
|
||||||
self.base_url = base_url
|
|
||||||
self.domain = urlparse(self.base_url).netloc
|
|
||||||
self.useragent = useragent or 'Fedora ProxyClient/%(version)s' % {
|
|
||||||
'version': __version__}
|
|
||||||
self.session_name = session_name
|
|
||||||
self.session_as_cookie = session_as_cookie
|
|
||||||
if session_as_cookie:
|
|
||||||
warnings.warn(b_('Returning cookies from send_request() is'
|
|
||||||
' deprecated and will be removed in 0.4. Please port your'
|
|
||||||
' code to use a session_id instead by calling the ProxyClient'
|
|
||||||
' constructor with session_as_cookie=False'),
|
|
||||||
DeprecationWarning, stacklevel=2)
|
|
||||||
self.insecure = insecure
|
|
||||||
|
|
||||||
# Have to do retries and timeout default values this way as BaseClient
|
|
||||||
# sends None for these values if not overridden by the user.
|
|
||||||
if retries is None:
|
|
||||||
self.retries = 0
|
|
||||||
else:
|
|
||||||
self.retries = retries
|
|
||||||
if timeout is None:
|
|
||||||
self.timeout = 120.0
|
|
||||||
else:
|
|
||||||
self.timeout = timeout
|
|
||||||
self.log.debug(b_('proxyclient.__init__:exited'))
|
|
||||||
|
|
||||||
def __get_debug(self):
|
|
||||||
'''Return whether we have debug logging turned on.
|
|
||||||
|
|
||||||
:Returns: True if debugging is on, False otherwise.
|
|
||||||
'''
|
|
||||||
if self._log_handler.level <= logging.DEBUG:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def __set_debug(self, debug=False):
|
|
||||||
'''Change debug level.
|
|
||||||
|
|
||||||
:kwarg debug: A true value to turn debugging on, false value to turn it
|
|
||||||
off.
|
|
||||||
'''
|
|
||||||
if debug:
|
|
||||||
self.log.setLevel(logging.DEBUG)
|
|
||||||
self._log_handler.setLevel(logging.DEBUG)
|
|
||||||
else:
|
|
||||||
self.log.setLevel(logging.ERROR)
|
|
||||||
self._log_handler.setLevel(logging.INFO)
|
|
||||||
|
|
||||||
debug = property(__get_debug, __set_debug, doc='''
|
|
||||||
When True, we log extra debugging statements. When False, we only log
|
|
||||||
errors.
|
|
||||||
''')
|
|
||||||
|
|
||||||
def send_request(self, method, req_params=None, auth_params=None,
|
|
||||||
file_params=None, retries=None, timeout=None):
|
|
||||||
'''Make an HTTP request to a server method.
|
|
||||||
|
|
||||||
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. Note that path parameters should be set by adding onto the
|
|
||||||
method, not via ``req_params``.
|
|
||||||
|
|
||||||
:arg method: Method to call on the server. It's 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 ``req_params``.
|
|
||||||
:kwarg req_params: dict containing extra parameters to send to the
|
|
||||||
server
|
|
||||||
:kwarg auth_params: dict containing one or more means of authenticating
|
|
||||||
to the server. Valid entries in this dict are:
|
|
||||||
|
|
||||||
:cookie: **Deprecated** Use ``session_id`` instead. If both
|
|
||||||
``cookie`` and ``session_id`` are set, only ``session_id`` will
|
|
||||||
be used. A ``Cookie.SimpleCookie`` to send as a session cookie
|
|
||||||
to the server
|
|
||||||
:session_id: Session id to put in a cookie to construct an identity
|
|
||||||
for the server
|
|
||||||
:username: Username to send to the server
|
|
||||||
:password: Password to use with username to send to the server
|
|
||||||
:httpauth: If set to ``basic`` 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
|
|
||||||
``basic``.
|
|
||||||
|
|
||||||
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.
|
|
||||||
:kwarg file_params: 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.
|
|
||||||
:kwarg retries: 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 :attr:`retries`
|
|
||||||
value set on the instance or in :meth:`__init__`.
|
|
||||||
:kwarg 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 the :attr:`timeout`
|
|
||||||
value set on the instance or in :meth:`__init__`.
|
|
||||||
: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.
|
|
||||||
:rtype: tuple of session information and data from server
|
|
||||||
|
|
||||||
.. versionchanged:: 0.3.17
|
|
||||||
No longer send tg_format=json parameter. We rely solely on the
|
|
||||||
Accept: application/json header now.
|
|
||||||
.. versionchanged:: 0.3.21
|
|
||||||
* Return data as a Bunch instead of a DictContainer
|
|
||||||
* Add file_params to allow uploading files
|
|
||||||
.. versionchanged:: 0.3.33
|
|
||||||
Added the timeout kwarg
|
|
||||||
'''
|
|
||||||
self.log.debug(b_('proxyclient.send_request: entered'))
|
|
||||||
|
|
||||||
# parameter mangling
|
|
||||||
file_params = file_params or {}
|
|
||||||
|
|
||||||
# Check whether we need to authenticate for this request
|
|
||||||
session_id = None
|
|
||||||
username = None
|
|
||||||
password = None
|
|
||||||
if auth_params:
|
|
||||||
if 'session_id' in auth_params:
|
|
||||||
session_id = auth_params['session_id']
|
|
||||||
elif 'cookie' in auth_params:
|
|
||||||
warnings.warn(b_('Giving a cookie to send_request() to'
|
|
||||||
' authenticate is deprecated and will be removed in 0.4.'
|
|
||||||
' Please port your code to use session_id instead.'),
|
|
||||||
DeprecationWarning, stacklevel=2)
|
|
||||||
session_id = auth_params['cookie'].output(attrs=[],
|
|
||||||
header='').strip()
|
|
||||||
if 'username' in auth_params and 'password' in auth_params:
|
|
||||||
username = auth_params['username']
|
|
||||||
password = auth_params['password']
|
|
||||||
elif 'username' in auth_params or 'password' in auth_params:
|
|
||||||
raise AuthError(b_('username and password must both be set in'
|
|
||||||
' auth_params'))
|
|
||||||
if not (session_id or username):
|
|
||||||
raise AuthError(b_('No known authentication methods'
|
|
||||||
' specified: set "cookie" in auth_params or set both'
|
|
||||||
' username and password in auth_params'))
|
|
||||||
|
|
||||||
# urljoin is slightly different than os.path.join(). Make sure method
|
|
||||||
# will work with it.
|
|
||||||
method = method.lstrip('/')
|
|
||||||
# And join to make our url.
|
|
||||||
url = urljoin(self.base_url, urllib.quote(method))
|
|
||||||
|
|
||||||
data = None # decoded JSON via json.load()
|
|
||||||
|
|
||||||
# Set standard headers
|
|
||||||
headers = {
|
|
||||||
'User-agent': self.useragent,
|
|
||||||
'Accept': 'application/json',
|
|
||||||
}
|
|
||||||
|
|
||||||
# Files to upload
|
|
||||||
for field_name, local_file_name in file_params:
|
|
||||||
file_params[field_name] = open(local_file_name, 'rb')
|
|
||||||
|
|
||||||
cookies = requests.cookies.RequestsCookieJar()
|
|
||||||
# If we have a session_id, send it
|
|
||||||
if session_id:
|
|
||||||
# Anytime the session_id exists, send it so that visit tracking
|
|
||||||
# works. Will also authenticate us if there's a need. Note that
|
|
||||||
# there's no need to set other cookie attributes because this is a
|
|
||||||
# cookie generated client-side.
|
|
||||||
cookies.set(self.session_name, session_id)
|
|
||||||
|
|
||||||
complete_params = req_params or {}
|
|
||||||
if session_id:
|
|
||||||
# Add the csrf protection token
|
|
||||||
token = sha_constructor(session_id)
|
|
||||||
complete_params.update({'_csrf_token': token.hexdigest()})
|
|
||||||
|
|
||||||
auth = None
|
|
||||||
if username and password:
|
|
||||||
if auth_params.get('httpauth', '').lower() == 'basic':
|
|
||||||
# HTTP Basic auth login
|
|
||||||
auth = (username, password)
|
|
||||||
else:
|
|
||||||
# TG login
|
|
||||||
# Adding this to the request data prevents it from being logged by
|
|
||||||
# apache.
|
|
||||||
complete_params.update({
|
|
||||||
'user_name': to_bytes(username),
|
|
||||||
'password': to_bytes(password),
|
|
||||||
'login': 'Login',
|
|
||||||
})
|
|
||||||
|
|
||||||
# If debug, give people our debug info
|
|
||||||
self.log.debug(b_('Creating request %(url)s') %
|
|
||||||
{'url': to_bytes(url)})
|
|
||||||
self.log.debug(b_('Headers: %(header)s') %
|
|
||||||
{'header': to_bytes(headers, nonstring='simplerepr')})
|
|
||||||
if self.debug and complete_params:
|
|
||||||
debug_data = copy.deepcopy(complete_params)
|
|
||||||
|
|
||||||
if 'password' in debug_data:
|
|
||||||
debug_data['password'] = 'xxxxxxx'
|
|
||||||
|
|
||||||
self.log.debug(b_('Data: %r') % debug_data)
|
|
||||||
|
|
||||||
if retries is None:
|
|
||||||
retries = self.retries
|
|
||||||
|
|
||||||
if timeout is None:
|
|
||||||
timeout = self.timeout
|
|
||||||
|
|
||||||
num_tries = 0
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
response = requests.post(
|
|
||||||
url,
|
|
||||||
data=complete_params,
|
|
||||||
cookies=cookies,
|
|
||||||
headers=headers,
|
|
||||||
auth=auth,
|
|
||||||
verify=not self.insecure,
|
|
||||||
timeout=timeout,
|
|
||||||
)
|
|
||||||
except (requests.Timeout, requests.exceptions.SSLError) as e:
|
|
||||||
if isinstance(e, requests.exceptions.SSLError):
|
|
||||||
# And now we know how not to code a library exception
|
|
||||||
# hierarchy... We're expecting that requests is raising
|
|
||||||
# the following stupidity:
|
|
||||||
# requests.exceptions.SSLError(
|
|
||||||
# urllib3.exceptions.SSLError(
|
|
||||||
# ssl.SSLError('The read operation timed out')))
|
|
||||||
# If we weren't interested in reraising the exception with
|
|
||||||
# full tracdeback we could use a try: except instead of
|
|
||||||
# this gross conditional. But we need to code defensively
|
|
||||||
# because we don't want to raise an unrelated exception
|
|
||||||
# here and if requests/urllib3 can do this sort of
|
|
||||||
# nonsense, they may change the nonsense in the future
|
|
||||||
if not (e.args and isinstance(e.args[0],
|
|
||||||
urllib3.exceptions.SSLError)
|
|
||||||
and e.args[0].args
|
|
||||||
and isinstance(e.args[0].args[0], ssl.SSLError)
|
|
||||||
and e.args[0].args[0].args
|
|
||||||
and 'timed out' in e.args[0].args[0].args[0]):
|
|
||||||
# We're only interested in timeouts here
|
|
||||||
raise
|
|
||||||
self.log.debug(b_('Request timed out'))
|
|
||||||
if retries < 0 or num_tries < retries:
|
|
||||||
num_tries += 1
|
|
||||||
self.log.debug(b_('Attempt #%(try)s failed') % {'try': num_tries})
|
|
||||||
time.sleep(0.5)
|
|
||||||
continue
|
|
||||||
# Fail and raise an error
|
|
||||||
# Raising our own exception protects the user from the
|
|
||||||
# implementation detail of requests vs pycurl vs urllib
|
|
||||||
raise ServerError(url, -1, 'Request timed out after %s seconds' % timeout)
|
|
||||||
|
|
||||||
# When the python-requests module gets a response, it attempts to
|
|
||||||
# guess the encoding using chardet (or a fork)
|
|
||||||
# That process can take an extraordinarily long time for long
|
|
||||||
# response.text strings.. upwards of 30 minutes for FAS queries to
|
|
||||||
# /accounts/user/list JSON api! Therefore, we cut that codepath
|
|
||||||
# off at the pass by assuming that the response is 'utf-8'. We can
|
|
||||||
# make that assumption because we're only interfacing with servers
|
|
||||||
# that we run (and we know that they all return responses
|
|
||||||
# encoded 'utf-8').
|
|
||||||
response.encoding = 'utf-8'
|
|
||||||
|
|
||||||
# Check for auth failures
|
|
||||||
# Note: old TG apps returned 403 Forbidden on authentication failures.
|
|
||||||
# Updated apps return 401 Unauthorized
|
|
||||||
# We need to accept both until all apps are updated to return 401.
|
|
||||||
http_status = response.status_code
|
|
||||||
if http_status in (401, 403):
|
|
||||||
# Wrong username or password
|
|
||||||
self.log.debug(b_('Authentication failed logging in'))
|
|
||||||
raise AuthError(b_('Unable to log into server. Invalid'
|
|
||||||
' authentication tokens. Send new username and password'))
|
|
||||||
elif http_status >= 400:
|
|
||||||
if retries < 0 or num_tries < retries:
|
|
||||||
# Retry the request
|
|
||||||
num_tries += 1
|
|
||||||
self.log.debug(b_('Attempt #%(try)s failed') % {'try': num_tries})
|
|
||||||
time.sleep(0.5)
|
|
||||||
continue
|
|
||||||
# Fail and raise an error
|
|
||||||
try:
|
|
||||||
msg = httplib.responses[http_status]
|
|
||||||
except (KeyError, AttributeError):
|
|
||||||
msg = b_('Unknown HTTP Server Response')
|
|
||||||
raise ServerError(url, http_status, msg)
|
|
||||||
# Successfully returned data
|
|
||||||
break
|
|
||||||
|
|
||||||
# In case the server returned a new session cookie to us
|
|
||||||
new_session = response.cookies.get(self.session_name, '')
|
|
||||||
|
|
||||||
try:
|
|
||||||
data = response.json
|
|
||||||
# Compatibility with newer python-requests
|
|
||||||
if callable(data):
|
|
||||||
data = data()
|
|
||||||
except ValueError, e:
|
|
||||||
# The response wasn't JSON data
|
|
||||||
raise ServerError(url, http_status, b_('Error returned from'
|
|
||||||
' json module while processing %(url)s: %(err)s') %
|
|
||||||
{'url': to_bytes(url), 'err': to_bytes(e)})
|
|
||||||
|
|
||||||
if 'exc' in data:
|
|
||||||
name = data.pop('exc')
|
|
||||||
message = data.pop('tg_flash')
|
|
||||||
raise AppError(name=name, message=message, extras=data)
|
|
||||||
|
|
||||||
# If we need to return a cookie for deprecated code, convert it here
|
|
||||||
if self.session_as_cookie:
|
|
||||||
cookie = Cookie.SimpleCookie()
|
|
||||||
cookie[self.session_name] = new_session
|
|
||||||
new_session = cookie
|
|
||||||
|
|
||||||
self.log.debug(b_('proxyclient.send_request: exited'))
|
|
||||||
data = bunchify(data)
|
|
||||||
return new_session, data
|
|
||||||
|
|
||||||
__all__ = (ProxyClient,)
|
|
|
@ -1,220 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Flask-FAS-OpenID - A Flask extension for authorizing users with FAS-OpenID
|
|
||||||
#
|
|
||||||
# Primary maintainer: Patrick Uiterwijk <puiterwijk@fedoraproject.org>
|
|
||||||
#
|
|
||||||
# Copyright (c) 2013, Patrick Uiterwijk
|
|
||||||
# This file is part of python-fedora
|
|
||||||
#
|
|
||||||
# python-fedora is free software; you can redistribute it and/or
|
|
||||||
# modify it under the terms of the GNU Lesser General Public
|
|
||||||
# License as published by the Free Software Foundation; either
|
|
||||||
# version 2.1 of the License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# python-fedora is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
# Lesser General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Lesser General Public
|
|
||||||
# License along with python-fedora; if not, see <http://www.gnu.org/licenses/>
|
|
||||||
|
|
||||||
'''
|
|
||||||
FAS-OpenID authentication plugin for the flask web framework
|
|
||||||
|
|
||||||
.. moduleauthor:: Patrick Uiterwijk <puiterwijk@fedoraproject.org>
|
|
||||||
|
|
||||||
..versionadded:: 0.3.33
|
|
||||||
'''
|
|
||||||
from functools import wraps
|
|
||||||
|
|
||||||
from bunch import Bunch
|
|
||||||
import flask
|
|
||||||
try:
|
|
||||||
from flask import _app_ctx_stack as stack
|
|
||||||
except ImportError:
|
|
||||||
from flask import _request_ctx_stack as stack
|
|
||||||
|
|
||||||
import openid
|
|
||||||
from openid.consumer import consumer
|
|
||||||
from openid.fetchers import setDefaultFetcher, Urllib2Fetcher
|
|
||||||
from openid.extensions import pape, sreg
|
|
||||||
from openid_cla import cla
|
|
||||||
from openid_teams import teams
|
|
||||||
|
|
||||||
from fedora import __version__
|
|
||||||
|
|
||||||
class FAS(object):
|
|
||||||
|
|
||||||
def __init__(self, app=None):
|
|
||||||
self.app = app
|
|
||||||
if self.app is not None:
|
|
||||||
self._init_app(app)
|
|
||||||
|
|
||||||
def _init_app(self, app):
|
|
||||||
app.config.setdefault('FAS_OPENID_ENDPOINT',
|
|
||||||
'http://id.fedoraproject.org/')
|
|
||||||
app.config.setdefault('FAS_OPENID_CHECK_CERT', True)
|
|
||||||
|
|
||||||
if not self.app.config['FAS_OPENID_CHECK_CERT']:
|
|
||||||
setDefaultFetcher(Urllib2Fetcher())
|
|
||||||
|
|
||||||
@app.route('/_flask_fas_openid_handler/', methods=['GET', 'POST'])
|
|
||||||
def flask_fas_openid_handler():
|
|
||||||
return self._handle_openid_request()
|
|
||||||
|
|
||||||
app.before_request(self._check_session)
|
|
||||||
|
|
||||||
def _handle_openid_request(self):
|
|
||||||
return_url = flask.session['FLASK_FAS_OPENID_RETURN_URL']
|
|
||||||
cancel_url = flask.session['FLASK_FAS_OPENID_CANCEL_URL']
|
|
||||||
base_url = self.normalize_url(flask.request.base_url)
|
|
||||||
oidconsumer = consumer.Consumer(flask.session, None)
|
|
||||||
info = oidconsumer.complete(flask.request.values, base_url)
|
|
||||||
display_identifier = info.getDisplayIdentifier()
|
|
||||||
|
|
||||||
if info.status == consumer.FAILURE and display_identifier:
|
|
||||||
return 'FAILURE. display_identifier: %s' % display_identifier
|
|
||||||
elif info.status == consumer.CANCEL:
|
|
||||||
if cancel_url:
|
|
||||||
return flask.redirect(cancel_url)
|
|
||||||
return 'OpenID request was cancelled'
|
|
||||||
elif info.status == consumer.SUCCESS:
|
|
||||||
sreg_resp = sreg.SRegResponse.fromSuccessResponse(info)
|
|
||||||
pape_resp = pape.Response.fromSuccessResponse(info)
|
|
||||||
teams_resp = teams.TeamsResponse.fromSuccessResponse(info)
|
|
||||||
cla_resp = cla.CLAResponse.fromSuccessResponse(info)
|
|
||||||
user = {'fullname': '', 'username': '', 'email': '', 'timezone': '', 'cla_done': False, 'groups': []}
|
|
||||||
if not sreg_resp:
|
|
||||||
# If we have no basic info, be gone with them!
|
|
||||||
return flask.redirect(cancel_url)
|
|
||||||
user['username'] = sreg_resp.get('nickname')
|
|
||||||
user['fullname'] = sreg_resp.get('fullname')
|
|
||||||
user['email'] = sreg_resp.get('email')
|
|
||||||
user['timezone'] = sreg_resp.get('timezone')
|
|
||||||
if cla_resp:
|
|
||||||
user['cla_done'] = cla.CLA_URI_FEDORA_DONE in cla_resp.clas
|
|
||||||
if teams_resp:
|
|
||||||
user['groups'] = frozenset(teams_resp.teams) # The groups do not contain the cla_ groups
|
|
||||||
flask.session['FLASK_FAS_OPENID_USER'] = user
|
|
||||||
flask.session.modified = True
|
|
||||||
return flask.redirect(return_url)
|
|
||||||
else:
|
|
||||||
return 'Strange state: %s' % info.status
|
|
||||||
|
|
||||||
def _check_session(self):
|
|
||||||
if not 'FLASK_FAS_OPENID_USER' in flask.session or flask.session['FLASK_FAS_OPENID_USER'] is None:
|
|
||||||
flask.g.fas_user = None
|
|
||||||
else:
|
|
||||||
user = flask.session['FLASK_FAS_OPENID_USER']
|
|
||||||
# Add approved_memberships to provide backwards compatibility
|
|
||||||
# New applications should only use g.fas_user.groups
|
|
||||||
user['approved_memberships'] = []
|
|
||||||
for group in user['groups']:
|
|
||||||
membership = dict()
|
|
||||||
membership['name'] = group
|
|
||||||
user['approved_memberships'].append(Bunch.fromDict(membership))
|
|
||||||
flask.g.fas_user = Bunch.fromDict(user)
|
|
||||||
flask.g.fas_session_id = 0
|
|
||||||
|
|
||||||
def login(self, username=None, password=None, return_url=None,
|
|
||||||
cancel_url=None, groups=['_FAS_ALL_GROUPS_']):
|
|
||||||
"""Tries to log in a user.
|
|
||||||
|
|
||||||
Sets the user information on :attr:`flask.g.fas_user`.
|
|
||||||
Will set 0 to :attr:`flask.g.fas_session_id, for compatibility
|
|
||||||
with flask_fas.
|
|
||||||
|
|
||||||
:arg username: Not used, but accepted for compatibility with the
|
|
||||||
flask_fas module
|
|
||||||
:arg password: Not used, but accepted for compatibility with the
|
|
||||||
flask_fas module
|
|
||||||
:arg return_url: The URL to forward the user to after login
|
|
||||||
:arg groups: A string or a list of group the user should belong to
|
|
||||||
to be authentified.
|
|
||||||
:returns: True if the user was succesfully authenticated.
|
|
||||||
:raises: Might raise an redirect to the OpenID endpoint
|
|
||||||
"""
|
|
||||||
if return_url is None:
|
|
||||||
if 'next' in flask.request.args.values():
|
|
||||||
return_url = flask.request.args.values['next']
|
|
||||||
else:
|
|
||||||
return_url = flask.request.url
|
|
||||||
oidconsumer = consumer.Consumer(flask.session, None)
|
|
||||||
try:
|
|
||||||
request = oidconsumer.begin(self.app.config['FAS_OPENID_ENDPOINT'])
|
|
||||||
except consumer.DiscoveryFailure, exc:
|
|
||||||
# VERY strange, as this means it could not discover an OpenID endpoint at FAS_OPENID_ENDPOINT
|
|
||||||
return 'discoveryfailure'
|
|
||||||
if request is None:
|
|
||||||
# Also very strange, as this means the discovered OpenID endpoint is no OpenID endpoint
|
|
||||||
return 'no-request'
|
|
||||||
|
|
||||||
if isinstance(groups, basestring):
|
|
||||||
groups = [groups]
|
|
||||||
|
|
||||||
request.addExtension(sreg.SRegRequest(required=['nickname', 'fullname', 'email', 'timezone']))
|
|
||||||
request.addExtension(pape.Request([]))
|
|
||||||
request.addExtension(teams.TeamsRequest(requested=groups))
|
|
||||||
request.addExtension(cla.CLARequest(requested=[cla.CLA_URI_FEDORA_DONE]))
|
|
||||||
|
|
||||||
trust_root = self.normalize_url(flask.request.url_root)
|
|
||||||
return_to = trust_root + '_flask_fas_openid_handler/'
|
|
||||||
|
|
||||||
flask.session['FLASK_FAS_OPENID_RETURN_URL'] = return_url
|
|
||||||
flask.session['FLASK_FAS_OPENID_CANCEL_URL'] = cancel_url
|
|
||||||
if request.shouldSendRedirect():
|
|
||||||
redirect_url = request.redirectURL(trust_root, return_to, False)
|
|
||||||
return flask.redirect(redirect_url)
|
|
||||||
else:
|
|
||||||
return request.htmlMarkup(trust_root, return_to, form_tag_attrs={'id': 'openid_message'}, immediate=False)
|
|
||||||
|
|
||||||
def logout(self):
|
|
||||||
'''Logout the user associated with this session
|
|
||||||
'''
|
|
||||||
flask.session['FLASK_FAS_OPENID_USER'] = None
|
|
||||||
flask.g.fas_session_id = None
|
|
||||||
flask.g.fas_user = None
|
|
||||||
flask.session.modified = True
|
|
||||||
|
|
||||||
def normalize_url(self, url):
|
|
||||||
''' Replace the scheme prefix of a url with our preferred scheme.
|
|
||||||
'''
|
|
||||||
scheme = self.app.config['PREFERRED_URL_SCHEME']
|
|
||||||
scheme_index = url.index('://')
|
|
||||||
return scheme + url[scheme_index:]
|
|
||||||
|
|
||||||
|
|
||||||
# This is a decorator we can use with any HTTP method (except login, obviously)
|
|
||||||
# to require a login.
|
|
||||||
# If the user is not logged in, it will redirect them to the login form.
|
|
||||||
# http://flask.pocoo.org/docs/patterns/viewdecorators/#login-required-decorator
|
|
||||||
def fas_login_required(function):
|
|
||||||
""" Flask decorator to ensure that the user is logged in against FAS.
|
|
||||||
To use this decorator you need to have a function named 'auth_login'.
|
|
||||||
Without that function the redirect if the user is not logged in will not
|
|
||||||
work.
|
|
||||||
"""
|
|
||||||
@wraps(function)
|
|
||||||
def decorated_function(*args, **kwargs):
|
|
||||||
if flask.g.fas_user is None:
|
|
||||||
return flask.redirect(flask.url_for('auth_login',
|
|
||||||
next=flask.request.url))
|
|
||||||
return function(*args, **kwargs)
|
|
||||||
return decorated_function
|
|
||||||
|
|
||||||
|
|
||||||
def cla_plus_one_required(function):
|
|
||||||
""" Flask decorator to retrict access to CLA+1.
|
|
||||||
To use this decorator you need to have a function named 'auth_login'.
|
|
||||||
Without that function the redirect if the user is not logged in will not
|
|
||||||
work.
|
|
||||||
"""
|
|
||||||
@wraps(function)
|
|
||||||
def decorated_function(*args, **kwargs):
|
|
||||||
if flask.g.fas_user is None or not flask.g.fas_user.cla_done or len(flask.g.fas_user.groups) < 1: # FAS-OpenID does not return cla_ groups
|
|
||||||
return flask.redirect(flask.url_for('auth_login',
|
|
||||||
next=flask.request.url))
|
|
||||||
else:
|
|
||||||
return function(*args, **kwargs)
|
|
||||||
return decorated_function
|
|
|
@ -1,220 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Flask-FAS-OpenID - A Flask extension for authorizing users with FAS-OpenID
|
|
||||||
#
|
|
||||||
# Primary maintainer: Patrick Uiterwijk <puiterwijk@fedoraproject.org>
|
|
||||||
#
|
|
||||||
# Copyright (c) 2013, Patrick Uiterwijk
|
|
||||||
# This file is part of python-fedora
|
|
||||||
#
|
|
||||||
# python-fedora is free software; you can redistribute it and/or
|
|
||||||
# modify it under the terms of the GNU Lesser General Public
|
|
||||||
# License as published by the Free Software Foundation; either
|
|
||||||
# version 2.1 of the License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# python-fedora is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
# Lesser General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Lesser General Public
|
|
||||||
# License along with python-fedora; if not, see <http://www.gnu.org/licenses/>
|
|
||||||
|
|
||||||
'''
|
|
||||||
FAS-OpenID authentication plugin for the flask web framework
|
|
||||||
|
|
||||||
.. moduleauthor:: Patrick Uiterwijk <puiterwijk@fedoraproject.org>
|
|
||||||
|
|
||||||
..versionadded:: 0.3.33
|
|
||||||
'''
|
|
||||||
from functools import wraps
|
|
||||||
|
|
||||||
from bunch import Bunch
|
|
||||||
import flask
|
|
||||||
try:
|
|
||||||
from flask import _app_ctx_stack as stack
|
|
||||||
except ImportError:
|
|
||||||
from flask import _request_ctx_stack as stack
|
|
||||||
|
|
||||||
import openid
|
|
||||||
from openid.consumer import consumer
|
|
||||||
from openid.fetchers import setDefaultFetcher, Urllib2Fetcher
|
|
||||||
from openid.extensions import pape, sreg
|
|
||||||
from openid_cla import cla
|
|
||||||
from openid_teams import teams
|
|
||||||
|
|
||||||
from fedora import __version__
|
|
||||||
|
|
||||||
class FAS(object):
|
|
||||||
|
|
||||||
def __init__(self, app=None):
|
|
||||||
self.app = app
|
|
||||||
if self.app is not None:
|
|
||||||
self._init_app(app)
|
|
||||||
|
|
||||||
def _init_app(self, app):
|
|
||||||
app.config.setdefault('FAS_OPENID_ENDPOINT',
|
|
||||||
'http://id.fedoraproject.org/')
|
|
||||||
app.config.setdefault('FAS_OPENID_CHECK_CERT', True)
|
|
||||||
|
|
||||||
if not self.app.config['FAS_OPENID_CHECK_CERT']:
|
|
||||||
setDefaultFetcher(Urllib2Fetcher())
|
|
||||||
|
|
||||||
@app.route('/_flask_fas_openid_handler/', methods=['GET', 'POST'])
|
|
||||||
def flask_fas_openid_handler():
|
|
||||||
return self._handle_openid_request()
|
|
||||||
|
|
||||||
app.before_request(self._check_session)
|
|
||||||
|
|
||||||
def _handle_openid_request(self):
|
|
||||||
return_url = flask.session['FLASK_FAS_OPENID_RETURN_URL']
|
|
||||||
cancel_url = flask.session['FLASK_FAS_OPENID_CANCEL_URL']
|
|
||||||
base_url = self.normalize_url(flask.request.base_url)
|
|
||||||
oidconsumer = consumer.Consumer(flask.session, None)
|
|
||||||
info = oidconsumer.complete(flask.request.values, base_url)
|
|
||||||
display_identifier = info.getDisplayIdentifier()
|
|
||||||
|
|
||||||
if info.status == consumer.FAILURE and display_identifier:
|
|
||||||
return 'FAILURE. display_identifier: %s' % display_identifier
|
|
||||||
elif info.status == consumer.CANCEL:
|
|
||||||
if cancel_url:
|
|
||||||
return flask.redirect(cancel_url)
|
|
||||||
return 'OpenID request was cancelled'
|
|
||||||
elif info.status == consumer.SUCCESS:
|
|
||||||
sreg_resp = sreg.SRegResponse.fromSuccessResponse(info)
|
|
||||||
pape_resp = pape.Response.fromSuccessResponse(info)
|
|
||||||
teams_resp = teams.TeamsResponse.fromSuccessResponse(info)
|
|
||||||
cla_resp = cla.CLAResponse.fromSuccessResponse(info)
|
|
||||||
user = {'fullname': '', 'username': '', 'email': '', 'timezone': '', 'cla_done': False, 'groups': []}
|
|
||||||
if not sreg_resp:
|
|
||||||
# If we have no basic info, be gone with them!
|
|
||||||
return flask.redirect(cancel_url)
|
|
||||||
user['username'] = sreg_resp.get('nickname')
|
|
||||||
user['fullname'] = sreg_resp.get('fullname')
|
|
||||||
user['email'] = sreg_resp.get('email')
|
|
||||||
user['timezone'] = sreg_resp.get('timezone')
|
|
||||||
if cla_resp:
|
|
||||||
user['cla_done'] = cla.CLA_URI_FEDORA_DONE in cla_resp.clas
|
|
||||||
if teams_resp:
|
|
||||||
user['groups'] = frozenset(teams_resp.teams) # The groups do not contain the cla_ groups
|
|
||||||
flask.session['FLASK_FAS_OPENID_USER'] = user
|
|
||||||
flask.session.modified = True
|
|
||||||
return flask.redirect(return_url)
|
|
||||||
else:
|
|
||||||
return 'Strange state: %s' % info.status
|
|
||||||
|
|
||||||
def _check_session(self):
|
|
||||||
if not 'FLASK_FAS_OPENID_USER' in flask.session or flask.session['FLASK_FAS_OPENID_USER'] is None:
|
|
||||||
flask.g.fas_user = None
|
|
||||||
else:
|
|
||||||
user = flask.session['FLASK_FAS_OPENID_USER']
|
|
||||||
# Add approved_memberships to provide backwards compatibility
|
|
||||||
# New applications should only use g.fas_user.groups
|
|
||||||
user['approved_memberships'] = []
|
|
||||||
for group in user['groups']:
|
|
||||||
membership = dict()
|
|
||||||
membership['name'] = group
|
|
||||||
user['approved_memberships'].append(Bunch.fromDict(membership))
|
|
||||||
flask.g.fas_user = Bunch.fromDict(user)
|
|
||||||
flask.g.fas_session_id = 0
|
|
||||||
|
|
||||||
def login(self, username=None, password=None, return_url=None,
|
|
||||||
cancel_url=None, groups=['_FAS_ALL_GROUPS_']):
|
|
||||||
"""Tries to log in a user.
|
|
||||||
|
|
||||||
Sets the user information on :attr:`flask.g.fas_user`.
|
|
||||||
Will set 0 to :attr:`flask.g.fas_session_id, for compatibility
|
|
||||||
with flask_fas.
|
|
||||||
|
|
||||||
:arg username: Not used, but accepted for compatibility with the
|
|
||||||
flask_fas module
|
|
||||||
:arg password: Not used, but accepted for compatibility with the
|
|
||||||
flask_fas module
|
|
||||||
:arg return_url: The URL to forward the user to after login
|
|
||||||
:arg groups: A string or a list of group the user should belong to
|
|
||||||
to be authentified.
|
|
||||||
:returns: True if the user was succesfully authenticated.
|
|
||||||
:raises: Might raise an redirect to the OpenID endpoint
|
|
||||||
"""
|
|
||||||
if return_url is None:
|
|
||||||
if 'next' in flask.request.args.values():
|
|
||||||
return_url = flask.request.args.values['next']
|
|
||||||
else:
|
|
||||||
return_url = flask.request.url
|
|
||||||
oidconsumer = consumer.Consumer(flask.session, None)
|
|
||||||
try:
|
|
||||||
request = oidconsumer.begin(self.app.config['FAS_OPENID_ENDPOINT'])
|
|
||||||
except consumer.DiscoveryFailure, exc:
|
|
||||||
# VERY strange, as this means it could not discover an OpenID endpoint at FAS_OPENID_ENDPOINT
|
|
||||||
return 'discoveryfailure'
|
|
||||||
if request is None:
|
|
||||||
# Also very strange, as this means the discovered OpenID endpoint is no OpenID endpoint
|
|
||||||
return 'no-request'
|
|
||||||
|
|
||||||
if isinstance(groups, basestring):
|
|
||||||
groups = [groups]
|
|
||||||
|
|
||||||
request.addExtension(sreg.SRegRequest(required=['nickname', 'fullname', 'email', 'timezone']))
|
|
||||||
request.addExtension(pape.Request([]))
|
|
||||||
request.addExtension(teams.TeamsRequest(requested=groups))
|
|
||||||
request.addExtension(cla.CLARequest(requested=[cla.CLA_URI_FEDORA_DONE]))
|
|
||||||
|
|
||||||
trust_root = self.normalize_url(flask.request.url_root)
|
|
||||||
return_to = trust_root + '_flask_fas_openid_handler/'
|
|
||||||
|
|
||||||
flask.session['FLASK_FAS_OPENID_RETURN_URL'] = return_url
|
|
||||||
flask.session['FLASK_FAS_OPENID_CANCEL_URL'] = cancel_url
|
|
||||||
if request.shouldSendRedirect():
|
|
||||||
redirect_url = request.redirectURL(trust_root, return_to, False)
|
|
||||||
return flask.redirect(redirect_url)
|
|
||||||
else:
|
|
||||||
return request.htmlMarkup(trust_root, return_to, form_tag_attrs={'id': 'openid_message'}, immediate=False)
|
|
||||||
|
|
||||||
def logout(self):
|
|
||||||
'''Logout the user associated with this session
|
|
||||||
'''
|
|
||||||
flask.session['FLASK_FAS_OPENID_USER'] = None
|
|
||||||
flask.g.fas_session_id = None
|
|
||||||
flask.g.fas_user = None
|
|
||||||
flask.session.modified = True
|
|
||||||
|
|
||||||
def normalize_url(self, url):
|
|
||||||
''' Replace the scheme prefix of a url with our preferred scheme.
|
|
||||||
'''
|
|
||||||
scheme = self.app.config['PREFERRED_URL_SCHEME']
|
|
||||||
scheme_index = url.index('://')
|
|
||||||
return scheme + url[scheme_index:]
|
|
||||||
|
|
||||||
|
|
||||||
# This is a decorator we can use with any HTTP method (except login, obviously)
|
|
||||||
# to require a login.
|
|
||||||
# If the user is not logged in, it will redirect them to the login form.
|
|
||||||
# http://flask.pocoo.org/docs/patterns/viewdecorators/#login-required-decorator
|
|
||||||
def fas_login_required(function):
|
|
||||||
""" Flask decorator to ensure that the user is logged in against FAS.
|
|
||||||
To use this decorator you need to have a function named 'auth_login'.
|
|
||||||
Without that function the redirect if the user is not logged in will not
|
|
||||||
work.
|
|
||||||
"""
|
|
||||||
@wraps(function)
|
|
||||||
def decorated_function(*args, **kwargs):
|
|
||||||
if flask.g.fas_user is None:
|
|
||||||
return flask.redirect(flask.url_for('auth_login',
|
|
||||||
next=flask.request.url))
|
|
||||||
return function(*args, **kwargs)
|
|
||||||
return decorated_function
|
|
||||||
|
|
||||||
|
|
||||||
def cla_plus_one_required(function):
|
|
||||||
""" Flask decorator to retrict access to CLA+1.
|
|
||||||
To use this decorator you need to have a function named 'auth_login'.
|
|
||||||
Without that function the redirect if the user is not logged in will not
|
|
||||||
work.
|
|
||||||
"""
|
|
||||||
@wraps(function)
|
|
||||||
def decorated_function(*args, **kwargs):
|
|
||||||
if flask.g.fas_user is None or not flask.g.fas_user.cla_done or len(flask.g.fas_user.groups) < 1: # FAS-OpenID does not return cla_ groups
|
|
||||||
return flask.redirect(flask.url_for('auth_login',
|
|
||||||
next=flask.request.url))
|
|
||||||
else:
|
|
||||||
return function(*args, **kwargs)
|
|
||||||
return decorated_function
|
|
Loading…
Add table
Add a link
Reference in a new issue