blockerbugs: remove hotfix, already released
This commit is contained in:
parent
714fc09d7a
commit
3cb6f5ca9d
2 changed files with 0 additions and 415 deletions
|
@ -1,406 +0,0 @@
|
|||
#!/usr/bin/env python2 -tt
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2013-2014 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/>
|
||||
#
|
||||
|
||||
"""Base client for application relying on OpenID for authentication.
|
||||
|
||||
.. moduleauthor:: Pierre-Yves Chibon <pingou@fedoraproject.org>
|
||||
.. moduleauthor:: Toshio Kuratomi <toshio@fedoraproject.org>
|
||||
|
||||
.. versionadded: 0.3.35
|
||||
|
||||
"""
|
||||
|
||||
# :F0401: Unable to import : Disabled because these will either import on py3
|
||||
# or py2 not both.
|
||||
# :E0611: No name $X in module: This was renamed in python3
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sqlite3
|
||||
from collections import defaultdict
|
||||
from functools import partial
|
||||
|
||||
# pylint: disable-msg=F0401
|
||||
try:
|
||||
# pylint: disable-msg=E0611
|
||||
# Python 3
|
||||
from urllib.parse import urljoin
|
||||
except ImportError:
|
||||
# Python 2
|
||||
from urlparse import urljoin
|
||||
# pylint: enable-msg=F0401,E0611
|
||||
|
||||
|
||||
import requests
|
||||
from functools import wraps
|
||||
from munch import munchify
|
||||
from kitchen.text.converters import to_bytes
|
||||
|
||||
from fedora import __version__
|
||||
from fedora.client import AuthError, LoginRequiredError, ServerError
|
||||
from fedora.client.openidproxyclient import (
|
||||
OpenIdProxyClient, absolute_url, openid_login)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
b_SESSION_DIR = os.path.join(os.path.expanduser('~'), '.fedora')
|
||||
b_SESSION_FILE = os.path.join(b_SESSION_DIR, 'baseclient-sessions.sqlite')
|
||||
|
||||
|
||||
def requires_login(func):
|
||||
"""
|
||||
Decorator function for get or post requests requiring login.
|
||||
|
||||
Decorate a controller method that requires the user to be authenticated.
|
||||
Example::
|
||||
|
||||
from fedora.client.openidbaseclient import requires_login
|
||||
|
||||
@requires_login
|
||||
def rename_user(new_name):
|
||||
user = new_name
|
||||
# [...]
|
||||
"""
|
||||
def _decorator(request, *args, **kwargs):
|
||||
""" Run the function and check if it redirected to the openid form.
|
||||
"""
|
||||
output = func(request, *args, **kwargs)
|
||||
if output and \
|
||||
'<title>OpenID transaction in progress</title>' in output.text:
|
||||
raise LoginRequiredError(
|
||||
'{0} requires a logged in user'.format(output.url))
|
||||
return output
|
||||
return wraps(func)(_decorator)
|
||||
|
||||
|
||||
class OpenIdBaseClient(OpenIdProxyClient):
|
||||
|
||||
""" A client for interacting with web services relying on openid auth. """
|
||||
|
||||
def __init__(self, base_url, login_url=None, useragent=None, debug=False,
|
||||
insecure=False, openid_insecure=False, username=None,
|
||||
session_id=None, session_name='session',
|
||||
openid_session_id=None, openid_session_name='FAS_OPENID',
|
||||
cache_session=True, retries=None, timeout=None):
|
||||
"""Client for interacting with web services relying on fas_openid auth.
|
||||
|
||||
:arg base_url: Base of every URL used to contact the server
|
||||
:kwarg login_url: The url to the login endpoint of the application.
|
||||
If none are specified, it uses the default `/login`.
|
||||
:kwarg useragent: Useragent string to use. If not given, default to
|
||||
"Fedora OpenIdBaseClient/VERSION"
|
||||
: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 openid_insecure: If True, do not check the openid 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 username: Username for establishing authenticated connections
|
||||
:kwarg session_id: id of the user's session
|
||||
:kwarg session_name: name of the cookie to use with session handling
|
||||
:kwarg openid_session_id: id of the user's openid session
|
||||
:kwarg openid_session_name: name of the cookie to use with openid
|
||||
session handling
|
||||
:kwarg cache_session: If set to true, cache the user's session data on
|
||||
the filesystem between runs
|
||||
: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.
|
||||
|
||||
"""
|
||||
|
||||
# These are also needed by OpenIdProxyClient
|
||||
self.useragent = useragent or 'Fedora BaseClient/%(version)s' % {
|
||||
'version': __version__}
|
||||
self.base_url = base_url
|
||||
self.login_url = login_url or urljoin(self.base_url, '/login')
|
||||
self.debug = debug
|
||||
self.insecure = insecure
|
||||
self.openid_insecure = openid_insecure
|
||||
self.retries = retries
|
||||
self.timeout = timeout
|
||||
self.session_name = session_name
|
||||
self.openid_session_name = openid_session_name
|
||||
|
||||
# These are specific to OpenIdBaseClient
|
||||
self.username = username
|
||||
self.cache_session = cache_session
|
||||
|
||||
# Make sure the database for storing the session cookies exists
|
||||
if cache_session:
|
||||
self._db = self._initialize_session_cache()
|
||||
if not self._db:
|
||||
self.cache_session = False
|
||||
|
||||
# Session cookie that identifies this user to the application
|
||||
self._session_id_map = defaultdict(str)
|
||||
if session_id:
|
||||
self.session_id = session_id
|
||||
if openid_session_id:
|
||||
self.openid_session_id = openid_session_id
|
||||
|
||||
# python-requests session. Holds onto cookies
|
||||
self._session = requests.session()
|
||||
|
||||
def _initialize_session_cache(self):
|
||||
# Note -- fallback to returning None on any problems as this isn't
|
||||
# critical. It just makes it so that we don't have to ask the user
|
||||
# for their password over and over.
|
||||
if not os.path.isdir(b_SESSION_DIR):
|
||||
try:
|
||||
os.makedirs(b_SESSION_DIR, mode=0o755)
|
||||
except OSError as err:
|
||||
log.warning('Unable to create {file}: {error}'.format(
|
||||
file=b_SESSION_DIR, error=err))
|
||||
return None
|
||||
|
||||
if not os.path.exists(b_SESSION_FILE):
|
||||
dbcon = sqlite3.connect(b_SESSION_FILE)
|
||||
cursor = dbcon.cursor()
|
||||
try:
|
||||
cursor.execute('create table sessions (username text,'
|
||||
' base_url text, session_id text,'
|
||||
' primary key (username, base_url))')
|
||||
except sqlite3.DatabaseError as err:
|
||||
# Probably not a database
|
||||
log.warning('Unable to initialize {file}: {error}'.format(
|
||||
file=b_SESSION_FILE, error=err))
|
||||
return None
|
||||
dbcon.commit()
|
||||
else:
|
||||
try:
|
||||
dbcon = sqlite3.connect(b_SESSION_FILE)
|
||||
except sqlite3.OperationalError as err:
|
||||
# Likely permission denied
|
||||
log.warning('Unable to connect to {file}: {error}'.format(
|
||||
file=b_SESSION_FILE, error=err))
|
||||
return None
|
||||
return dbcon
|
||||
|
||||
def _get_id(self, base_url=None):
|
||||
# base_url is only sent as a param if we're looking for the openid
|
||||
# session
|
||||
base_url = base_url or self.base_url
|
||||
|
||||
username = self.username or ''
|
||||
|
||||
session_id_key = '{}:{}'.format(base_url, username)
|
||||
if self._session_id_map[session_id_key]:
|
||||
# Cached in memory
|
||||
return self._session_id_map[session_id_key]
|
||||
|
||||
if username and self.cache_session:
|
||||
# Look for a session on disk
|
||||
cursor = self._db.cursor()
|
||||
cursor.execute('select * from sessions where'
|
||||
' username = ? and base_url = ?',
|
||||
(username, base_url))
|
||||
found_sessions = cursor.fetchall()
|
||||
if found_sessions:
|
||||
self._session_id_map[session_id_key] = found_sessions[0]
|
||||
|
||||
if not self._session_id_map[session_id_key]:
|
||||
log.debug('No session cached for "{username}"'.format(
|
||||
username=self.username))
|
||||
return self._session_id_map[session_id_key]
|
||||
|
||||
def _set_id(self, session_id, base_url=None):
|
||||
#if not self.username:
|
||||
# base_url is only sent as a param if we're looking for the openid
|
||||
# session
|
||||
base_url = base_url or self.base_url
|
||||
|
||||
username = self.username or ''
|
||||
|
||||
# Cache in memory
|
||||
session_id_key = '{}:{}'.format(base_url, username)
|
||||
self._session_id_map[session_id_key] = session_id
|
||||
|
||||
if username and self.cache_session:
|
||||
# Save to disk as well
|
||||
cursor = self._db.cursor()
|
||||
try:
|
||||
cursor.exectue('insert into sessions'
|
||||
' (session_id, username, base_url)'
|
||||
' values (?, ?, ?)',
|
||||
(session_id, username, base_url))
|
||||
except sqlite3.IntegrityError:
|
||||
# Record already exists for that username and url
|
||||
cursor.execute('update sessions set session_id = ? where'
|
||||
' username = ? and base_url = ?',
|
||||
(session_id, username, base_url))
|
||||
cursor.commit()
|
||||
|
||||
def _del_id(self, base_url=None):
|
||||
# base_url is only sent as a param if we're looking for the openid
|
||||
# session
|
||||
base_url = base_url or self.base_url
|
||||
|
||||
username = self.username or ''
|
||||
|
||||
# Remove from the in-memory cache
|
||||
session_id_key = '{}:{}'.format(base_url, username)
|
||||
del self._session_id_map[session_id_key]
|
||||
|
||||
if username and self.cache_session:
|
||||
# Remove from the disk cache as well
|
||||
cursor = self._db.cursor()
|
||||
cursor.execute('delete from sessions where'
|
||||
' username = ? and base_url = ?',
|
||||
(username, base_url))
|
||||
|
||||
session_id = property(
|
||||
_get_id,
|
||||
_set_id,
|
||||
_del_id,
|
||||
"""The session_id.
|
||||
|
||||
The session id is saved in a file in case it is needed in
|
||||
consecutive runs of BaseClient.
|
||||
""")
|
||||
|
||||
openid_session_id = property(
|
||||
partial(_get_id, base_url='FAS_OPENID'),
|
||||
partial(_set_id, base_url='FAS_OPENID'),
|
||||
partial(_del_id, base_url='FAS_OPENID'),
|
||||
"""The openid_session_id.
|
||||
|
||||
The openid session id is saved in a file in case it is needed in
|
||||
consecutive runs of BaseClient.
|
||||
""")
|
||||
|
||||
@requires_login
|
||||
def _authed_post(self, url, params=None, data=None, **kwargs):
|
||||
""" Return the request object of a post query."""
|
||||
response = self._session.post(url, params=params, data=data, **kwargs)
|
||||
return response
|
||||
|
||||
@requires_login
|
||||
def _authed_get(self, url, params=None, data=None, **kwargs):
|
||||
""" Return the request object of a get query."""
|
||||
response = self._session.get(url, params=params, data=data, **kwargs)
|
||||
return response
|
||||
|
||||
def send_request(self, method, auth=False, verb='POST', **kwargs):
|
||||
"""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.
|
||||
|
||||
:arg method: Method to call on the server. It's a url fragment that
|
||||
comes after the :attr:`base_url` set in :meth:`__init__`.
|
||||
: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__` (which 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. Default to use the
|
||||
:attr:`timeout` value set on the instance or in :meth:`__init__`
|
||||
(which defaults to 120s).
|
||||
:kwarg auth: If True perform auth to the server, else do not.
|
||||
:kwarg req_params: Extra parameters to send to the server.
|
||||
: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 verb: HTTP verb to use. GET and POST are currently supported.
|
||||
POST is the default.
|
||||
"""
|
||||
# Decide on the set of auth cookies to use
|
||||
|
||||
method = absolute_url(self.base_url, method)
|
||||
|
||||
self._authed_verb_dispatcher = {(False, 'POST'): self._session.post,
|
||||
(False, 'GET'): self._session.get,
|
||||
(True, 'POST'): self._authed_post,
|
||||
(True, 'GET'): self._authed_get}
|
||||
try:
|
||||
func = self._authed_verb_dispatcher[(auth, verb)]
|
||||
except KeyError:
|
||||
raise Exception('Unknown HTTP verb')
|
||||
|
||||
if auth:
|
||||
auth_params = {'session_id': self.session_id,
|
||||
'openid_session_id': self.openid_session_id}
|
||||
try:
|
||||
output = func(method, auth_params, **kwargs)
|
||||
except LoginRequiredError:
|
||||
raise AuthError()
|
||||
else:
|
||||
try:
|
||||
output = func(method, **kwargs)
|
||||
except LoginRequiredError:
|
||||
raise AuthError()
|
||||
|
||||
try:
|
||||
data = output.json
|
||||
# Compatibility with newer python-requests
|
||||
if callable(data):
|
||||
data = data()
|
||||
except ValueError as e:
|
||||
# The response wasn't JSON data
|
||||
raise ServerError(
|
||||
method, output.status_code, 'Error returned from'
|
||||
' json module while processing %(url)s: %(err)s\n%(output)s' %
|
||||
{
|
||||
'url': to_bytes(method),
|
||||
'err': to_bytes(e),
|
||||
'output': to_bytes(output.text),
|
||||
})
|
||||
|
||||
data = munchify(data)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def login(self, username, password, otp=None):
|
||||
""" Open a session for the user.
|
||||
|
||||
Log in the user with the specified username and password
|
||||
against the FAS OpenID server.
|
||||
|
||||
:arg username: the FAS username of the user that wants to log in
|
||||
:arg password: the FAS password of the user that wants to log in
|
||||
:kwarg otp: currently unused. Eventually a way to send an otp to the
|
||||
API that the API can use.
|
||||
|
||||
"""
|
||||
response = openid_login(
|
||||
session=self._session,
|
||||
login_url=self.login_url,
|
||||
username=username,
|
||||
password=password,
|
||||
otp=otp,
|
||||
openid_insecure=self.openid_insecure)
|
||||
return response
|
||||
|
||||
__all__ = ('OpenIdBaseClient', 'requires_login')
|
|
@ -52,12 +52,3 @@
|
|||
tags:
|
||||
- config
|
||||
- blockerbugs
|
||||
|
||||
# hotfix openidbaseclient.py in python-fedora so it doesn't blow up during sync.
|
||||
# fix is in upstream, waiting for new release: https://github.com/fedora-infra/python-fedora/commit/a5a66e8e744ad1c54788e00d78bd6b66e67fe83b
|
||||
- name: hotfix python-fedora so it doesn't blow up
|
||||
when: env == "production"
|
||||
copy: src=python-fedora-openidbaseclient-hotfix.py dest=/usr/lib/python2.7/site-packages/fedora/client/openidbaseclient.py owner=root group=root mode=0644
|
||||
tags:
|
||||
- blockerbugs
|
||||
- hotfix
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue