diff --git a/roles/blockerbugs/files/python-fedora-openidbaseclient-hotfix.py b/roles/blockerbugs/files/python-fedora-openidbaseclient-hotfix.py
new file mode 100644
index 0000000000..5bc1c8af14
--- /dev/null
+++ b/roles/blockerbugs/files/python-fedora-openidbaseclient-hotfix.py
@@ -0,0 +1,406 @@
+#!/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
+#
+
+"""Base client for application relying on OpenID for authentication.
+
+.. moduleauthor:: Pierre-Yves Chibon
+.. moduleauthor:: Toshio Kuratomi
+
+.. 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 \
+ 'OpenID transaction in progress' 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')
diff --git a/roles/blockerbugs/tasks/main.yml b/roles/blockerbugs/tasks/main.yml
index ffe176d284..df3a079a19 100644
--- a/roles/blockerbugs/tasks/main.yml
+++ b/roles/blockerbugs/tasks/main.yml
@@ -51,3 +51,8 @@
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
+ copy: src=python-fedora-openidbaseclient-hotfix.py dest=/usr/lib/python2.7/site-packages/fedora/client/openidbaseclient.py owner=root group=root mode=0644