diff --git a/doc/CSRF.rst b/doc/CSRF.rst
deleted file mode 100644
index 6323ad8c..00000000
--- a/doc/CSRF.rst
+++ /dev/null
@@ -1,404 +0,0 @@
-.. _CSRF-Protection:
-
-===============
-CSRF Protection
-===============
-
-:Authors: Toshio Kuratomi
-:Date: 21 February 2009
-:For Version: 0.3.x
-
-.. currentmodule:: fedora.tg.utils
-
-:term:`CSRF`, Cross-Site Request Forgery is a technique where a malicious
-website can gain access to a Fedora Service by hijacking a currently open
-session in a user's web browser. This technique affects identification via SSL
-Certificates, cookies, or anything else that the browser sends to the server
-automatically when a request to that server is made. It can take place over
-both GET and POST. GET requests just need to hit a vulnerable URL. POST
-requests require JavaScript to construct the form that is sent to the
-vulnerable server.
-
-.. note::
- If you just want to implement this in :ref:`Fedora-Services`, skip to the
- `Summary of Changes Per App`_ section
-
---------------
-How CSRF Works
---------------
-
-1) A vulnerable web service allows users to change things on their site using
- just a cookie for authentication and submission of a form or by hitting a
- URL with an ``img`` tag.
-
-2) A malicious website is crafted that looks harmless but has JavaScript or an
- ``img`` tag that sends a request to the web service with the form data or
- just hits the vulnerable URL.
-
-3) The user goes somewhere that people who are frequently logged into the site
- are at and posts something innocuous that gets people to click on the link
- to the malicious website.
-
-4) When a user who is logged into the vulnerable website clicks on the link,
- their web browser loads the page. It sees the img tag and contacts the
- vulnerable website to request the listed URL *sending the user's
- authentication cookie automatically*.
-
-5) The vulnerable server performs the action that the URL requires as the
- user whose cookies were sent and the damage is done... typically without
- the user knowing any better until much later.
-
------------------
-How to Prevent It
------------------
-
-Theory
-======
-
-In order to remove this problem we need to have a shared secret between the
-user's browser and the web site that is only available via the http
-request-response. This secret is required in order for any actions to be
-performed on the site. Because the :term:`Same Origin Policy` prevents the
-malicious website from reading the web page itself, a shared secret passed in
-this manner will prevent the malicious site from performing any actions.
-Note that this secret cannot be transferred from the user's browser to the
-server via a cookie because this is something that the browser does
-automatically. It can, however, be transferred from the server to the browser
-via a cookie because the browser prevents scripts from other domains from
-*reading* the cookies.
-
-Practice
-========
-
-The strategy we've adopted is sometimes called :term:`double submit`. Every
-time we POST a form or make a GET request that requires us to be authenticated
-we must also submit a token consisting of a hash of the ``tg-visit`` to show
-the server that we were able to read either the cookie or the response from a
-previous request. We store the token value in a GET or POST parameter named
-``_csrf_token``. If the server receives the ``tg-visit`` without the
-``_csrf_token`` parameter, the server renders the user anonymous until the
-user clicks another link.
-
-.. note::
- We hash the ``tg-visit`` session to make the token because we sometimes
- send the token as a parameter in GET requests so it will show up in the
- servers http logs.
-
-Verifying the Token
--------------------
-
-The :mod:`~fedora.tg.identity.jsonfasprovider1` does the work of
-verifying that ``_csrf_token`` has been set and that it is a valid hash of the
-``tg-visit`` token belonging to the user. The sequence of events to verify a
-user's identity follows this outline:
-
-1) If username and password given
-
- 1. Verify with the identity provider that the username and password match
-
- 1) [YES] authenticate the user
- 2) [NO] user is anonymous.
-
-2) if tg-visit cookie present
-
- 1. if session_id from ``tg-visit`` is in the db and not expired and
- (sha1sum(``tg-visit``) matches ``_csrf_token`` given as a (POST variable
- or GET variable)
-
- 1) [YES] authenticate the user
- 2) [NO] Take user to login page that just requires the user to click a
- link. Clicking the link shows that it's not just JavaScript in the
- browser attempting to load the page but an actual user. Once the link
- is clicked, the user will have a ``_csrf_token``. If the link is not
- clicked the user is treated as anonymous.
-
- 2. Verify via SSL Certificate
-
- 1) SSL Certificate is not revoked and able to retrieve info for the
- username and (sha1sum(``tg-visit``) matches ``_csrf_token`` given as a
- POST variable or GET variable)
-
- 1. [YES] authenticate the user
- 2. [NO] Take user to login page that just requires the user to click a
- link. Clicking the link shows that it's not just JavaScript in the
- browser attempting to load the page but an actual user. Once the link
- is clicked, the user will have a ``_csrf_token``. If the link is not
- clicked the user is treated as anonymous.
-
-This work should mostly happen behind the scenes so the application programmer
-does not need to worry about this.
-
-.. seealso:: The :mod:`~fedora.tg.identity.jsonfasprovider1`
- documentation has more information on methods that are provided by the
- identity provider in case you do need to tell what the authentication token
- is and whether it is missing.
-
-Getting the Token into the Page
--------------------------------
-
-Embedding the :term:`CSRF` token into the URLs that the user can click on is
-the other half of this problem. :mod:`fedora.tg.utils` provides two
-functions to make this easier.
-
-.. autofunction:: url
-
-This function does everything :func:`tg.url` does in the templates. In
-addition it makes sure that ``_csrf_token`` is appended to the URL.
-
-.. autofunction:: enable_csrf
-
-This function sets config values to allow ``_csrf_token`` to be passed to any
-URL on the server and makes :func:`turbogears.url` point to our :func:`url`
-function. Once this is run, the :func:`tg.url` function you use in the
-templates will make any links that use with it contain the :term:`CSRF`
-protecting token.
-
-Intra-Application Links
-~~~~~~~~~~~~~~~~~~~~~~~
-
-We support single sign-on among the web applications we've written for Fedora.
-In order for this to continue working with this :term:`CSRF` protection scheme
-we have to add the ``_csrf_token`` parameter to links between
-:ref:`Fedora-Services`. Linking from the PackageDB to Bodhi, for instance,
-needs to have the hash appended to the URL for Bodhi. Our :func:`url`
-function is capable of generating these by passing the full URL into the
-function like this::
-
- Bodhi
-
-.. note::
-
- You probably already use :func:`tg.url` for links within you web app to
- support :attr:`server.webpath`. Adding :func:`tg.url` to external links is
- new. You will likely have to update your application's templates to call
- :func:`url` on these URLs.
-
-Logging In
-----------
-
-Each app's :meth:`login` controller method and templates need to be modified
-in several ways.
-
-.. _CSRF-Login-Template:
-
-Templates
-~~~~~~~~~
-
-For the templates, python-fedora provides a set of standard templates that can
-be used to add the token.
-
-.. automodule:: fedora.tg.templates.genshi
-
-Using the ```` template will give you a login form that
-automatically does several things for you.
-
-1. The ``forward_url`` and ``previous_url`` parameters that are passed in
- hidden form elements will be run through :func:`tg.url` in order to get the
- ``_csrf_token`` added.
-2. The page will allow "Click through validation" of a user when they have a
- valid ``tg-visit`` but do not have a ``_csrf_token``.
-
-Here's a complete login.html from the pkgdb to show what this could look
-like::
-
-
-
-
-
-
-
-
- Login to the PackageDB
-
-
-
- ${message}
-
-
-
-You should notice that this looks like a typical genshi_ template in your
-project with two new features. The ```` tag in the body that's
-defined in :mod:`fedora.tg.templates.genshi` is used in the body to pull in
-the login formand the ```` of :file:`login.html`
-uses :func:`tg.fedora_template` to load the template from python-fedora.
-This function resides in :mod:`fedora.tg.utils` and is added to the
-``tg`` template variable when :func:`~fedora.tg.utils.enable_csrf` is
-called at startup. It does the following:
-
-.. currentmodule:: fedora.tg.utils
-
-.. autofunction:: fedora_template
-
-.. _genshi: http://genshi.edgewall.org
-.. _`genshi match template`: http://genshi.edgewall.org/wiki/Documentation/0.5.x/xml-templates.html#py:match
-
-The second match template in :file:`login.html` is to help you modify the
-login and logout links that appear at the top of a typical application's page.
-This is an optional change that lets the links display a click-through login
-link in addition to the usual login and logout. To use this, you would follow
-the example to add a ``toolbar`` with the ```` into your master
-template. Here's some snippets from a :file:`master.html` to illustrate::
-
-
-
-
- [...]
-
- [...]
-
- [...]
-
-
-
-
-
-.. warning::
-
- Notice that the ```` of :file:`login.html` happens after the
- ```` tag? It is important to do that because the ```` tag in
- :file:`master.html` is a match template just like ````. In
- genshi_, the order in which match templates are defined is significant.
-
-If you need to look at these templates to modify them yourself (perhaps to
-port them to a different templating language) you can find them in
-:file:`fedora/tg/templates/genshi/login.html` in the source tarball.
-
-.. _CSRF-controller-methods:
-
-Controllers
-~~~~~~~~~~~
-
-Calling :ref:`Fedora-Services` from JavaScript poses one further problem.
-Sometimes the user will not have a current :term:`CSRF` token and will need to
-log in. When this is done with a username and password there's no
-problem because the username and password are sufficient to prove the user is
-in control of the browser. However, when using an SSL Certificate, we need
-the browser to log in and then use the new :term:`CSRF` token to show the user
-is in control. Since JavaScript calls are most likely going to request the
-information as JSON, we need to provide the token in the dict returned from
-:meth:`login`. You can either modify your :meth:`login` method to do that or
-use the one provided in :func:`fedora.tg.controllers.login`.
-
-.. note::
-
- Fedora Services do not currently use certificate authentication but doing
- it would not take much code. Working out the security ramifications within
- Infrastructure is the main sticking point to turning this on.
-
-.. note::
- The `Fedora Account System `_
- has a slightly different login() method. This is because it has to handle
- account expiration, mandatory password resets, and other things tied to the
- status of an account. Other Fedora Services rely on FAS to do this instead
- of having to handle it themselves.
-
-.. automodule:: fedora.tg.controllers
- :members:
-
-AJAX
-====
-
-Making JavaScript calls requires that we can get the token and add it to the
-URLs we request. Since JavaScript doesn't have a standard library of crypto
-functions we provide the token by setting it via a template so we do not have
-to calculate it on the client. This has the added bonus of propagating a
-correct token even if we change the hash function later. Making use of the
-token can then be done in ad hoc JavaScript code but is better handled via a
-dedicated JavaScript method like the :class:`fedora.dojo.BaseClient`.
-
-Template
---------
-
-.. automodule:: fedora.tg.templates.genshi.jsglobals
-
-.. warning::
- Just like :file:`login.html`, the ```` tag needs to come after
- the ```` tag since they're both match templates.
-
-
-JavaScript
-----------
-
-The :class:`fedora.dojo.BaseClient` class has been modified to send and update
-the csrf token that is provided by fedora.identity.token. It is highly
-recommended that you use this library or another like it to make all calls to
-the server. This keeps you from having to deal with adding the :term:`CSRF`
-token to every call yourself.
-
-Here's a small bit of sample code::
-
-
-
-
---------------------------
-Summary of Changes Per App
---------------------------
-
- * On startup, run :func:`~fedora.tg.utils.enable_csrf`. This could be
- done in a start-APP.py and APP.wsgi scripts. Code like this will do it::
-
- from turbogears import startup
- from fedora.tg.utils import enable_csrf
- startup.call_on_startup.append(enable_csrf)
-
- * Links to other :ref:`Fedora-Services` in the templates must be run through
- the :func:`tg.url` method so that the :term:`CSRF` token can be appended.
-
- * You must use an updated login template. Using the one provided in
- python-fedora is possible by changing your login template as shown in the
- :ref:`CSRF-login-template` section.
-
- * Optional: update the master template that shows the Login/Logout Link to
- have a "Verify Login" button. You can add one to a toolbar in your
- application following the instructions in the :ref:`CSRF-login-template`
- section.
-
- * Use an updated identity provider from python-fedora. At this time, you
- need python-fedora 0.3.10 or later which has a
- :mod:`~fedora.tg.identity.jsonfasprovider2` and
- :mod:`~fedora.tg.visit.jsonfasvisit2` that provide :term:`CSRF` protection.
- The original :mod:`~fedora.tg.identity.jsonfasprovider1` is provided for
- applications that have not yet started using
- :func:`~fedora.tg.utils.enable_csrf` so you have to make this change in
- your configuration file (:file:`APPNAME/config/app.cfg`)
-
- * Get the :term:`CSRF` token into your forms and URLs. The recommended way
- to do this is to use :func:`tg.url` in your forms for URLs that are local
- to the app or are for :ref:`Fedora-Services`.
-
- * Update your :meth:`login` method to make sure you're setting
- ``forward_url = request.path_info`` rather than ``request.path``. One easy
- way to do this is to use the :meth:`~fedora.tg.controllers.login` and
- :meth:`~fedora.tg.controllers.logout` as documented in
- :ref:`CSRF-controller-methods`
-
- * Add the token and other identity information so JavaScript can get at it.
- Use the :mod:`~fedora.tg.templates.genshi.jsglobals` template to accomplish
- this.
-
-**This one still needs to be implemented**
- * AJAX calls need to be enhanced to append the CSRF token to the data. This
- is best done using a JavaScript function for this like the
- :class:`fedora.dojo.BaseClient` library.
diff --git a/doc/api.rst b/doc/api.rst
index e81d835c..ce9a83d7 100644
--- a/doc/api.rst
+++ b/doc/api.rst
@@ -8,48 +8,6 @@ docs into the hand created docs as we have time to integrate them.
.. toctree::
:maxdepth: 2
-------
-Client
-------
-
-.. automodule:: fedora.client
- :members: FedoraServiceError, ServerError, AuthError, AppError,
- FedoraClientError, FASError, CLAError, BodhiClientException,
- DictContainer
-
-Generic Clients
-===============
-
-BaseClient
-----------
-
-.. autoclass:: fedora.client.BaseClient
- :members:
- :undoc-members:
-
-ProxyClient
------------
-
-.. autoclass:: fedora.client.ProxyClient
- :members:
- :undoc-members:
-
-
-OpenIdBaseClient
-----------------
-
-.. autoclass:: fedora.client.OpenIdBaseClient
- :members:
- :undoc-members:
-
-.. autofunction:: fedora.client.openidbaseclient.requires_login
-
-OpenIdProxyClient
------------------
-
-.. autoclass:: fedora.client.OpenIdProxyClient
- :members:
- :undoc-members:
Clients for Specific Services
=============================
diff --git a/doc/auth.rst b/doc/auth.rst
deleted file mode 100644
index fa5baf09..00000000
--- a/doc/auth.rst
+++ /dev/null
@@ -1,128 +0,0 @@
-=====================
-Authentication to FAS
-=====================
-
-The :ref:`Fedora-Account-System` has a :term:`JSON` interface that we make use
-of to authenticate users in our web apps. Currently, there are two modes of
-operation. Some web apps have :term:`single sign-on` capability with
-:ref:`FAS`. These are the :term:`TurboGears` applications that use the
-:mod:`~fedora.tg.identity.jsonfasprovider`. Other apps do not have
-:term:`single sign-on` but they do connect to :ref:`FAS` to verify the
-username and password so changing the password in :ref:`FAS` changes it
-everywhere.
-
-.. _jsonfas2:
-
-TurboGears Identity Provider 2
-==============================
-
-An identity provider with :term:`CSRF` protection.
-
-This will install as a TurboGears identity plugin. To use it, set the
-following in your :file:`APPNAME/config/app.cfg` file::
-
- identity.provider='jsonfas2'
- visit.manager='jsonfas2'
-
-
-.. seealso:: :ref:`CSRF-Protection`
-
-.. automodule:: fedora.tg.identity.jsonfasprovider2
- :members: JsonFasIdentity, JsonFasIdentityProvider
- :undoc-members:
-
-.. automodule:: fedora.tg.visit.jsonfasvisit2
- :members: JsonFasVisitManager
- :undoc-members:
-
-.. _jsonfas1:
-
-Turbogears Identity Provider 1
-==============================
-
-These methods are **deprecated** because they do not provide the :term:`CSRF`
-protection of :ref:`jsonfas2`. Please use that identity provider instead.
-
-.. automodule:: fedora.tg.identity.jsonfasprovider1
- :members: JsonFasIdentity, JsonFasIdentityProvider
- :undoc-members:
- :deprecated:
-
-.. automodule:: fedora.tg.visit.jsonfasvisit1
- :members: JsonFasVisitManager
- :undoc-members:
- :deprecated:
-
-.. _djangoauth:
-
-Django Authentication Backend
-=============================
-.. toctree::
- :maxdepth: 2
-
- django
-
-
-.. _flask_fas:
-
-Flask Auth Plugin
-=================
-
-.. toctree::
- :maxdepth: 2
-
- flask_fas
-
-.. _flaskopenid:
-
-Flask FAS OpenId Auth Plugin
-============================
-
-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.
-
-* Any compliant OpenID server should allow you to use the basic authn features of OpenID
- OpenID authentication core: http://openid.net/specs/openid-authentication-2_0.html
-
-* Retrieving simple information about the user such as username, human name, email
- is done with sreg: http://openid.net/specs/openid-simple-registration-extension-1_0.html
- which is an extension supported by many providers.
-
-* 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:
- http://openid.net/specs/openid-provider-authentication-policy-extension-1_0.html
-
-* To get groups information, the provider must implement the
- https://dev.launchpad.net/OpenIDTeams extension.
-
- * We have extended the teams extension so you can request a team name of
- ``_FAS_ALL_GROUPS_`` 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.
-
-* Retrieving information about whether a user has signed a CLA (For Fedora,
- this is the Fedora Project Contributor Agreement).
- http://fedoraproject.org/specs/open_id/cla
-
-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.
-
-.. toctree::
- :maxdepth: 2
-
- flask_fas_openid
-
-.. _faswho:
-
-FAS Who Plugin for TurboGears2
-==============================
-.. toctree::
- :maxdepth: 2
-
- faswho
diff --git a/doc/client.rst b/doc/client.rst
deleted file mode 100644
index dea966a7..00000000
--- a/doc/client.rst
+++ /dev/null
@@ -1,301 +0,0 @@
-=============
-Fedora Client
-=============
-:Authors: Toshio Kuratomi
- Luke Macken
-:Date: 28 May 2008
-:For Version: 0.3.x
-
-The client module allows you to easily code an application that talks to a
-`Fedora Service`_. It handles the details of decoding the data sent from the
-Service into a python data structure and raises an Exception_ if an error is
-encountered.
-
-.. _`Fedora Service`: service.html
-.. _Exception: Exceptions_
-
-.. toctree::
-
-----------
-BaseClient
-----------
-
-The :class:`~fedora.client.BaseClient` class is the basis of all your
-interactions with the server. It is flexible enough to be used as is for
-talking with a service but is really meant to be subclassed and have methods
-written for it that do the things you specifically need to interact with the
-`Fedora Service`_ you care about. Authors of a `Fedora Service`_ are
-encouraged to provide their own subclasses of
-:class:`~fedora.client.BaseClient` that make it easier for other people to use
-a particular Service out of the box.
-
-Using Standalone
-================
-
-If you don't want to subclass, you can use :class:`~fedora.client.BaseClient`
-as a utility class to talk to any `Fedora Service`_. There's three steps to
-this. First you import the :class:`~fedora.client.BaseClient` and Exceptions_
-from the ``fedora.client`` module. Then you create a new
-:class:`~fedora.client.BaseClient` with the URL that points to the root of the
-`Fedora Service`_ you're interacting with. Finally, you retrieve data from a
-method on the server. Here's some code that illustrates the process::
-
- from fedora.client import BaseClient, AppError, ServerError
-
- client = BaseClient('https://admin.fedoraproject.org/pkgdb')
- try:
- collectionData = client.send_request('/collections', auth=False)
- except ServerError as e:
- print('%s' % e)
- except AppError as e:
- print('%s: %s' % (e.name, e.message))
-
- for collection in collectionData['collections']:
- print('%s %s' % (collection['name'], collection['version'])
-
-BaseClient Constructor
-~~~~~~~~~~~~~~~~~~~~~~
-
-In our example we only provide ``BaseClient()`` with the URL fragment it uses
-as the base of all requests. There are several more optional parameters that
-can be helpful.
-
-If you need to make an authenticated request you can specify the username and
-password to use when you construct your :class:`~fedora.client.BaseClient`
-using the ``username`` and ``password`` keyword arguments. If you do not use
-these, authenticated requests will try to connect via a cookie that was saved
-from previous runs of :class:`~fedora.client.BaseClient`. If that fails as
-well, :class:`~fedora.client.BaseClient` will throw an Exception_ which you
-can catch in order to prompt for a new username and password::
-
- from fedora.client import BaseClient, AuthError
- import getpass
- MAX_RETRIES = 5
- client = BaseClient('https://admin.fedoraproject.org/pkgdb',
- username='foo', password='bar')
- # Note this is simplistic. It only prompts once for another password.
- # Your application may want to loop through this several times.
- while (count < MAX_RETRIES):
- try:
- collectionData = client.send_request('/collections', auth=True)
- except AuthError as e:
- client.password = getpass.getpass('Retype password for %s: ' % username)
- else:
- # data retrieved or we had an error unrelated to username/password
- break
- count = count + 1
-
-.. warning::
-
- Note that although you can set the ``username`` and ``password`` as shown
- above you do have to be careful in cases where your application is
- multithreaded or simply processes requests for more than one user with the
- same :class:`~fedora.client.BaseClient`. In those cases, you can
- accidentally overwrite the ``username`` and ``password`` between two
- requests. To avoid this, make sure you instantiate a separate
- :class:`~fedora.client.BaseClient` for every thread of control or for
- every request you handle or use :class:`~fedora.client.ProxyClient`
- instead.
-
-The ``useragent`` parameter is useful for identifying in log files that your
-script is calling the server rather than another. The default value is
-``Fedora BaseClient/VERSION`` where VERSION is the version of the
-:class:`~fedora.client.BaseClient` module. If you want to override this just
-give another string to this::
-
- client = BaseClient('https://admin.fedoraproject.org/pkgdb',
- useragent='Package Database Client/1.0')
-
-The ``debug`` parameter turns on a little extra output when running the
-program. Set it to true if you're having trouble and want to figure out what
-is happening inside of the :class:`~fedora.client.BaseClient` code.
-
-send_request()
-~~~~~~~~~~~~~~
-
-``send_request()`` is what does the heavy lifting of making a request of the
-server, receiving the reply, and turning that into a python dictionary. The
-usage is pretty straightforward.
-
-The first argument to ``send_request()`` is ``method``. It contains the name
-of the method on the server. It also has any of the positional parameters
-that the method expects (extra path information interpreted by the server for
-those building non-`TurboGears`_ applications).
-
-The ``auth`` keyword argument is a boolean. If True, the session cookie for
-the user is sent to the server. If this fails, the ``username`` and
-``password`` are sent. If that fails, an Exception_ is raised that you can
-handle in your code.
-
-``req_params`` contains a dictionary of additional keyword arguments for the
-server method. These would be the names and values returned via a form if it
-was a CGI. Note that parameters passed as extra path information should be
-added to the ``method`` argument instead.
-
-An example::
-
- import BaseClient
- client = BaseClient('https://admin.fedoraproject.org/pkgdb/')
- client.send_request('/package/name/python-fedora', auth=False,
- req_params={'collectionVersion': '9', 'collectionName': 'Fedora'})
-
-In this particular example, knowing how the server works, ``/packages/name/``
-defines the method that the server is going to invoke. ``python-fedora`` is a
-positional parameter for the name of the package we're looking up.
-``auth=False`` means that we'll try to look at this method without having to
-authenticate. The ``req_params`` sends two additional keyword arguments:
-``collectionName`` which specifies whether to filter on a single distro or
-include Fedora, Fedora EPEL, Fedora OLPC, and Red Hat Linux in the output and
-``collectionVersion`` which specifies which version of the distribution to
-output for.
-
-The URL constructed by :class:`~fedora.client.BaseClient` to the server could
-be expressed as[#]_::
-
- https://admin.fedoraproject.org/pkgdb/package/name/python-fedora/?collectionName=Fedora&collectionVersion=9
-
-In previous releases of python-fedora, there would be one further query
-parameter: ``tg_format=json``. That parameter instructed the server to
-return the information as JSON data instead of HTML. Although this is usually
-still supported in the server, :class:`~fedora.client.BaseClient` has
-deprecated this method. Servers should be configured to use an ``Accept``
-header to get this information instead. See the `JSON output`_ section of the
-`Fedora Service`_ documentation for more information about the server side.
-
-.. _`TurboGears`: http://www.turbogears.org/
-.. _`JSON output`: service.html#selecting-json-output
-.. _[#]: Note that the ``req_params`` are actually sent via ``POST`` request
- rather than ``GET``. Among other things, this means that values in
- ``req_params`` won't show up in apache logs.
-
-Subclassing
-===========
-
-Building a client using subclassing builds on the information you've already
-seen inside of :class:`~fedora.client.BaseClient`. You might want to use this
-if you want to provide a module for third parties to access a particular
-`Fedora Service`_. A subclass can provide a set of standard methods for
-calling the server instead of forcing the user to remember the URLs used to
-access the server directly.
-
-Here's an example that turns the previous calls into the basis of a python API
-to the `Fedora Package Database`_::
-
- import getpass
- import sys
- from fedora.client import BaseClient, AuthError
-
- class MyClient(BaseClient):
- def __init__(self, baseURL='https://admin.fedoraproject.org/pkgdb',
- username=None, password=None,
- useragent='Package Database Client/1.0', debug=None):
- super(BaseClient, self).__init__(baseURL, username, password,
- useragent, debug)
-
- def collection_list(self):
- '''Return a list of collections.'''
- return client.send_request('/collection')
-
- def package_owners(self, package, collectionName=None,
- collectionVersion=None):
- '''Return a mapping of release to owner for this package.'''
- pkgData = client.send_request('/packages/name/%s' % (package),
- {'collectionName': collectionName,
- 'collectionVersion': collectionVersion})
- ownerMap = {}
- for listing in pkgData['packageListings']:
- ownerMap['-'.join(listing['collection']['name'],
- listing['collection']['version'])] = \
- listing['owneruser']
- return ownerMap
-
-A few things to note:
-
-1) In our constructor we list a default ``baseURL`` and ``useragent``. This
- is usually a good idea as we know the URL of the `Fedora Service`_ we're
- connecting to and we want to know that people are using our specific API.
-
-2) Sometimes we'll want methods that are thin shells around the server methods
- like ``collection_list()``. Other times we'll want to do more
- post processing to get specific results as ``package_owners()`` does. Both
- types of methods are valid if they fit the needs of your API. If you find
- yourself writing more of the latter, though, you may want to consider
- getting a new method implemented in the server that can return results more
- appropriate to your needs as it could save processing on the server and
- bandwidth downloading the data to get information that more closely matches
- what you need.
-
-See ``pydoc fedora.client.fas2`` for a module that implements a standard
-client API for the `Fedora Account System`_
-
-.. _`Fedora Package Database`: https://fedorahosted.org/packagedb
-.. _`Fedora Account System`: https://fedorahosted.org/fas/
-
----------------
-Handling Errors
----------------
-
-:class:`~fedora.client.BaseClient` will throw a variety of errors that can be
-caught to tell you what kind of error was generated.
-
-Exceptions
-==========
-
-:``FedoraServiceError``: The base of all exceptions raised by
- :class:`~fedora.client.BaseClient`. If your code needs to catch any of the
- listed errors then you can catch that to do so.
-
-:``ServerError``: Raised if there's a problem communicating with the service.
- For instance, if we receive an HTML response instead of JSON.
-
-:``AuthError``: If something happens during authentication, like an invalid
- usernsme or password, ``AuthError`` will be raised. You can catch this to
- prompt the user for a new usernsme.
-
-:``AppError``: If there is a `server side error`_ when processing a request,
- the `Fedora Service`_ can alert the client of this by setting certain
- flags in the response. :class:`~fedora.client.BaseClient` will see these
- flags and raise an AppError. The name of the error will be stored in
- AppError's ``name`` field. The error's message will be stored in
- ``message``.
-
-.. _`server side error`: service.html#Error Handling
-
-Example
-=======
-Here's an example of the exceptions in action::
-
- from fedora.client import ServerError, AuthError, AppError, BaseClient
- import getpass
- MAXRETRIES = 5
-
- client = BaseClient('https://admin.fedoraproject.org/pkgdb')
- for retry in range(0, MAXRETRIES):
- try:
- collectionData = client.send_request('/collections', auth=True)
- except AuthError as e:
- from six.moves import input
- client.username = input('Username: ').strip()
- client.password = getpass.getpass('Password: ')
- continue
- except ServerError as e:
- print('Error talking to the server: %s' % e)
- break
- except AppError as e:
- print('The server issued the following exception: %s: %s' % (
- e.name, e.message))
-
- for collection in collectionData['collections']:
- print('%s %s' % (collection[0]['name'], collection[0]['version']))
-
-----------------
-OpenIdBaseClient
-----------------
-
-Applications that use OpenId to authenticate are not able to use the standard
-BaseClient because the pattern of authenticating is very different. We've
-written a separate client object called
-:class:`~fedora.client.OpenIdBaseClient` to do this.
-
-
-
diff --git a/doc/django.rst b/doc/django.rst
deleted file mode 100644
index a67ac172..00000000
--- a/doc/django.rst
+++ /dev/null
@@ -1,70 +0,0 @@
-====================================
-Fedora Django Authentication Backend
-====================================
-:Authors: Ignacio Vazquez-Abrams
-:Date: 23 Feb 2009
-:For Version: 0.3.x
-
-The django.auth package provides an authentication backend for Django
-projects.
-
-.. note::
-
- Django authentication does not provide :term:`single sign-on` with other
- Fedora web apps. it also does not provide :term:`CSRF` protection. Look
- at the way that Dango's builtin forms implement :term:`CSRF` protection
- for guidance on how to protect against this sort of attack.
-
-------------------
-fedora.django.auth
-------------------
-
-As FAS users are authenticated they are added to
-:class:`~fedora.django.auth.models.FasUser`. FAS groups are added to
-:class:`~django.contrib.auth.models.Group` both during ``syncdb`` and when
-a user is authenticated.
-
-Integrating into a Django Project
-=================================
-
-Add the following lines to the project's :file:`settings.py`::
-
- AUTHENTICATION_BACKENDS = (
- 'fedora.django.auth.backends.FasBackend',
- )
-
- FAS_USERNAME = ''
- FAS_PASSWORD = ''
- FAS_USERAGENT = ''
- FAS_URL = ''
- FAS_ADMINS = ( ... )
-
-``FAS_USERNAME`` and ``FAS_PASSWORD`` are used to retrieve group
-information during ``syncdb`` as well as to retrieve users via the
-authentication backend. They should be set to a low-privilege account
-that can read group and user information.
-
-``FAS_USERAGENT`` is the string used to identify yourself to the FAS
-server.
-
-``FAS_URL`` is the base URL of the FAS server to authenticate against.
-
-``FAS_ADMINS`` is a tuple of usernames that you want to have superuser
-rights in the Django project.
-
-Add ``fedora.django.auth.middleware.FasMiddleware`` to the
-``MIDDLEWARE_CLASSES`` tuple, between
-``django.contrib.sessions.middleware.SessionMiddleware`` and
-``django.contrib.auth.middleware.AuthenticationMiddleware``.
-
-Additionally, set ``FAS_GENERICEMAIL`` to ``False`` in order to use the
-email address specified in FAS instead of ``@fedoraproject.org``.
-
-Add ``fedora.django.auth`` to ``INSTALLED_APPS``.
-
-Finally, run ``python manage.py syncdb`` to add the models for the added app to the database.
-
-.. warning::
- The ``User.first_name`` and ``User.last_name`` attributes are always
- empty since FAS does not have any equivalents. The ``name``
- read-only property results in a round trip to the FAS server.
diff --git a/doc/existing.rst b/doc/existing.rst
deleted file mode 100644
index 3c33a224..00000000
--- a/doc/existing.rst
+++ /dev/null
@@ -1,70 +0,0 @@
-=================
-Existing Services
-=================
-
-There are many Services in Fedora. Many of these have an interface that we
-can query and get back information as :term:`JSON` data. There is
-documentation here about both the services and the client modules that can
-access them.
-
-.. _`Fedora-Account-System`:
-.. _`FAS`:
-
----------------------
-Fedora Account System
----------------------
-
-FAS is the Fedora Account System. It holds the account data for all of our
-contributors.
-
-.. toctree::
- :maxdepth: 2
-
-.. autoclass:: fedora.client.AccountSystem
- :members:
- :undoc-members:
-
-Threadsafe Account System Access
-================================
-
-It is not safe to use a single instance of the
-:class:`~fedora.client.AccountSystem` object in multiple threads. This is
-because instance variables are used to hold some connection-specific
-information (for instance, the user who is logging in). For this reason, we
-also provide the :class:`fedora.client.FasProxyClient` object.
-
-This is especially handy when writing authn and authz adaptors that talk to
-fas from a multithreaded webserver.
-
-.. toctree::
- :maxdepth: 2
-
-.. autoclass:: fedora.client.FasProxyClient
- :members:
- :undoc-members:
-
-.. _`Bodhi`:
-
-------------------------
-Bodhi, the Update Server
-------------------------
-
-Bodhi is used to push updates from the build system to the download
-repositories. It lets packagers send packages to the testing repository or to
-the update repository.
-
-pythyon-fedora currently supports both the old Bodhi1 interface and the new
-Bodhi2 interface. By using ``fedora.client.BodhiCLient``, the correct one
-should be returned to you depending on what is running live on Fedora
-Infrastructure servers.
-
-.. toctree::
- :maxdepth: 2
-
-.. autoclass:: fedora.client.Bodhi2Client
- :members:
- :undoc-members:
-
-.. autoclass:: fedora.client.Bodhi1Client
- :members:
- :undoc-members:
diff --git a/doc/faswho.rst b/doc/faswho.rst
deleted file mode 100644
index ba4edc30..00000000
--- a/doc/faswho.rst
+++ /dev/null
@@ -1,63 +0,0 @@
-=============
-FASWho Plugin
-=============
-:Authors: Luke Macken
- Toshio Kuratomi
-:Date: 3 September 2011
-
-This plugin provides authentication to the Fedora Account System using the
-`repoze.who` WSGI middleware. It is designed for use with :term:`TurboGears2`
-but it may be used with any `repoze.who` using application. Like
-:ref:`jsonfas2`, faswho has builtin :term:`CSRF` protection. This protection
-is implemented as a second piece of middleware and may be used with other
-`repoze.who` authentication schemes.
-
--------------------------------------------
-Authenticating against FAS with TurboGears2
--------------------------------------------
-
-Setting up authentication against FAS in :term:`TurboGears2` is very easy. It
-requires one change to be made to :file:`app/config/app_cfg.py`. This change
-will take care of registering faswho as the authentication provider, enabling
-:term:`CSRF` protection, switching :func:`tg.url` to use
-:func:`fedora.ta2g.utils.url` instead, and allowing the `_csrf_token`
-parameter to be given to any URL.
-
-.. autofunction:: fedora.tg2.utils.add_fas_auth_middleware
-
-.. autofunction:: fedora.wsgi.faswho.faswhoplugin.make_faswho_middleware
-
----------------------------------------------
-Using CSRF middleware with other Auth Methods
----------------------------------------------
-
-This section needs to be made clearer so that apps like mirrormanager can be
-ported to use this.
-
-.. automodule:: fedora.wsgi.csrf
-.. autoclass:: fedora.wsgi.csrf.CSRFProtectionMiddleware
-.. autoclass:: fedora.wsgi.csrf.CSRFMetadataProvider
-
----------
-Templates
----------
-
-The :mod:`fedora.tg2.utils` module contains some templates to help you
-write :term:`CSRF` aware login forms and buttons. You can use the
-:func:`~fedora.tg2.utils.fedora_template` function to integrate them into your
-templates:
-
-.. autofunction:: fedora.tg2.utils.fedora_template
-
-The templates themselves come in two flavors. One set for use with mako and
-one set for use with genshi.
-
-Mako
-====
-
-.. automodule:: fedora.tg2.templates.mako
-
-Genshi
-======
-
-.. automodule:: fedora.tg2.templates.genshi
diff --git a/doc/flask_fas.rst b/doc/flask_fas.rst
deleted file mode 100644
index ba9f4bd8..00000000
--- a/doc/flask_fas.rst
+++ /dev/null
@@ -1,229 +0,0 @@
-=====================
-FAS Flask Auth Plugin
-=====================
-
-:Authors: Toshio Kuratomi, Ian Weller
-:Date: 29 October 2012
-:For Version: 0.3.x
-
-The :ref:`Fedora-Account-System` has a :term:`JSON` interface that we make use
-of to authenticate users in our web apps. For our :term:`Flask` applications
-we have an identity provider that has :term:`single sign-on` with our
-:term:`TurboGears` 1 and 2 applications. It does not protect against
-:term:`CSRF` attacks in the identity layer. The flask-wtf forms package
-should be used to provide that.
-
--------------
-Configuration
--------------
-
-The FAS auth plugin has several config values that can be used to control how
-the auth plugin functions. You can set these in your application's config
-file.
-
-FAS_BASE_URL
- Set this to the URL of the FAS server you are authenticating against.
- Default is "https://admin.fedoraproject.org/accounts/"
-
-FAS_USER_AGENT
- User agent string to be used when connecting to FAS. You can set this to
- something specific to your application to aid in debugging a connection to
- the FAS server as it will show up in the FAS server's logs. Default is
- "Flask-FAS/|version|"
-
-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
-
-FAS_COOKIE_NAME
- The name of the cookie used to store the session id across the Fedora
- Applications that support :term:`single sign-on`. Default: "tg-visit"
-
-FAS_FLASK_COOKIE_REQUIRES_HTTPS
- When this is set to True, the session cookie will only be returned to the
- server via ssl (https). If you connect to the server via plain http, the
- cookie will not be sent. This prevents sniffing of the cookie contents.
- This may be set to False when testing your application but should always
- be set to True in production. Default is True.
-
-------------------
-Sample Application
-------------------
-
-The following is a sample, minimal flask application that uses fas_flask for
-authentication::
-
- #!/usr/bin/python -tt
- # Flask-FAS - A Flask extension for authorizing users with FAS
- # Primary maintainer: Ian Weller
- #
- # 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 ''AS IS'' 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['SECRET_KEY'] = 'change me!'
- # 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 'secure' option with
- # the login cookie (default True)
- # You should use these options' defaults for production applications!
- app.config['FAS_BASE_URL'] = 'https://fakefas.fedoraproject.org/accounts/'
- app.config['FAS_CHECK_CERT'] = False
- app.config['FAS_FLASK_COOKIE_REQUIRES_HTTPS'] = False
-
-
- # A basic login form
- class LoginForm(wtf.Form):
- username = wtf.TextField('Username', [wtf.validators.Required()])
- password = wtf.PasswordField('Password', [wtf.validators.Required()])
-
-
- # Inline templates keep this test application all in one file. Don't do this in
- # a real application. Please.
- TEMPLATE_START = """
-
' % \
- flask.url_for('secret')
- return flask.render_template_string(data)
-
-
- @app.route('/login', methods=['GET', 'POST'])
- 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 'next' in flask.request.args:
- next_url = flask.request.args['next']
- else:
- next_url = flask.url_for('index')
- # 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 += ('
Log into the '
- 'Fedora Accounts System:')
- # 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 += '
Invalid login
'
- data += """
- """
- return flask.render_template_string(data, form=form)
-
-
- @app.route('/logout')
- def logout():
- if flask.g.fas_user:
- fas.logout()
- return flask.redirect(flask.url_for('index'))
-
- # 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('/secret')
- @fas_login_required
- def secret():
- data = TEMPLATE_START + '
Be sure to drink your Ovaltine
'
- 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('/claplusone')
- def claplusone():
- data = TEMPLATE_START
- if not flask.g.fas_user:
- # Not logged in
- return flask.render_template_string(data +
- '
You must log in to check your cla +1 status
')
- non_cla_groups = [x.name for x in flask.g.fas_user.approved_memberships
- if x.group_type != 'cla']
- if len(non_cla_groups) > 0:
- data += '
Your account is cla+1.
'
- else:
- data += '
Your account is not cla+1.
'
- return flask.render_template_string(data)
-
-
- if __name__ == '__main__':
- app.run(debug=True)
diff --git a/doc/index.rst b/doc/index.rst
index 56046f90..a4ea5dea 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -10,9 +10,6 @@ Lesser General Public License version 2 or later.
:maxdepth: 2
client
- existing
- service
- auth
javascript
api
diff --git a/fedora/client/__init__.py b/fedora/client/__init__.py
index 89af0ec3..f63bc77f 100644
--- a/fedora/client/__init__.py
+++ b/fedora/client/__init__.py
@@ -154,11 +154,6 @@ def check_file_permissions(filename, allow_notexists=False):
# We want people to be able to import fedora.client.*Client directly
# pylint: disable-msg=W0611
-from fedora.client.proxyclient import ProxyClient
-from fedora.client.fasproxy import FasProxyClient
-from fedora.client.baseclient import BaseClient
-from fedora.client.openidproxyclient import OpenIdProxyClient
-from fedora.client.openidbaseclient import OpenIdBaseClient
from fedora.client.fas2 import AccountSystem, FASError, CLAError
from fedora.client.wiki import Wiki
# pylint: enable-msg=W0611
@@ -166,6 +161,4 @@ def check_file_permissions(filename, allow_notexists=False):
__all__ = ('FedoraServiceError', 'ServerError', 'AuthError', 'AppError',
'FedoraClientError', 'LoginRequiredError', 'DictContainer',
'FASError', 'CLAError', 'BodhiClientException',
- 'ProxyClient', 'FasProxyClient', 'BaseClient', 'OpenIdProxyClient',
- 'OpenIdBaseClient', 'AccountSystem', 'BodhiClient',
- 'Wiki')
+ 'AccountSystem', 'Wiki')
diff --git a/fedora/client/fasproxy.py b/fedora/client/fasproxy.py
deleted file mode 100644
index 85dfc10c..00000000
--- a/fedora/client/fasproxy.py
+++ /dev/null
@@ -1,198 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2008-2009 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
-#
-'''Implement a class that sets up threadsafe communication with the Fedora
- Account System
-
-.. moduleauthor:: Ricky Zhou
-.. moduleauthor:: Toshio Kuratomi
-
-.. versionadded:: 0.3.17
-'''
-
-from fedora.client import AuthError, AppError
-from fedora.client.proxyclient import ProxyClient
-from fedora import __version__
-
-import logging
-log = logging.getLogger(__name__)
-
-
-class FasProxyClient(ProxyClient):
- '''A threadsafe client to the Fedora Account System.'''
-
- def __init__(self, base_url='https://admin.fedoraproject.org/accounts/',
- *args, **kwargs):
- '''A threadsafe client to the Fedora Account System.
-
- This class is optimized to proxy multiple users to the account system.
- 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 a single user's connection to
- the Account System then use fedora.client.AccountSystem instead.
-
- :kwargs base_url: Base of every URL used to contact the server.
- Defaults to the Fedora Project FAS instance.
- :kwargs useragent: useragent string to use. If not given, default to
- "FAS Proxy Client/VERSION"
- :kwarg session_name: name of the cookie to use with session handling
- :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.
- '''
- if 'useragent' not in kwargs:
- kwargs['useragent'] = 'FAS Proxy Client/%s' % __version__
- if 'session_as_cookie' in kwargs and kwargs['session_as_cookie']:
- # No need to allow this in FasProxyClient as it's deprecated in
- # ProxyClient
- raise TypeError('FasProxyClient() got an unexpected keyword'
- ' argument \'session_as_cookie\'')
- kwargs['session_as_cookie'] = False
- super(FasProxyClient, self).__init__(base_url, *args, **kwargs)
-
- def login(self, username, password):
- '''Login to the Account System
-
- :arg username: username to send to FAS
- :arg password: Password to verify the username with
- :returns: a tuple of the session id FAS has associated with the user
- and the user's account information. This is similar to what is
- returned by
- :meth:`fedora.client.proxyclient.ProxyClient.get_user_info`
- :raises AuthError: if the username and password do not work
- '''
- return self.send_request(
- '/login',
- auth_params={'username': username, 'password': password}
- )
-
- def logout(self, session_id):
- '''Logout of the Account System
-
- :arg session_id: a FAS session_id to remove from FAS
- '''
- self.send_request('/logout', auth_params={'session_id': session_id})
-
- def refresh_session(self, session_id):
- '''Try to refresh a session_id to prevent it from timing out
-
- :arg session_id: FAS session_id to refresh
- :returns: session_id that FAS has set now
- '''
- return self.send_request('', auth_params={'session_id': session_id})
-
- def verify_session(self, session_id):
- '''Verify that a session is active.
-
- :arg session_id: session_id to verify is currently associated with a
- logged in user
- :returns: True if the session_id is valid. False otherwise.
- '''
- try:
- self.send_request('/home', auth_params={'session_id': session_id})
- except AuthError:
- return False
- except:
- raise
- return True
-
- def verify_password(self, username, password):
- '''Return whether the username and password pair are valid.
-
- :arg username: username to try authenticating
- :arg password: password for the user
- :returns: True if the username/password are valid. False otherwise.
- '''
- try:
- self.send_request('/home',
- auth_params={'username': username,
- 'password': password})
- except AuthError:
- return False
- except:
- raise
- return True
-
- def get_user_info(self, auth_params):
- '''Retrieve information about a logged in user.
-
- :arg auth_params: Auth information for a particular user. For
- instance, this can be a username/password pair or a session_id.
- Refer to
- :meth:`fedora.client.proxyclient.ProxyClient.send_request` for all
- the legal values for this.
- :returns: a tuple of session_id and information about the user.
- :raises AuthError: if the auth_params do not give access
- '''
- request = self.send_request('/user/view', auth_params=auth_params)
- return (request[0], request[1]['person'])
-
- def person_by_id(self, person_id, auth_params):
- '''Retrieve information about a particular person
-
- :arg auth_params: Auth information for a particular user. For
- instance, this can be a username/password pair or a session_id.
- Refer to
- :meth:`fedora.client.proxyclient.ProxyClient.send_request` for all
- the legal values for this.
- :returns: a tuple of session_id and information about the user.
- :raises AppError: if the server returns an exception
- :raises AuthError: if the auth_params do not give access
- '''
- request = self.send_request('/json/person_by_id',
- req_params={'person_id': person_id},
- auth_params=auth_params)
- if request[1]['success']:
- # In a devel version of FAS, membership info was returned
- # separately
- # This has been corrected in a later version
- # Can remove this code at some point
- if 'approved' in request[1]:
- request[1]['person']['approved_memberships'] = \
- request[1]['approved']
- if 'unapproved' in request[1]:
- request[1]['person']['unapproved_memberships'] = \
- request[1]['unapproved']
- return (request[0], request[1]['person'])
- else:
- raise AppError(name='Generic AppError',
- message=request[1]['tg_flash'])
-
- def group_list(self, auth_params):
- '''Retrieve a list of groups
-
- :arg auth_params: Auth information for a particular user. For
- instance, this can be a username/password pair or a session_id.
- Refer to
- :meth:`fedora.client.proxyclient.ProxyClient.send_request` for all
- the legal values for this.
- :returns: a tuple of session_id and information about groups. The
- groups information is in two fields:
-
- :groups: contains information about each group
- :memberships: contains information about which users are members
- of which groups
- :raises AuthError: if the auth_params do not give access
- '''
- request = self.send_request('/group/list', auth_params=auth_params)
- return request
diff --git a/fedora/client/openidbaseclient.py b/fedora/client/openidbaseclient.py
deleted file mode 100644
index 08374be3..00000000
--- a/fedora/client/openidbaseclient.py
+++ /dev/null
@@ -1,370 +0,0 @@
-#!/usr/bin/env python2 -tt
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2013-2015 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
-.. moduleauthor:: Ralph Bean
-
-.. 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 json
-import logging
-import os
-
-import lockfile
-import requests
-import requests.adapters
-from requests.packages.urllib3.util import Retry
-from six.moves.urllib.parse import urljoin
-
-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,
- UnsafeFileError,
- check_file_permissions)
-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, 'openidbaseclient-sessions.cache')
-
-
-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.
- Or if we got a 403
- """
- 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))
- elif output.status_code == 403:
- 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,
- cache_session=True, retries=None, timeout=None,
- retry_backoff_factor=0):
- """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 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.
- Note that this can only be set during object initialization.
- :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.
- :kwarg retry_backoff_factor: Exponential backoff factor to apply in
- between retry attempts. We will sleep for:
-
- `{retry_backoff_factor}*(2 ^ ({number of failed retries} - 1))`
-
- ...seconds inbetween attempts. The backoff factor scales the rate
- at which we back off. Defaults to 0 (backoff disabled).
- Note that this attribute can only be set at object initialization.
- """
-
- # 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
-
- # These are specific to OpenIdBaseClient
- self.username = username
- self.cache_session = cache_session
- self.cache_lock = lockfile.FileLock(b_SESSION_FILE)
-
- # Make sure the database for storing the session cookies exists
- if cache_session:
- self._initialize_session_cache()
-
- # python-requests session. Holds onto cookies
- self._session = requests.session()
-
- # Also hold on to retry logic.
- # http://www.coglib.com/~icordasc/blog/2014/12/retries-in-requests.html
- server_errors = [500, 501, 502, 503, 504, 506, 507, 508, 509, 599]
- method_whitelist = Retry.DEFAULT_METHOD_WHITELIST.union(set(['POST']))
- if retries is not None:
- prefixes = ['http://', 'https://']
- for prefix in prefixes:
- self._session.mount(prefix, requests.adapters.HTTPAdapter(
- max_retries=Retry(
- total=retries,
- status_forcelist=server_errors,
- backoff_factor=retry_backoff_factor,
- method_whitelist=method_whitelist,
- ),
- ))
-
- # See if we have any cookies kicking around from a previous run
- self._load_cookies()
-
- 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=0o750)
- except OSError as err:
- log.warning('Unable to create {file}: {error}'.format(
- file=b_SESSION_DIR, error=err))
- self.cache_session = False
- return None
-
- @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
-
- @requires_login
- def _authed_put(self, url, params=None, data=None, **kwargs):
- """ Return the request object of a put query."""
- response = self._session.put(url, params=params, data=data, **kwargs)
- return response
-
- @requires_login
- def _authed_delete(self, url, params=None, data=None, **kwargs):
- """ Return the request object of a delete query."""
- response = self._session.delete(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 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,
- (False, 'PUT'): self._session.put,
- (False, 'DELETE'): self._session.delete,
- (True, 'POST'): self._authed_post,
- (True, 'GET'): self._authed_get,
- (True, 'PUT'): self._authed_put,
- (True, 'DELETE'): self._authed_delete}
-
- if 'timeout' not in kwargs:
- kwargs['timeout'] = self.timeout
-
- try:
- func = self._authed_verb_dispatcher[(auth, verb)]
- except KeyError:
- raise Exception('Unknown HTTP verb')
-
- try:
- output = func(method, **kwargs)
- except LoginRequiredError:
- raise AuthError()
-
- try:
- data = output.json()
- 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.
-
- """
- if not username:
- raise AuthError("Username may not be %r at login." % username)
- if not password:
- raise AuthError("Password required for login.")
- # It looks like we're really doing this. Let's make sure that we don't
- # collide various cookies, and clear every cookie we had up until now
- # for this service.
- self._session.cookies.clear()
- self._save_cookies()
-
- response = openid_login(
- session=self._session,
- login_url=self.login_url,
- username=username,
- password=password,
- otp=otp,
- openid_insecure=self.openid_insecure)
- self._save_cookies()
- return response
-
- @property
- def session_key(self):
- return "%s:%s" % (self.base_url, self.username or '')
-
- def has_cookies(self):
- return bool(self._session.cookies)
-
- def _load_cookies(self):
- if not self.cache_session:
- return
-
- try:
- check_file_permissions(b_SESSION_FILE, True)
- except UnsafeFileError as e:
- log.debug('Current sessions ignored: {}'.format(str(e)))
- return
-
- try:
- with self.cache_lock:
- with open(b_SESSION_FILE, 'rb') as f:
- data = json.loads(f.read().decode('utf-8'))
- for key, value in data[self.session_key]:
- self._session.cookies[key] = value
- except KeyError:
- log.debug("No pre-existing session for %s" % self.session_key)
- except IOError:
- # The file doesn't exist, so create it.
- log.debug("Creating %s", b_SESSION_FILE)
- oldmask = os.umask(0o027)
- with open(b_SESSION_FILE, 'wb') as f:
- f.write(json.dumps({}).encode('utf-8'))
- os.umask(oldmask)
-
- def _save_cookies(self):
- if not self.cache_session:
- return
-
- with self.cache_lock:
- try:
- check_file_permissions(b_SESSION_FILE, True)
- with open(b_SESSION_FILE, 'rb') as f:
- data = json.loads(f.read(), encoding='utf-8')
- except UnsafeFileError as e:
- log.debug('Clearing sessions: {}'.format(str(e)))
- os.unlink(b_SESSION_FILE)
- data = {}
- except Exception:
- log.warn("Failed to open cookie cache before saving.")
- data = {}
-
- oldmask = os.umask(0o027)
- data[self.session_key] = self._session.cookies.items()
- with open(b_SESSION_FILE, 'wb') as f:
- f.write(json.dumps(data).encode('utf-8'))
- os.umask(oldmask)
-
-
-__all__ = ('OpenIdBaseClient', 'requires_login')
diff --git a/fedora/client/openidproxyclient.py b/fedora/client/openidproxyclient.py
deleted file mode 100644
index 55f61996..00000000
--- a/fedora/client/openidproxyclient.py
+++ /dev/null
@@ -1,560 +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
-#
-"""Implement a class that sets up simple communication to a Fedora Service.
-
-.. moduleauthor:: Pierre-Yves Chibon
-.. moduleauthor:: Toshio Kuratomi
-
-.. versionadded: 0.3.35
-
-"""
-
-import copy
-import logging
-import re
-# For handling an exception that's coming from requests:
-import ssl
-import time
-
-from six.moves import http_client as httplib
-from six.moves.urllib.parse import quote, parse_qs, urljoin, urlparse
-
-# Hack, hack, hack around
-# the horror that is logging!
-# Verily, verliy, verily, verily
-# We should all use something better
-try:
- # Python 2.7+
- from logging import NullHandler
-except ImportError:
- class NullHandler(logging.Handler):
- def emit(self, *args):
- pass
-
-import requests
-
-#from munch import munchify
-from kitchen.text.converters import to_bytes
-# For handling an exception that's coming from requests:
-import urllib3
-
-from fedora import __version__
-from fedora.client import AuthError, ServerError, FedoraServiceError
-
-log = logging.getLogger(__name__)
-log.addHandler(NullHandler())
-
-OPENID_SESSION_NAME = 'FAS_OPENID'
-
-FEDORA_OPENID_API = 'https://id.fedoraproject.org/api/v1/'
-FEDORA_OPENID_RE = re.compile(r'^http(s)?:\/\/id\.(|stg.|dev.)?fedoraproject\.org(/)?')
-
-
-def _parse_response_history(response):
- """ Retrieve the attributes from the response history. """
- data = {}
- for r in response.history:
- if FEDORA_OPENID_RE.match(r.url):
- parsed = parse_qs(urlparse(r.url).query)
- for key, value in parsed.items():
- data[key] = value[0]
- break
- return data
-
-
-def openid_login(session, login_url, username, password, otp=None,
- openid_insecure=False):
- """ Open a session for the user.
-
- Log in the user with the specified username and password
- against the FAS OpenID server.
-
- :arg session: Requests session object required to persist the cookies
- that are created during redirects on the openid provider.
- :arg login_url: The url to the login endpoint of the application.
- :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.
- :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.
-
- """
- # Log into the service
- response = session.get(
- login_url, headers={'Accept': 'application/json'})
-
- try:
- data = response.json()
- openid_url = data.get('server_url', None)
- if not FEDORA_OPENID_RE.match(openid_url):
- raise FedoraServiceError(
- 'Un-expected openid provider asked: %s' % openid_url)
- except:
- # Some consumers (like pyramid_openid) return redirects with the
- # openid attributes encoded in the url
- if not FEDORA_OPENID_RE.match(response.url):
- raise FedoraServiceError(
- 'Un-expected openid provider asked: %s' % response.url)
- data = _parse_response_history(response)
-
- # Contact openid provider
- data['username'] = username
- data['password'] = password
- # Let's precise to FedOAuth that we want to authenticate with FAS
- data['auth_module'] = 'fedoauth.auth.fas.Auth_FAS'
- data['auth_flow'] = 'fedora'
- if not 'openid.mode' in data:
- data['openid.mode'] = 'checkid_setup'
- response = session.post(
- FEDORA_OPENID_API, data=data, verify=not openid_insecure)
- if not bool(response):
- raise ServerError(FEDORA_OPENID_API, response.status_code,
- 'Error returned from our POST to ipsilon.')
-
- output = response.json()
-
- if not output['success']:
- raise AuthError(output['message'])
-
- response = session.post(output['response']['openid.return_to'],
- data=output['response'])
-
- return response
-
-
-def absolute_url(beginning, end):
- """ Join two urls parts if the last part does not start with the first
- part specified """
- if not end.startswith(beginning):
- end = urljoin(beginning, end)
- return end
-
-
-class OpenIdProxyClient(object):
- # pylint: disable-msg=R0903
- """
- 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.
-
- If you want something that can manage one user's connection to a Fedora
- Service, then look into using :class:`~fedora.client.OpenIdBaseClient`
- instead.
-
- 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.)
-
- .. 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:`OpenIdProxyClient` 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:: 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.
-
- """
-
- def __init__(self, base_url, login_url=None, useragent=None,
- session_name='session', debug=False, insecure=False,
- openid_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 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 ProxyClient/VERSION"
- :kwarg session_name: name of the cookie to use with session handling
- :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 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.
-
- """
- self.debug = debug
- log.debug('proxyclient.__init__:entered')
-
- # 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)
-
- if base_url[-1] != '/':
- base_url = base_url + '/'
- self.base_url = base_url
- self.login_url = login_url or urljoin(self.base_url, '/login')
- self.domain = urlparse(self.base_url).netloc
- self.useragent = useragent or 'Fedora ProxyClient/%(version)s' % {
- 'version': __version__}
- self.session_name = session_name
- self.insecure = insecure
- self.openid_insecure = openid_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
- log.debug('proxyclient.__init__:exited')
-
- def __get_debug(self):
- """Return whether we have debug logging turned on.
-
- :Returns: True if debugging is on, False otherwise.
-
- """
- if log.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:
- log.setLevel(logging.DEBUG)
- else:
- log.setLevel(logging.ERROR)
-
- debug = property(__get_debug, __set_debug, doc="""
- When True, we log extra debugging statements. When False, we only log
- errors.
- """)
-
- 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.
- :return: a tuple containing both the response from the OpenID
- provider and the session used to by this provider.
-
- """
- session = requests.session()
- response = openid_login(
- session=session,
- login_url=self.login_url,
- username=username,
- password=password,
- otp=otp,
- openid_insecure=self.openid_insecure)
- return (response, session)
-
- def send_request(self, method, verb='POST', req_params=None,
- auth_params=None, file_params=None, retries=None,
- timeout=None, headers=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__`.
- :kwarg headers: A dictionary containing specific headers to add to
- the request made.
- :returns: A tuple of session_id and data.
- :rtype: tuple of session information and data from server
-
- """
- log.debug('openidproxyclient.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']
- 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(
- 'username and password must both be set in auth_params'
- )
- if not (session_id or username):
- raise AuthError(
- '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, quote(method))
-
- # Set standard headers
- if headers:
- if not 'User-agent' in headers:
- headers['User-agent'] = self.useragent
- if not 'Accept' in headers:
- headers['Accept'] = 'application/json'
- else:
- 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 {}
-
- auth = None
- if username and password:
- # OpenID login
- resp, session = self.login(username, password, otp=None)
- cookies = session.cookies
-
- # If debug, give people our debug info
- log.debug('Creating request %s', to_bytes(url))
- log.debug('Headers: %s', 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'
-
- log.debug('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 = session.request(
- method=verb,
- url=url,
- data=complete_params,
- cookies=cookies,
- headers=headers,
- auth=auth,
- verify=not self.insecure,
- timeout=timeout,
- )
- except (requests.Timeout, requests.exceptions.SSLError) as err:
- if isinstance(err, 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 traceback 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 (err.args and isinstance(
- err.args[0], urllib3.exceptions.SSLError)
- and err.args[0].args
- and isinstance(err.args[0].args[0], ssl.SSLError)
- and err.args[0].args[0].args
- and 'timed out' in err.args[0].args[0].args[0]):
- # We're only interested in timeouts here
- raise
- log.debug('Request timed out')
- if retries < 0 or num_tries < retries:
- num_tries += 1
- log.debug('Attempt #%s failed', 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
- log.debug('Authentication failed logging in')
- raise AuthError(
- '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
- log.debug('Attempt #%s failed', num_tries)
- time.sleep(0.5)
- continue
- # Fail and raise an error
- try:
- msg = httplib.responses[http_status]
- except (KeyError, AttributeError):
- msg = '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 = session.cookies.get(self.session_name, '')
-
- log.debug('openidproxyclient.send_request: exited')
- #data = munchify(data)
- return new_session, response
-
-
-__all__ = (OpenIdProxyClient,)
diff --git a/fedora/client/proxyclient.py b/fedora/client/proxyclient.py
deleted file mode 100644
index 912a9e4d..00000000
--- a/fedora/client/proxyclient.py
+++ /dev/null
@@ -1,509 +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
-#
-'''Implement a class that sets up simple communication to a Fedora Service.
-
-.. moduleauthor:: Luke Macken
-.. moduleauthor:: Toshio Kuratomi
-.. moduleauthor:: Ralph Bean
-'''
-
-import copy
-from hashlib import sha1
-import logging
-# For handling an exception that's coming from requests:
-import ssl
-import time
-import warnings
-
-from munch import munchify
-from kitchen.text.converters import to_bytes
-import requests
-from six.moves import http_client as httplib
-from six.moves import http_cookies as Cookie
-from six.moves.urllib.parse import quote, urljoin, urlparse
-
-from fedora import __version__
-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('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(
- '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('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('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(
- '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('username and password must both be set in'
- ' auth_params')
- if not (session_id or username):
- raise AuthError(
- '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, 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 = sha1(to_bytes(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('Creating request %(url)s' %
- {'url': to_bytes(url)})
- self.log.debug('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('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
- #
- # And a note on the __class__.__name__/__module__ parsing:
- # On top of all the other things it does wrong, requests
- # is bundling a copy of urllib3. Distros can unbundle it.
- # But because of the bundling, python will think
- # exceptions raised by the version downloaded by pypi
- # (requests.packages.urllib3.exceptions.SSLError) are
- # different than the exceptions raised by the unbundled
- # distro version (urllib3.exceptions.SSLError). We could
- # do a try: except trying to import both of these
- # SSLErrors and then code to detect either one of them but
- # the whole thing is just stupid. So we'll use a stupid
- # hack of our own that (1) means we don't have to depend
- # on urllib3 just for this exception and (2) is (slightly)
- # less likely to break on the whims of the requests
- # author.
- if not (e.args
- and e.args[0].__class__.__name__ == 'SSLError'
- and e.args[0].__class__.__module__.endswith(
- 'urllib3.exceptions')
- 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('Request timed out')
- if retries < 0 or num_tries < retries:
- num_tries += 1
- self.log.debug(
- '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('Authentication failed logging in')
- raise AuthError(
- '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(
- '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 = '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()
- except ValueError as e:
- # The response wasn't JSON data
- raise ServerError(
- url, http_status, '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('proxyclient.send_request: exited')
- data = munchify(data)
- return new_session, data
-
-__all__ = (ProxyClient,)
diff --git a/fedora/django/__init__.py b/fedora/django/__init__.py
deleted file mode 100644
index 02b2c336..00000000
--- a/fedora/django/__init__.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2009 Ignacio Vazquez-Abrams
-# 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
-#
-'''
-.. moduleauthor:: Ignacio Vazquez-Abrams
-.. moduleauthor:: Toshio Kuratomi
-'''
-from fedora.client import FasProxyClient
-
-from django.conf import settings
-
-connection = None
-
-if not connection:
- connection = FasProxyClient(
- base_url=settings.FAS_URL, useragent=settings.FAS_USERAGENT
- )
-
-
-def person_by_id(userid):
- sid, userinfo = connection.person_by_id(
- userid,
- {'username': settings.FAS_USERNAME,
- 'password': settings.FAS_PASSWORD}
- )
- return userinfo
diff --git a/fedora/django/auth/__init__.py b/fedora/django/auth/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/fedora/django/auth/backends.py b/fedora/django/auth/backends.py
deleted file mode 100644
index 92c7b5c9..00000000
--- a/fedora/django/auth/backends.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2009 Ignacio Vazquez-Abrams
-# 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
-#
-'''
-.. moduleauthor:: Ignacio Vazquez-Abrams
-.. moduleauthor:: Toshio Kuratomi
-'''
-from fedora.client import AuthError
-from fedora.django import connection, person_by_id
-from fedora.django.auth.models import FasUser
-
-from django.contrib.auth.models import AnonymousUser
-from django.contrib.auth.backends import ModelBackend
-
-
-class FasBackend(ModelBackend):
- def authenticate(self, username=None, password=None,
- session_id=None):
- try:
- if session_id:
- auth = {'session_id': session_id}
- else:
- auth = {'username': username, 'password': password}
- session_id, userinfo = connection.get_user_info(auth_params=auth)
- user = FasUser.objects.user_from_fas(userinfo)
- user.session_id = session_id
- if user.is_active:
- return user
- except AuthError:
- pass
-
- def get_user(self, userid):
- try:
- userinfo = person_by_id(userid)
- if userinfo:
- return FasUser.objects.user_from_fas(userinfo)
- except AuthError:
- return AnonymousUser()
diff --git a/fedora/django/auth/management/__init__.py b/fedora/django/auth/management/__init__.py
deleted file mode 100644
index c023b502..00000000
--- a/fedora/django/auth/management/__init__.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2009 Ignacio Vazquez-Abrams
-# 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
-#
-'''
-.. moduleauthor:: Ignacio Vazquez-Abrams
-'''
-from fedora.django.auth import models
-
-from django.db.models.signals import post_syncdb
-
-post_syncdb.connect(models._syncdb_handler, sender=models)
diff --git a/fedora/django/auth/middleware.py b/fedora/django/auth/middleware.py
deleted file mode 100644
index ede99ec0..00000000
--- a/fedora/django/auth/middleware.py
+++ /dev/null
@@ -1,97 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2009 Ignacio Vazquez-Abrams
-# Copyright (C) 2012 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
-#
-'''
-.. moduleauthor:: Ignacio Vazquez-Abrams
-.. moduleauthor:: Toshio Kuratomi
-
-.. note:: Toshio only added httponly cookie support
-
-.. versionchanged:: 0.3.26
- Made session cookies httponly
-'''
-from fedora.client import AuthError
-
-import django
-from django.contrib.auth import authenticate, login, logout
-from django.contrib.auth.models import AnonymousUser
-
-
-class FasMiddleware(object):
- def process_request(self, request):
- # Retrieve the sessionid that the user gave, associating them with the
- # account system session
- sid = request.COOKIES.get('tg-visit', None)
-
- # Check if the session is still valid
- authenticated = False
- if sid:
- user = authenticate(session_id=sid)
- if user:
- try:
- login(request, user)
- authenticated = True
- except AuthError:
- pass
-
- if not authenticated:
- # Hack around misthought out djiblits/django interaction;
- # If we're logging in in django and get to here without having
- # the second factor of authentication, we need to logout the
- # django kept session information. Since we can't know precisely
- # what private information django might be keeping we need to use
- # django API to remove everything. However, djiblits requires the
- # request.session.test_cookie_worked() function in order to log
- # someone in later. The django logout function has removed that
- # function from the session attribute so the djiblit login fails.
- #
- # Save the necessary pieces of the session architecture here
- cookie_status = request.session.test_cookie_worked()
- logout(request)
- # python doesn't have closures
- if cookie_status:
- request.session.test_cookie_worked = lambda: True
- else:
- request.session.test_cookie_worked = lambda: False
- request.session.delete_test_cookie = lambda: None
-
- def process_response(self, request, response):
- if response.status_code != 301:
- if isinstance(request.user, AnonymousUser):
- response.set_cookie(key='tg-visit', value='', max_age=0)
- if 'tg-visit' in request.session:
- del request.session['tg-visit']
- else:
- try:
- if django.VERSION[:2] <= (1, 3):
- response.set_cookie(
- 'tg-visit',
- request.user.session_id, max_age=1814400,
- path='/', secure=True)
- else:
- response.set_cookie(
- 'tg-visit',
- request.user.session_id, max_age=1814400,
- path='/', secure=True, httponly=True)
- except AttributeError:
- # We expect that request.user.session_id won't be set
- # if the user is logging in with a non-FAS account
- # (ie: Django local auth).
- pass
- return response
diff --git a/fedora/django/auth/models.py b/fedora/django/auth/models.py
deleted file mode 100644
index 3edf216c..00000000
--- a/fedora/django/auth/models.py
+++ /dev/null
@@ -1,146 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2009 Ignacio Vazquez-Abrams
-# 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
-#
-'''
-.. moduleauthor:: Ignacio Vazquez-Abrams
-.. moduleauthor:: Toshio Kuratomi
-'''
-from __future__ import print_function
-from fedora.client import AuthError
-from fedora.django import connection, person_by_id
-from fedora import _
-
-import django.contrib.auth.models as authmodels
-from django.conf import settings
-import six
-
-# Map FAS user elements to model attributes
-_fasmap = {
- 'id': 'id',
- 'username': 'username',
- 'email': 'email',
-}
-
-
-def _new_group(group):
- try:
- g = authmodels.Group.objects.get(id=group['id'])
- except authmodels.Group.DoesNotExist:
- g = authmodels.Group(id=group['id'])
- g.name = group['name']
- g.save()
- return g
-
-
-def _syncdb_handler(sender, **kwargs):
- # Import FAS groups
- verbosity = kwargs.get('verbosity', 1)
- if verbosity > 0:
- print(_('Loading FAS groups...'))
- try:
- gl = connection.group_list({'username': settings.FAS_USERNAME,
- 'password': settings.FAS_PASSWORD})
- except AuthError:
- if verbosity > 0:
- print(_('Unable to load FAS groups. Did you set '
- 'FAS_USERNAME and FAS_PASSWORD?'))
- else:
- groups = gl[1]['groups']
- for group in groups:
- _new_group(group)
- if verbosity > 0:
- print(_('FAS groups loaded. Don\'t forget to set '
- 'FAS_USERNAME and FAS_PASSWORD to a low-privilege '
- 'account.'))
-
-
-class FasUserManager(authmodels.UserManager):
- def user_from_fas(self, user):
- """
- Creates a user in the table based on the structure returned
- by FAS
- """
- log = open('/var/tmp/django.log', 'a')
- log.write('in models user_from_fas\n')
- log.close()
- d = {}
- for k, v in six.iteritems(_fasmap):
- d[v] = user[k]
- u = FasUser(**d)
- u.set_unusable_password()
- u.is_active = user['status'] == 'active'
- admin = (user['username'] in
- getattr(settings, 'FAS_ADMINS', ()))
- u.is_staff = admin
- u.is_superuser = admin
- if getattr(settings, 'FAS_GENERICEMAIL', True):
- u.email = u._get_email()
- u.save()
- known_groups = []
- for group in u.groups.values():
- known_groups.append(group['id'])
-
- fas_groups = []
- for group in user['approved_memberships']:
- fas_groups.append(group['id'])
-
- # This user has been added to one or more FAS groups
- for group in (g for g in user['approved_memberships']
- if g['id'] not in known_groups):
- newgroup = _new_group(group)
- u.groups.add(newgroup)
-
- # This user has been removed from one or more FAS groups
- for gid in known_groups:
- found = False
- for g in user['approved_memberships']:
- if g['id'] == gid:
- found = True
- break
- if not found:
- u.groups.remove(authmodels.Group.objects.get(id=gid))
-
- u.save()
- return u
-
-
-class FasUser(authmodels.User):
- def _get_name(self):
- log = open('/var/tmp/django.log', 'a')
- log.write('in models _get_name\n')
- log.close()
- userinfo = person_by_id(self.id)
- return userinfo['human_name']
-
- def _get_email(self):
- log = open('/var/tmp/django.log', 'a')
- log.write('in models _get_email\n')
- log.close()
- return '%s@fedoraproject.org' % self.username
-
- name = property(_get_name)
-
- objects = FasUserManager()
-
- def get_full_name(self):
- log = open('/var/tmp/django.log', 'a')
- log.write('in models get_full_name\n')
- log.close()
- if self.name:
- return self.name.strip()
- return self.username.strip()
diff --git a/fedora/tg/__init__.py b/fedora/tg/__init__.py
deleted file mode 100644
index b5287694..00000000
--- a/fedora/tg/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-'''
-Functions and classes to help build a Fedora Service.
-'''
-
-__all__ = ('client', 'json', 'tg1utils', 'tg2utils', 'widgets',
- 'identity', 'utils', 'visit')
diff --git a/fedora/tg/client.py b/fedora/tg/client.py
deleted file mode 100644
index cdccac66..00000000
--- a/fedora/tg/client.py
+++ /dev/null
@@ -1,11 +0,0 @@
-'''This is for compatibility.
-
-The canonical location for this module from 0.3 on is fedora.client
-'''
-import warnings
-
-warnings.warn('fedora.tg.client has moved to fedora.client. This location'
- ' will disappear in 0.4', DeprecationWarning, stacklevel=2)
-
-# pylint: disable-msg=W0401,W0614
-from fedora.client import *
diff --git a/fedora/tg/controllers.py b/fedora/tg/controllers.py
deleted file mode 100644
index 21d280cc..00000000
--- a/fedora/tg/controllers.py
+++ /dev/null
@@ -1,121 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2008-2011 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
-#
-'''
-Controller functions that are standard across Fedora Applications
-
-
-.. moduleauthor:: Toshio Kuratomi
-'''
-from turbogears import flash
-from turbogears import identity, redirect
-from cherrypy import request, response
-
-from fedora.tg.utils import request_format
-
-from turbogears.i18n.utils import get_locale
-from turbogears.i18n.tg_gettext import tg_gettext
-
-
-def f_(msg):
- '''
- Translate given message from current tg locale
-
- Parameters
- :message: text to be translated
- Returns: Translated message string
- '''
- return tg_gettext(msg, get_locale(), 'python-fedora')
-
-
-def login(forward_url=None, *args, **kwargs):
- '''Page to become authenticated to the Account System.
-
- This shows a small login box to type in your username and password
- from the Fedora Account System.
-
- To use this, replace your current login controller method with::
-
- from fedora.controllers import login as fc_login
-
- @expose(template='yourapp.yourlogintemplate', allow_json=True)
- def login(self, forward_url=None, *args, **kwargs):
- login_dict = fc_login(forward_url, args, kwargs)
- # Add anything to the return dict that you need for your app
- return login_dict
-
- :kwarg: forward_url: The url to send to once authentication succeeds
- '''
- if forward_url:
- if isinstance(forward_url, list):
- forward_url = forward_url.pop(0)
- else:
- del request.params['forward_url']
-
- if not identity.current.anonymous and identity.was_login_attempted() \
- and not identity.get_identity_errors():
- # User is logged in
- flash(f_('Welcome, %s') % identity.current.user_name)
- if request_format() == 'json':
- # When called as a json method, doesn't make any sense to redirect
- # to a page. Returning the logged in identity is better.
- return dict(user=identity.current.user,
- _csrf_token=identity.current.csrf_token)
- redirect(forward_url or '/')
-
- if identity.was_login_attempted():
- msg = f_('The credentials you supplied were not correct or '
- 'did not grant access to this resource.')
- elif identity.get_identity_errors():
- msg = f_('You must provide your credentials before accessing '
- 'this resource.')
- else:
- msg = f_('Please log in.')
- if not forward_url:
- forward_url = request.headers.get('Referer', '/')
-
- response.status = 403
- return dict(
- logging_in=True, message=msg,
- forward_url=forward_url, previous_url=request.path_info,
- original_parameters=request.params
- )
-
-
-def logout(url=None):
- '''Logout from the server.
-
- To use this, replace your current login controller method with::
-
- from fedora.controllers import logout as fc_logout
-
- @expose(allow_json=True)
- def logout(self):
- return fc_logout()
-
- :kwarg url: If provided, when the user logs out, they will always be taken
- to this url. This defaults to taking the user to the URL they were
- at when they clicked logout.
- '''
- identity.current.logout()
- flash(f_('You have successfully logged out.'))
- if request_format() == 'json':
- return dict()
- if not url:
- url = request.headers.get('Referer', '/')
- redirect(url)
diff --git a/fedora/tg/identity/__init__.py b/fedora/tg/identity/__init__.py
deleted file mode 100644
index fd220400..00000000
--- a/fedora/tg/identity/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-'''TurboGears identity modules that work with Fedora.'''
-
-__all__ = ('jsonfasprovider',)
diff --git a/fedora/tg/identity/jsonfasprovider1.py b/fedora/tg/identity/jsonfasprovider1.py
deleted file mode 100644
index a62f6ff6..00000000
--- a/fedora/tg/identity/jsonfasprovider1.py
+++ /dev/null
@@ -1,292 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2007-2008 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
-#
-'''**Deprecated** Use jsonfasprovider2 instead a it provides CSRF protection.
-
-This plugin provides integration with the Fedora Account System using
-:term:`JSON` calls.
-
-
-.. moduleauthor:: Toshio Kuratomi
-.. moduleauthor:: Ricky Zhou
-'''
-
-from cherrypy import response
-from turbogears import config, identity
-from kitchen.text.converters import to_bytes
-from kitchen.pycompat24 import sets
-sets.add_builtin_set()
-
-from fedora.client import BaseClient, FedoraServiceError
-
-from fedora import __version__
-
-import crypt
-
-import logging
-log = logging.getLogger('turbogears.identity.safasprovider')
-
-
-class JsonFasIdentity(BaseClient):
- '''
- Associate an identity with a person in the auth system.
- '''
- cookie_name = config.get('visit.cookie.name', 'tg-visit')
- fas_url = config.get(
- 'fas.url', 'https://admin.fedoraproject.org/accounts/'
- )
- useragent = 'JsonFasIdentity/%s' % __version__
- cache_session = False
-
- def __init__(self, visit_key, user=None, username=None, password=None):
- if user:
- self._user = user
- self._groups = frozenset(
- [g['name'] for g in user['approved_memberships']]
- )
- self.visit_key = visit_key
- if visit_key:
- # Set the cookie to the user's tg_visit key before requesting
- # authentication. That way we link the two together.
- session_id = visit_key
- else:
- session_id = None
-
- debug = config.get('jsonfas.debug', False)
- super(JsonFasIdentity, self).__init__(
- self.fas_url,
- useragent=self.useragent, debug=debug,
- username=username, password=password,
- session_id=session_id, cache_session=self.cache_session)
-
- if self.debug:
- import inspect
- caller = inspect.getouterframes(inspect.currentframe())[1][3]
- log.debug('JsonFasIdentity.__init__ caller: %s' % caller)
-
- response.simple_cookie[self.cookie_name] = visit_key
-
- # Send a request so that we associate the visit_cookie with the user
- self.send_request('', auth=True)
- log.debug('Leaving JsonFasIdentity.__init__')
-
- def send_request(self, method, req_params=None, auth=False):
- '''
- Make an HTTP Request to a server method.
-
- We need to override the send_request provided by ``BaseClient`` to
- keep the visit_key in sync.
- '''
- log.debug('Entering jsonfas send_request')
- if self.session_id != self.visit_key:
- # When the visit_key changes (because the old key had expired or
- # been deleted from the db) change the visit_key in our variables
- # and the session cookie to be sent back to the client.
- self.visit_key = self.session_id
- response.simple_cookie[self.cookie_name] = self.visit_key
- log.debug('Leaving jsonfas send_request')
- return super(JsonFasIdentity, self).send_request(
- method, req_params=req_params, auth=auth)
-
- def _get_user(self):
- '''Retrieve information about the user from cache or network.'''
- # pylint: disable-msg=W0704
- try:
- return self._user
- except AttributeError:
- # User hasn't already been set
- pass
- # pylint: enable-msg=W0704
-
- if self.debug:
- import inspect
- caller = inspect.getouterframes(inspect.currentframe())[1][3]
- log.debug('JSONFASPROVIDER.send_request caller: %s' % caller)
-
- # Attempt to load the user. After this code executes, there *WILL* be
- # a _user attribute, even if the value is None.
- # Query the account system URL for our given user's sessionCookie
- # FAS returns user and group listing
- # pylint: disable-msg=W0702
- try:
- data = self.send_request('user/view', auth=True)
- except:
- # Any errors have to result in no user being set. The rest of the
- # framework doesn't know what to do otherwise.
- self._user = None
- return None
- # pylint: enable-msg=W0702
- if not data['person']:
- self._user = None
- return None
- self._user = data['person']
- self._groups = frozenset(
- [g['name'] for g in data['person']['approved_memberships']]
- )
- return self._user
- user = property(_get_user)
-
- def _get_user_name(self):
- '''Return the username for the user.'''
- if self.debug:
- import inspect
- caller = inspect.getouterframes(inspect.currentframe())[1][3]
- log.debug('JsonFasProvider._get_user_name caller: %s' % caller)
-
- if not self.user:
- return None
- return self.user['username']
- user_name = property(_get_user_name)
-
- def _get_anonymous(self):
- '''Return True if there's no user logged in.'''
- return not self.user
- anonymous = property(_get_anonymous)
-
- def _get_display_name(self):
- '''Return the user's display name.'''
- if not self.user:
- return None
- return self.user['human_name']
- display_name = property(_get_display_name)
-
- def _get_groups(self):
- '''Return the groups that a user is a member of.'''
- try:
- return self._groups
- except AttributeError:
- # User and groups haven't been returned. Since the json call
- # returns both user and groups, this is set at user creation time.
- self._groups = frozenset()
- return self._groups
- groups = property(_get_groups)
-
- def logout(self):
- '''
- Remove the link between this identity and the visit.
- '''
- if not self.visit_key:
- return
- # Call Account System Server logout method
- self.send_request('logout', auth=True)
-
-
-class JsonFasIdentityProvider(object):
- '''
- IdentityProvider that authenticates users against the fedora account system
- '''
- def __init__(self):
- # Default encryption algorithm is to use plain text passwords
- algorithm = config.get(
- 'identity.saprovider.encryption_algorithm', None
- )
- # pylint: disable-msg=W0212
- # TG does this so we shouldn't get rid of it.
- self.encrypt_password = lambda pw: identity._encrypt_password(
- algorithm, pw)
-
- def create_provider_model(self):
- '''
- Create the database tables if they don't already exist.
- '''
- # No database tables to create because the db is behind the FAS2
- # server
- pass
-
- def validate_identity(self, user_name, password, visit_key):
- '''
- Look up the identity represented by user_name and determine whether the
- password is correct.
-
- Must return either None if the credentials weren't valid or an object
- with the following properties:
-
- :user_name: original user name
- :user: a provider dependant object (TG_User or similar)
- :groups: a set of group IDs
- :permissions: a set of permission IDs
- '''
- # pylint: disable-msg=R0201
- # TG identity providers have this method so we can't get rid of it.
- try:
- user = JsonFasIdentity(visit_key,
- username=user_name,
- password=password)
- except FedoraServiceError as e:
- log.warning('Error logging in %(user)s: %(error)s' % {
- 'user': to_bytes(user_name), 'error': to_bytes(e)})
- return None
-
- return user
-
- def validate_password(self, user, user_name, password):
- '''
- Check the supplied user_name and password against existing credentials.
- Note: user_name is not used here, but is required by external
- password validation schemes that might override this method.
- If you use SqlAlchemyIdentityProvider, but want to check the passwords
- against an external source (i.e. PAM, LDAP, Windows domain, etc),
- subclass SqlAlchemyIdentityProvider, and override this method.
-
- :arg user: User information. Not used.
- :arg user_name: Given username.
- :arg password: Given, plaintext password.
- :returns: True if the password matches the username. Otherwise False.
- Can return False for problems within the Account System as well.
- '''
- # pylint: disable-msg=W0613,R0201
- # TG identity providers take user_name in case an external provider
- # needs it so we can't get rid of it.
- # TG identity providers have this method so we can't get rid of it.
- return user.password == crypt.crypt(password, user.password)
-
- def load_identity(self, visit_key):
- '''Lookup the principal represented by visit_key.
-
- :arg visit_key: The session key for whom we're looking up an identity.
- :returns: an object with the following properties:
-
- :user_name: original user name
- :user: a provider dependant object (TG_User or similar)
- :groups: a set of group IDs
- :permissions: a set of permission IDs
- '''
- # pylint: disable-msg=R0201
- # TG identity providers have this method so we can't get rid of it.
- return JsonFasIdentity(visit_key)
-
- def anonymous_identity(self):
- '''
- Must return an object with the following properties:
-
- :user_name: original user name
- :user: a provider dependant object (TG_User or similar)
- :groups: a set of group IDs
- :permissions: a set of permission IDs
- '''
- # pylint: disable-msg=R0201
- # TG identity providers have this method so we can't get rid of it.
- return JsonFasIdentity(None)
-
- def authenticated_identity(self, user):
- '''
- Constructs Identity object for user that has no associated visit_key.
- '''
- # pylint: disable-msg=R0201
- # TG identity providers have this method so we can't get rid of it.
- return JsonFasIdentity(None, user)
diff --git a/fedora/tg/identity/jsonfasprovider2.py b/fedora/tg/identity/jsonfasprovider2.py
deleted file mode 100644
index 0a4e48a4..00000000
--- a/fedora/tg/identity/jsonfasprovider2.py
+++ /dev/null
@@ -1,507 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2007-2011 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
-#
-# Adapted from code in the TurboGears project licensed under the MIT license.
-#
-'''
-This plugin provides authentication by integrating with the Fedora Account
-System using JSON calls.
-
-
-.. moduleauthor:: Toshio Kuratomi
-.. moduleauthor:: Ricky Zhou
-'''
-
-import crypt
-from hashlib import sha1
-
-import six
-from turbogears import config, identity
-from turbogears.identity import set_login_attempted
-import cherrypy
-from kitchen.pycompat24 import sets
-from kitchen.text.converters import to_bytes
-
-sets.add_builtin_set()
-
-from fedora.client import (
- AccountSystem, AuthError, BaseClient,
- FedoraServiceError
-)
-
-from fedora import __version__
-
-import logging
-log = logging.getLogger('turbogears.identity.jsonfasprovider')
-
-if config.get('identity.ssl', False):
- fas_user = config.get('fas.username', None)
- fas_password = config.get('fas.password', None)
- if not (fas_user and fas_password):
- raise identity.IdentityConfigurationException(
- 'Cannot enable ssl certificate auth via identity.ssl'
- ' without setting fas.usernamme and fas.password for'
- ' authorization')
- __url = config.get('fas.url', None)
- if __url:
- fas = AccountSystem(__url, username=config.get('fas.username'),
- password=config.get('fas.password'), retries=3)
-
-
-class JsonFasIdentity(BaseClient):
- '''Associate an identity with a person in the auth system.
- '''
- cookie_name = config.get('visit.cookie.name', 'tg-visit')
- fas_url = config.get(
- 'fas.url',
- 'https://admin.fedoraproject.org/accounts/'
- )
- useragent = 'JsonFasIdentity/%s' % __version__
- cache_session = False
-
- def __init__(self, visit_key=None, user=None, username=None, password=None,
- using_ssl=False):
- # The reason we have both _retrieved_user and _user is this:
- # _user is set if both the user is authenticated and a csrf_token is
- # present.
- # _retrieved_user actually caches the user info from the server.
- # Sometimes we have to determine if a user is only lacking a token,
- # then retrieved_user comes in handy.
- self._retrieved_user = None
- self.log = log
- self.visit_key = visit_key
- session_id = visit_key
- self._group_ids = frozenset()
- self.using_ssl = using_ssl
- if user:
- self._user = user
- self._user_retrieved = user
- self._groups = frozenset(
- [g['name'] for g in user['approved_memberships']]
- )
-
- debug = config.get('jsonfas.debug', False)
- super(JsonFasIdentity, self).__init__(
- self.fas_url,
- useragent=self.useragent, debug=debug,
- username=username, password=password,
- session_id=session_id, cache_session=self.cache_session,
- retries=3
- )
-
- if self.debug:
- import inspect
- caller = inspect.getouterframes(inspect.currentframe())[1][3]
- self.log.debug('JsonFasIdentity.__init__ caller: %s' % caller)
-
- cherrypy.response.simple_cookie[self.cookie_name] = visit_key
-
- self.login(using_ssl)
- self.log.debug('Leaving JsonFasIdentity.__init__')
-
- def send_request(self, method, req_params=None, auth=False):
- '''Make an HTTP Request to a server method.
-
- We need to override the send_request provided by ``BaseClient`` to
- keep the visit_key in sync.
- '''
- self.log.debug('entering jsonfas send_request')
- if self.session_id != self.visit_key:
- # When the visit_key changes (because the old key had expired or
- # been deleted from the db) change the visit_key in our variables
- # and the session cookie to be sent back to the client.
- self.visit_key = self.session_id
- cherrypy.response.simple_cookie[self.cookie_name] = self.visit_key
- self.log.debug('leaving jsonfas send_request')
- return super(JsonFasIdentity, self).send_request(
- method,
- req_params=req_params,
- auth=auth,
- retries=3
- )
-
- def __retrieve_user(self):
- '''Attempt to load the user from the visit_key.
-
- :returns: a user or None
- '''
- if self.debug:
- import inspect
- caller = inspect.getouterframes(inspect.currentframe())[2][3]
- self.log.debug('JSONFASPROVIDER.send_request caller: %s' % caller)
-
- # The cached value can be in four states:
- # Holds a user: we successfully retrieved it last time, return it
- # Holds None: we haven't yet tried to retrieve a user, do so now
- # Holds a session_id that is the same as our session_id, we
- # unsuccessfully tried to retrieve a session with this id already,
- # return None Holds a session_id that is different than the current
- # session_id: we tried with a previous session_id; try again with the
- # new one.
- if self._retrieved_user:
- if isinstance(self._retrieved_user, six.string_types):
- if self._retrieved_user == self.session_id:
- return None
- else:
- self._retrieved_user = None
- else:
- return self._retrieved_user
- # I hope this is a safe place to double-check the SSL variables.
- # TODO: Double check my logic with this - is it unnecessary to
- # check that the username matches up?
- if self.using_ssl:
- if cherrypy.request.headers['X-Client-Verify'] != 'SUCCESS':
- self.logout()
- return None
- # Retrieve the user information differently when using ssl
- try:
- person = fas.person_by_username(self.username, auth=True)
- except Exception as e: # pylint: disable-msg=W0703
- # :W0703: Any errors have to result in no user being set. The
- # rest of the framework doesn't know what to do otherwise.
- self.log.warning('jsonfasprovider, ssl, returned errors'
- ' from send_request: %s' % to_bytes(e))
- person = None
- self._retrieved_user = person or None
- return self._retrieved_user
- # pylint: disable-msg=W0702
- try:
- data = self.send_request('user/view', auth=True)
- except AuthError as e:
- # Failed to login with present credentials
- self._retrieved_user = self.session_id
- return None
- except Exception as e: # pylint: disable-msg=W0703
- # :W0703: Any errors have to result in no user being set. The rest
- # of the framework doesn't know what to do otherwise.
- self.log.warning('jsonfasprovider returned errors from'
- ' send_request: %s' % to_bytes(e))
- return None
- # pylint: enable-msg=W0702
-
- self._retrieved_user = data['person'] or None
- return self._retrieved_user
-
- def _get_user(self):
- '''Get user instance for this identity.'''
- visit = self.visit_key
- if not visit:
- # No visit, no user
- self._user = None
- else:
- if not (self.username and self.password):
- # Unless we were given the user_name and password to login on
- # this request, a CSRF token is required
- if (not '_csrf_token' in cherrypy.request.params or
- cherrypy.request.params['_csrf_token'] !=
- sha1(self.visit_key).hexdigest()):
- self.log.info("Bad _csrf_token")
- if '_csrf_token' in cherrypy.request.params:
- self.log.info("visit: %s token: %s" % (
- self.visit_key,
- cherrypy.request.params['_csrf_token']))
- else:
- self.log.info('No _csrf_token present')
- cherrypy.request.fas_identity_failure_reason = 'bad_csrf'
- self._user = None
-
- # pylint: disable-msg=W0704
- try:
- return self._user
- except AttributeError:
- # User hasn't already been set
- # Attempt to load the user. After this code executes, there
- # *will* be a _user attribute, even if the value is None.
- self._user = self.__retrieve_user()
-
- if self._user:
- self._groups = frozenset(
- [g['name'] for g in self._user.approved_memberships]
- )
- else:
- self._groups = frozenset()
-
- # pylint: enable-msg=W0704
- return self._user
- user = property(_get_user)
-
- def _get_token(self):
- '''Get the csrf token for this identity'''
- if self.visit_key:
- return sha1(self.visit_key).hexdigest()
- else:
- return ''
- csrf_token = property(_get_token)
-
- def _get_user_name(self):
- '''Get user name of this identity.'''
- if self.debug:
- import inspect
- caller = inspect.getouterframes(inspect.currentframe())[1][3]
- self.log.debug(
- 'JsonFasProvider._get_user_name caller: %s' % caller)
-
- if not self.user:
- return None
- return self.user.username
- user_name = property(_get_user_name)
-
- ### TG: Same as TG-1.0.8
- def _get_user_id(self):
- '''
- Get user id of this identity.
- '''
- if not self.user:
- return None
- return self.user.id
- user_id = property(_get_user_id)
-
- ### TG: Same as TG-1.0.8
- def _get_anonymous(self):
- '''
- Return True if not logged in.
- '''
- return not self.user
- anonymous = property(_get_anonymous)
-
- def _get_only_token(self):
- '''
- In one specific instance in the login template we need to know whether
- an anonymous user is just lacking a token.
- '''
- if self.__retrieve_user():
- # user is valid, just the token is missing
- return True
-
- # Else the user still has to login
- return False
- only_token = property(_get_only_token)
-
- def _get_permissions(self):
- '''Get set of permission names of this identity.'''
- # pylint: disable-msg=R0201
- # :R0201: method is part of the TG Identity API
- ### TG difference: No permissions in FAS
- return frozenset()
- permissions = property(_get_permissions)
-
- def _get_display_name(self):
- '''Return the user's display name.
-
- .. warning::
- This is not a TG standard attribute. Don't use this if you want
- to be compatible with other identity providers.
- '''
- if not self.user:
- return None
- return self.user['human_name']
- display_name = property(_get_display_name)
-
- def _get_groups(self):
- '''Return the groups that a user is a member of.'''
- try:
- return self._groups
- except AttributeError: # pylint: disable-msg=W0704
- # :W0704: Groups haven't been computed yet
- pass
- if not self.user:
- # User and groups haven't been returned. Since the json call
- # computes both user and groups, this will now be set.
- self._groups = frozenset()
- return self._groups
- groups = property(_get_groups)
-
- def _get_group_ids(self):
- '''Get set of group IDs of this identity.'''
- try:
- return self._group_ids
- except AttributeError: # pylint: disable-msg=W0704
- # :W0704: Groups haven't been computed yet
- pass
- if not self.groups:
- self._group_ids = frozenset()
- else:
- self._group_ids = frozenset([g.id for g in
- self._user.approved_memberships])
- return self._group_ids
- group_ids = property(_get_group_ids)
-
- ### TG: Same as TG-1.0.8
- def _get_login_url(self):
- '''Get the URL for the login page.'''
- return identity.get_failure_url()
- login_url = property(_get_login_url)
-
- def login(self, using_ssl=False):
- '''Send a request so that we associate the visit_cookie with the user
-
- :kwarg using_ssl: Boolean that tells whether ssl was used to
- authenticate
- '''
- if not using_ssl:
- # This is only of value if we have username and password
- # which we don't if using ssl certificates
- self.send_request('', auth=True)
- self.using_ssl = using_ssl
-
- def logout(self):
- '''
- Remove the link between this identity and the visit.
- '''
- if not self.visit_key:
- return
- # Call Account System Server logout method
- self.send_request('logout', auth=True)
-
-
-class JsonFasIdentityProvider(object):
- '''
- IdentityProvider that authenticates users against the fedora account system
- '''
- def __init__(self):
- # Default encryption algorithm is to use plain text passwords
- algorithm = config.get(
- 'identity.saprovider.encryption_algorithm', None)
- self.log = log
- # pylint: disable-msg=W0212
- # TG does this so we shouldn't get rid of it.
- self.encrypt_password = lambda pw: identity._encrypt_password(
- algorithm, pw)
-
- def create_provider_model(self):
- '''
- Create the database tables if they don't already exist.
- '''
- # No database tables to create because the db is behind the FAS2
- # server
- pass
-
- def validate_identity(self, user_name, password, visit_key):
- '''
- Look up the identity represented by user_name and determine whether the
- password is correct.
-
- Must return either None if the credentials weren't valid or an object
- with the following properties:
-
- :user_name: original user name
- :user: a provider dependant object (TG_User or similar)
- :groups: a set of group IDs
- :permissions: a set of permission IDs
-
- :arg user_name: user_name we're authenticating. If None, we'll try
- to lookup a username from SSL variables
- :arg password: password to authenticate user_name with
- :arg visit_key: visit_key from the user's session
- '''
- using_ssl = False
- if not user_name and config.get('identity.ssl'):
- if cherrypy.request.headers['X-Client-Verify'] == 'SUCCESS':
- user_name = cherrypy.request.headers['X-Client-CN']
- cherrypy.request.fas_provided_username = user_name
- using_ssl = True
-
- # pylint: disable-msg=R0201
- # TG identity providers have this method so we can't get rid of it.
- try:
- user = JsonFasIdentity(visit_key, username=user_name,
- password=password, using_ssl=using_ssl)
- except FedoraServiceError as e:
- self.log.warning('Error logging in %(user)s: %(error)s' % {
- 'user': to_bytes(user_name), 'error': to_bytes(e)})
- return None
-
- return user
-
- def validate_password(self, user, user_name, password):
- '''
- Check the supplied user_name and password against existing credentials.
- Note: user_name is not used here, but is required by external
- password validation schemes that might override this method.
- If you use SqlAlchemyIdentityProvider, but want to check the passwords
- against an external source (i.e. PAM, LDAP, Windows domain, etc),
- subclass SqlAlchemyIdentityProvider, and override this method.
-
- :arg user: User information.
- :arg user_name: Given username. Not used.
- :arg password: Given, plaintext password.
- :returns: True if the password matches the username. Otherwise False.
- Can return False for problems within the Account System as well.
- '''
- # pylint: disable-msg=R0201,W0613
- # :R0201: TG identity providers must instantiate this method.
-
- # crypt.crypt(stuff, '') == ''
- # Just kill any possibility of blanks.
- if not user.password:
- return False
- if not password:
- return False
-
- # pylint: disable-msg=W0613
- # :W0613: TG identity providers have this method
- return to_bytes(user.password) == crypt.crypt(
- to_bytes(password),
- to_bytes(user.password)
- )
-
- def load_identity(self, visit_key):
- '''Lookup the principal represented by visit_key.
-
- :arg visit_key: The session key for whom we're looking up an identity.
- :return: an object with the following properties:
- :user_name: original user name
- :user: a provider dependant object (TG_User or similar)
- :groups: a set of group IDs
- :permissions: a set of permission IDs
- '''
- # pylint: disable-msg=R0201
- # :R0201: TG identity providers must instantiate this method.
- ident = JsonFasIdentity(visit_key)
- if 'csrf_login' in cherrypy.request.params:
- cherrypy.request.params.pop('csrf_login')
- set_login_attempted(True)
- return ident
-
- def anonymous_identity(self):
- '''Returns an anonymous user object
-
- :return: an object with the following properties:
- :user_name: original user name
- :user: a provider dependant object (TG_User or similar)
- :groups: a set of group IDs
- :permissions: a set of permission IDs
- '''
- # pylint: disable-msg=R0201
- # :R0201: TG identity providers must instantiate this method.
- return JsonFasIdentity(None)
-
- def authenticated_identity(self, user):
- '''
- Constructs Identity object for user that has no associated visit_key.
-
- :arg user: The user structure the identity is constructed from
- :return: an object with the following properties:
- :user_name: original user name
- :user: a provider dependant object (TG_User or similar)
- :groups: a set of group IDs
- :permissions: a set of permission IDs
- '''
- # pylint: disable-msg=R0201
- # :R0201: TG identity providers must instantiate this method.
- return JsonFasIdentity(None, user)
diff --git a/fedora/tg/identity/soprovidercsrf.py b/fedora/tg/identity/soprovidercsrf.py
deleted file mode 100644
index b9c50005..00000000
--- a/fedora/tg/identity/soprovidercsrf.py
+++ /dev/null
@@ -1,551 +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
-#
-# Adapted from code in the TurboGears project licensed under the MIT license.
-#
-'''
-This plugin provides authentication from a database using sqlobject. In
-addition to the standard stuff provided by the TurboGears soprovider, it
-adds a csrf_token attribute that can be used to protect the server from
-csrf attacks.
-
-.. versionadded:: 0.3.14
-
-
-.. moduleauthor:: Toshio Kuratomi
-'''
-from datetime import datetime
-
-try:
- from hashlib import sha1 as hash_constructor
-except ImportError:
- from sha import new as hash_constructor
-
-
-from sqlobject import SQLObject, SQLObjectNotFound, RelatedJoin, \
- DateTimeCol, IntCol, StringCol, UnicodeCol
-from sqlobject.inheritance import InheritableSQLObject
-
-import warnings
-import logging
-log = logging.getLogger("turbogears.identity.soprovider")
-
-import cherrypy
-import turbogears
-from turbogears import identity
-from turbogears.database import PackageHub
-from turbogears.util import load_class
-from turbogears.identity import set_login_attempted
-from turbojson.jsonify import jsonify_sqlobject, jsonify
-
-hub = PackageHub("turbogears.identity")
-__connection__ = hub
-
-try:
- set, frozenset
-except NameError: # Python 2.3
- from sets import Set as set, ImmutableSet as frozenset
-
-
-def to_db_encoding(s, encoding):
- if isinstance(s, str):
- pass
- elif hasattr(s, '__unicode__'):
- s = unicode(s)
- if isinstance(s, unicode):
- s = s.encode(encoding)
- return s
-
-
-class DeprecatedAttr(object):
- def __init__(self, old_name, new_name):
- self.old_name = old_name
- self.new_name = new_name
-
- def __get__(self, obj, type=None):
- warnings.warn("%s has been deprecated in favour of %s" %
- (self.old_name, self.new_name), DeprecationWarning)
- return getattr(obj, self.new_name)
-
- def __set__(self, obj, value):
- warnings.warn( "%s has been deprecated in favour of %s" %
- (self.old_name, self.new_name), DeprecationWarning)
- return setattr(obj, self.new_name, value)
-
-
-# Global class references --
-# these will be set when the provider is initialised.
-user_class = None
-group_class = None
-permission_class = None
-visit_class = None
-
-
-class SqlObjectCsrfIdentity(object):
- """Identity that uses a model from a database (via SQLObject)."""
-
- def __init__(self, visit_key=None, user=None):
- # The reason we have both _retrieved_user and _user is this:
- # _user is set if both the user is authenticated and a csrf_token is
- # present.
- # _retrieved_user actually caches the user info from the server.
- # Sometimes we have to determine if a user is only lacking a token,
- # then retrieved_user comes in handy.
- self._retrieved_user = None
- self.visit_key = visit_key
- if user:
- self._user = user
- self._user_retrieved = user
- if visit_key is not None:
- self.login()
-
- def __retrieve_user(self):
- '''Attempt to load the user from the visit_key
-
- :returns: a user or None
- '''
- if self._retrieved_user:
- return self._retrieved_user
-
- # Retrieve the user info from the db
- visit = self.visit_link
- if not visit:
- return None
- try:
- self._retrieved_user = user_class.get(visit.user_id)
- except SQLObjectNotFound:
- log.warning("No such user with ID: %s", visit.user_id)
- self._retrieved_user = None
- return self._retrieved_user
-
- def _get_user(self):
- """Get user instance for this identity."""
- try:
- return self._user
- except AttributeError:
- # User hasn't already been set
- pass
- # Attempt to load the user. After this code executes, there *will* be
- # a _user attribute, even if the value is None.
- visit = self.visit_link
- if not visit:
- # No visit, no user
- self._user = None
- else:
- # Unless we were given the user_name and password to login on
- # this request, a CSRF token is required
- if (not '_csrf_token' in cherrypy.request.params or
- cherrypy.request.params['_csrf_token'] !=
- hash_constructor(self.visit_key).hexdigest()):
- log.info("Bad _csrf_token")
- if '_csrf_token' in cherrypy.request.params:
- log.info("visit: %s token: %s" % (self.visit_key,
- cherrypy.request.params['_csrf_token']))
- else:
- log.info('No _csrf_token present')
- cherrypy.request.fas_identity_failure_reason = 'bad_csrf'
- self._user = None
- try:
- return self._user
- except AttributeError:
- # User hasn't already been set
- # Attempt to load the user. After this code executes, there
- # *will* be a _user attribute, even if the value is None.
- self._user = self.__retrieve_user()
- return self._user
- user = property(_get_user)
-
- def _get_token(self):
- '''Get the csrf token for this identity'''
- if self.visit_key:
- return hash_constructor(self.visit_key).hexdigest()
- else:
- return ''
- csrf_token = property(_get_token)
-
- def _get_user_name(self):
- """Get user name of this identity."""
- if not self.user:
- return None
- return self.user.user_name
- user_name = property(_get_user_name)
-
- def _get_user_id(self):
- """Get user id of this identity."""
- if not self.user:
- return None
- return self.user.id
- user_id = property(_get_user_id)
-
- def _get_anonymous(self):
- """Return true if not logged in."""
- return not self.user
- anonymous = property(_get_anonymous)
-
- def _get_only_token(self):
- '''
- In one specific instance in the login template we need to know whether
- an anonymous user is just lacking a token.
- '''
- if self.__retrieve_user():
- # user is valid, just the token is missing
- return True
-
- # Else the user still has to login
- return False
- only_token = property(_get_only_token)
-
- def _get_permissions(self):
- """Get set of permission names of this identity."""
- try:
- return self._permissions
- except AttributeError:
- # Permissions haven't been computed yet
- pass
- if not self.user:
- self._permissions = frozenset()
- else:
- self._permissions = frozenset(
- [p.permission_name for p in self.user.permissions])
- return self._permissions
- permissions = property(_get_permissions)
-
- def _get_groups(self):
- """Get set of group names of this identity."""
- try:
- return self._groups
- except AttributeError:
- # Groups haven't been computed yet
- pass
- if not self.user:
- self._groups = frozenset()
- else:
- self._groups = frozenset([g.group_name for g in self.user.groups])
- return self._groups
- groups = property(_get_groups)
-
- def _get_group_ids(self):
- """Get set of group IDs of this identity."""
- try:
- return self._group_ids
- except AttributeError:
- # Groups haven't been computed yet
- pass
- if not self.user:
- self._group_ids = frozenset()
- else:
- self._group_ids = frozenset([g.id for g in self.user.groups])
- return self._group_ids
- group_ids = property(_get_group_ids)
-
- def _get_visit_link(self):
- """Get the visit link to this identity."""
- if self.visit_key is None:
- return None
- try:
- return visit_class.by_visit_key(self.visit_key)
- except SQLObjectNotFound:
- return None
- visit_link = property(_get_visit_link)
-
- def _get_login_url(self):
- """Get the URL for the login page."""
- return identity.get_failure_url()
- login_url = property(_get_login_url)
-
- def login(self):
- """Set the link between this identity and the visit."""
- visit = self.visit_link
- if visit:
- visit.user_id = self._user.id
- else:
- visit = visit_class(visit_key=self.visit_key, user_id=self._user.id)
-
- def logout(self):
- """Remove the link between this identity and the visit."""
- visit = self.visit_link
- if visit:
- visit.destroySelf()
- # Clear the current identity
- identity.set_current_identity(SqlObjectCsrfIdentity())
-
-
-class SqlObjectCsrfIdentityProvider(object):
- """IdentityProvider that uses a model from a database (via SQLObject)."""
-
- def __init__(self):
- super(SqlObjectCsrfIdentityProvider, self).__init__()
- get = turbogears.config.get
-
- global user_class, group_class, permission_class, visit_class
-
- user_class_path = get("identity.soprovider.model.user",
- __name__ + ".TG_User")
- user_class = load_class(user_class_path)
- if user_class:
- log.info("Succesfully loaded \"%s\"" % user_class_path)
- try:
- self.user_class_db_encoding = \
- user_class.sqlmeta.columns['user_name'].dbEncoding or 'UTF-8'
- except (KeyError, AttributeError):
- self.user_class_db_encoding = 'UTF-8'
- group_class_path = get("identity.soprovider.model.group",
- __name__ + ".TG_Group")
- group_class = load_class(group_class_path)
- if group_class:
- log.info("Succesfully loaded \"%s\"" % group_class_path)
-
- permission_class_path = get("identity.soprovider.model.permission",
- __name__ + ".TG_Permission")
- permission_class = load_class(permission_class_path)
- if permission_class:
- log.info("Succesfully loaded \"%s\"" % permission_class_path)
-
- visit_class_path = get("identity.soprovider.model.visit",
- __name__ + ".TG_VisitIdentity")
- visit_class = load_class(visit_class_path)
- if visit_class:
- log.info("Succesfully loaded \"%s\"" % visit_class_path)
-
- # Default encryption algorithm is to use plain text passwords
- algorithm = get("identity.soprovider.encryption_algorithm", None)
- self.encrypt_password = lambda pw: \
- identity._encrypt_password(algorithm, pw)
-
- def create_provider_model(self):
- """Create the database tables if they don't already exist."""
- try:
- hub.begin()
- user_class.createTable(ifNotExists=True)
- group_class.createTable(ifNotExists=True)
- permission_class.createTable(ifNotExists=True)
- visit_class.createTable(ifNotExists=True)
- hub.commit()
- hub.end()
- except KeyError:
- log.warning("No database is configured:"
- " SqlObjectCsrfIdentityProvider is disabled.")
- return
-
- def validate_identity(self, user_name, password, visit_key):
- """Validate the identity represented by user_name using the password.
-
- Must return either None if the credentials weren't valid or an object
- with the following properties:
- user_name: original user name
- user: a provider dependant object (TG_User or similar)
- groups: a set of group names
- permissions: a set of permission names
-
- """
- try:
- user_name = to_db_encoding(user_name, self.user_class_db_encoding)
- user = user_class.by_user_name(user_name)
- if not self.validate_password(user, user_name, password):
- log.info("Passwords don't match for user: %s", user_name)
- return None
- log.info("Associating user (%s) with visit (%s)",
- user_name, visit_key)
- return SqlObjectCsrfIdentity(visit_key, user)
- except SQLObjectNotFound:
- log.warning("No such user: %s", user_name)
- return None
-
- def validate_password(self, user, user_name, password):
- """Check the user_name and password against existing credentials.
-
- Note: user_name is not used here, but is required by external
- password validation schemes that might override this method.
- If you use SqlObjectCsrfIdentityProvider, but want to check the passwords
- against an external source (i.e. PAM, a password file, Windows domain),
- subclass SqlObjectCsrfIdentityProvider, and override this method.
-
- """
- return user.password == self.encrypt_password(password)
-
- def load_identity(self, visit_key):
- """Lookup the principal represented by user_name.
-
- Return None if there is no principal for the given user ID.
-
- Must return an object with the following properties:
- user_name: original user name
- user: a provider dependant object (TG_User or similar)
- groups: a set of group names
- permissions: a set of permission names
-
- """
- ident = SqlObjectCsrfIdentity(visit_key)
- if 'csrf_login' in cherrypy.request.params:
- cherrypy.request.params.pop('csrf_login')
- set_login_attempted(True)
- return ident
-
- def anonymous_identity(self):
- """Return anonymous identity.
-
- Must return an object with the following properties:
- user_name: original user name
- user: a provider dependant object (TG_User or similar)
- groups: a set of group names
- permissions: a set of permission names
-
- """
- return SqlObjectCsrfIdentity()
-
- def authenticated_identity(self, user):
- """Constructs Identity object for users with no visit_key."""
- return SqlObjectCsrfIdentity(user=user)
-
-
-class TG_VisitIdentity(SQLObject):
- """A visit to your website."""
- class sqlmeta:
- table = "tg_visit_identity"
-
- visit_key = StringCol(length=40, alternateID=True,
- alternateMethodName="by_visit_key")
- user_id = IntCol()
-
-
-class TG_Group(InheritableSQLObject):
- """An ultra-simple group definition."""
- class sqlmeta:
- table = "tg_group"
-
- group_name = UnicodeCol(length=16, alternateID=True,
- alternateMethodName="by_group_name")
- display_name = UnicodeCol(length=255)
- created = DateTimeCol(default=datetime.now)
-
- # Old names
- groupId = DeprecatedAttr("groupId", "group_name")
- displayName = DeprecatedAttr("displayName", "display_name")
-
- # collection of all users belonging to this group
- users = RelatedJoin("TG_User", intermediateTable="tg_user_group",
- joinColumn="group_id", otherColumn="user_id")
-
- # collection of all permissions for this group
- permissions = RelatedJoin("TG_Permission", joinColumn="group_id",
- intermediateTable="tg_group_permission",
- otherColumn="permission_id")
-
-[jsonify.when('isinstance(obj, TG_Group)')]
-def jsonify_group(obj):
- """Convert group to JSON."""
- result = jsonify_sqlobject(obj)
- result["users"] = [u.user_name for u in obj.users]
- result["permissions"] = [p.permission_name for p in obj.permissions]
- return result
-
-
-class TG_User(InheritableSQLObject):
- """Reasonably basic User definition."""
- class sqlmeta:
- table = "tg_user"
-
- user_name = UnicodeCol(length=16, alternateID=True,
- alternateMethodName="by_user_name")
- email_address = UnicodeCol(length=255, alternateID=True,
- alternateMethodName="by_email_address")
- display_name = UnicodeCol(length=255)
- password = UnicodeCol(length=40)
- created = DateTimeCol(default=datetime.now)
-
- # Old attribute names
- userId = DeprecatedAttr("userId", "user_name")
- emailAddress = DeprecatedAttr("emailAddress", "email_address")
- displayName = DeprecatedAttr("displayName", "display_name")
-
- # groups this user belongs to
- groups = RelatedJoin("TG_Group", intermediateTable="tg_user_group",
- joinColumn="user_id", otherColumn="group_id")
-
- def _get_permissions(self):
- perms = set()
- for g in self.groups:
- perms = perms | set(g.permissions)
- return perms
-
- def _set_password(self, cleartext_password):
- """Run cleartext_password through the hash algorithm before saving."""
- try:
- hash = identity.current_provider.encrypt_password(
- cleartext_password)
- except identity.exceptions.IdentityManagementNotEnabledException:
- # Creating identity provider just to encrypt password
- # (so we don't reimplement the encryption step).
- ip = SqlObjectCsrfIdentityProvider()
- hash = ip.encrypt_password(cleartext_password)
- if hash == cleartext_password:
- log.info("Identity provider not enabled,"
- " and no encryption algorithm specified in config."
- " Setting password as plaintext.")
- self._SO_set_password(hash)
-
- def set_password_raw(self, password):
- """Save the password as-is to the database."""
- self._SO_set_password(password)
-
-[jsonify.when('isinstance(obj, TG_User)')]
-def jsonify_user(obj):
- """Convert user to JSON."""
- result = jsonify_sqlobject(obj)
- del result['password']
- result["groups"] = [g.group_name for g in obj.groups]
- result["permissions"] = [p.permission_name for p in obj.permissions]
- return result
-
-
-class TG_Permission(InheritableSQLObject):
- """Permissions for a given group."""
- class sqlmeta:
- table = "tg_permission"
-
- permission_name = UnicodeCol(length=16, alternateID=True,
- alternateMethodName="by_permission_name")
- description = UnicodeCol(length=255)
-
- # Old attributes
- permissionId = DeprecatedAttr("permissionId", "permission_name")
-
- groups = RelatedJoin("TG_Group", intermediateTable="tg_group_permission",
- joinColumn="permission_id", otherColumn="group_id")
-
-[jsonify.when('isinstance(obj, TG_Permission)')]
-def jsonify_permission(obj):
- """Convert permissions to JSON."""
- result = jsonify_sqlobject(obj)
- result["groups"] = [g.group_name for g in obj.groups]
- return result
-
-
-def encrypt_password(cleartext_password):
- """Encrypt given cleartext password."""
- try:
- hash = identity.current_provider.encrypt_password(cleartext_password)
- except identity.exceptions.RequestRequiredException:
- # Creating identity provider just to encrypt password
- # (so we don't reimplement the encryption step).
- ip = SqlObjectCsrfIdentityProvider()
- hash = ip.encrypt_password(cleartext_password)
- if hash == cleartext_password:
- log.info("Identity provider not enabled, and no encryption "
- "algorithm specified in config. Setting password as plaintext.")
- return hash
diff --git a/fedora/tg/json.py b/fedora/tg/json.py
deleted file mode 100644
index c7d60ceb..00000000
--- a/fedora/tg/json.py
+++ /dev/null
@@ -1,191 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2007-2008 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
-#
-'''
-JSON Helper functions. Most JSON code directly related to classes is
-implemented via the __json__() methods in model.py. These methods define
-methods of transforming a class into json for a few common types.
-
-A JSON-based API(view) for your app. Most rules would look like::
-
- @jsonify.when("isinstance(obj, YourClass)")
- def jsonify_yourclass(obj):
- return [obj.val1, obj.val2]
-
-@jsonify can convert your objects to following types:
-lists, dicts, numbers and strings
-
-.. moduleauthor:: Toshio Kuratomi
-'''
-
-# Pylint ignored messages
-# :E1101: turbogears.jsonify monkey patches some functionality in. These do
-# not show up when we pylint so it thinks the members di not exist.
-
-import sqlalchemy
-import sqlalchemy.orm
-import sqlalchemy.ext.associationproxy
-import sqlalchemy.engine.base
-from turbojson.jsonify import jsonify
-
-
-class SABase(object):
- '''Base class for SQLAlchemy mapped objects.
-
- This base class makes sure we have a __json__() method on each SQLAlchemy
- mapped object that knows how to:
-
- 1) Return json for the object.
- 2) Selectively add tables pulled in from the table to the data we're
- returning.
- '''
- # :R0903: The SABase object just adds a __json__ method to all SA mapped
- # classes so they can be serialized as json. It's used as a base class
- # and that's it.
- # pylint: disable-msg=R0903
- def __json__(self):
- '''Transform any SA mapped class into json.
-
- This method takes an SA mapped class and turns the "normal" python
- attributes into json. The properties (from properties in the mapper)
- are also included if they have an entry in json_props. You make
- use of this by setting json_props in the controller.
-
- Example controller::
-
- john = model.Person.get_by(name='John')
- # Person has a property, addresses, linking it to an Address class.
- # Address has a property, phone_nums, linking it to a Phone class.
- john.json_props = {'Person': ['addresses'],
- 'Address': ['phone_nums']}
- return dict(person=john)
-
- json_props is a dict that maps class names to lists of properties you
- want to output. This allows you to selectively pick properties you
- are interested in for one class but not another. You are responsible
- for avoiding loops. ie: *don't* do this::
-
- john.json_props = {'Person': ['addresses'], 'Address': ['people']}
- '''
- props = {}
- prop_list = {}
- if hasattr(self, 'json_props'):
- for base_class in self.__class__.__mro__:
- # pylint: disable-msg=E1101
- if base_class.__name__ in self.json_props:
- prop_list = self.json_props[base_class.__name__]
- break
- # pylint: enable-msg=E1101
-
- # Load all the columns from the table
- for column in sqlalchemy.orm.object_mapper(self).iterate_properties:
- if isinstance(column, sqlalchemy.orm.properties.ColumnProperty):
- props[column.key] = getattr(self, column.key)
-
- # Load things that are explicitly listed
- for field in prop_list:
- props[field] = getattr(self, field)
- try:
- # pylint: disable-msg=E1101
- props[field].json_props = self.json_props
- except AttributeError: # pylint: disable-msg=W0704
- # :W0704: Certain types of objects are terminal and won't
- # allow setting json_props
- pass
-
- # Note: Because of the architecture of simplejson and turbojson,
- # anything that inherits from a builtin list, tuple, basestring,
- # or dict but needs special handling needs to be specified
- # expicitly here. Using the @jsonify.when() decorator won't work.
- if isinstance(props[field],
- sqlalchemy.orm.collections.InstrumentedList):
- props[field] = jsonify_salist(props[field])
-
- return props
-
-
-@jsonify.when('isinstance(obj, sqlalchemy.orm.query.Query)')
-def jsonify_sa_select_results(obj):
- '''Transform selectresults into lists.
-
- The one special thing is that we bind the special json_props into each
- descendent. This allows us to specify a json_props on the toplevel
- query result and it will pass to all of its children.
-
- :arg obj: sqlalchemy Query object to jsonify
- :Returns: list representation of the Query with each element in it given
- a json_props attributes
- '''
- if 'json_props' in obj.__dict__:
- for element in obj:
- element.json_props = obj.json_props
- return list(obj)
-
-
-# Note: due to the way simplejson works, InstrumentedList has to be taken care
-# of explicitly in SABase's __json__() method. (This is true of any object
-# derived from a builtin type (list, dict, tuple, etc))
-@jsonify.when('''(
- isinstance(obj, sqlalchemy.orm.collections.InstrumentedList) or
- isinstance(obj, sqlalchemy.orm.attributes.InstrumentedAttribute) or
- isinstance(obj, sqlalchemy.ext.associationproxy._AssociationList))''')
-def jsonify_salist(obj):
- '''Transform SQLAlchemy InstrumentedLists into json.
-
- The one special thing is that we bind the special json_props into each
- descendent. This allows us to specify a json_props on the toplevel
- query result and it will pass to all of its children.
-
- :arg obj: One of the sqlalchemy list types to jsonify
- :Returns: list of jsonified elements
- '''
- if 'json_props' in obj.__dict__:
- for element in obj:
- element.json_props = obj.json_props
- return [jsonify(element) for element in obj]
-
-
-@jsonify.when('''(
- isinstance(obj, sqlalchemy.engine.ResultProxy)
- )''')
-def jsonify_saresult(obj):
- '''Transform SQLAlchemy ResultProxy into json.
-
- The one special thing is that we bind the special json_props into each
- descendent. This allows us to specify a json_props on the toplevel
- query result and it will pass to all of its children.
-
- :arg obj: sqlalchemy ResultProxy to jsonify
- :Returns: list of jsonified elements
- '''
- if 'json_props' in obj.__dict__:
- for element in obj:
- element.json_props = obj.json_props
- return [list(row) for row in obj]
-
-
-@jsonify.when('''(isinstance(obj, set))''')
-def jsonify_set(obj):
- '''Transform a set into a list.
-
- simplejson doesn't handle sets natively so transform a set into a list.
-
- :arg obj: set to jsonify
- :Returns: list representation of the set
- '''
- return list(obj)
diff --git a/fedora/tg/templates/__init__.py b/fedora/tg/templates/__init__.py
deleted file mode 100644
index b0c67307..00000000
--- a/fedora/tg/templates/__init__.py
+++ /dev/null
@@ -1,8 +0,0 @@
-'''
-Templates to make adding templates for some common services easier.
-
-
-.. moduleauthor:: Toshio Kuratomi
-
-.. versionadded:: 0.3.10
-'''
diff --git a/fedora/tg/templates/genshi/__init__.py b/fedora/tg/templates/genshi/__init__.py
deleted file mode 100644
index be88e913..00000000
--- a/fedora/tg/templates/genshi/__init__.py
+++ /dev/null
@@ -1,83 +0,0 @@
-'''
-Genshi version of templates to make adding certain Fedora widgets easier.
-
---------------------------------------------
-:mod:`fedora.tg.templates.genshi.login.html`
---------------------------------------------
-.. module:: fedora.tg.templates.genshi.login.html
- :synopsis: Templates related to logging in and out.
-.. moduleauthor:: Toshio Kuratomi
-.. versionadded:: 0.3.10
-
-
-Include this using::
-
-
-.. function:: loginform([message])
-
-: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)
-
-A match template for the main login form. This is a :term:`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.
-
-Typical usage would be::
-
- ${message}
-
-.. function:: logintoolitem(@href=URL)
-
-:@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.
-
-A match template to add an entry to a toolbar. The entry will contain the
-user's 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 :term:`CSRF` token, or a
-login button if the user doesn't have a session cookie.
-
-Typical usage looks like this::
-
-
-
-
-
-------------------------------------------------
-:mod:`fedora.tg.templates.genshi.jsglobals.html`
-------------------------------------------------
-.. module:: fedora.tg.templates.genshi.jsglobals.html
- :synopsis: Templates to get information into javascript
-.. moduleauthor:: Toshio Kuratomi
-.. versionadded:: 0.3.10
-
-
-Include this using::
-
-
-.. function:: jsglobals()
-
-A match template to add global variables to a page. Typically, you'd include
-this in your :file:`master.html` 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:
-
- :fedora.baseurl: URL fragment to prepend to any calls to the application.
- In a :term:`TurboGears` application, this is the scheme, host, and
- server.webpath. Example: https://admin.fedoraproject.org/pkgdb/.
- This may be a relative link.
- :fedora.identity.anonymous: If ``true``, there will be no other variables
- in the `fedora.identity` namespace. If ``false``, these variables are
- defined:
- :fedora.identity.userid: Numeric, unique identifier for the user
- :fedora.identity.username: Publically visible unique identifier for the
- user
- :fedora.identity.display_name: Common human name for the user
- :fedora.identity.token: csrf token for this user's session to be added to
- urls that query the server.
-
-Typical usage would be::
-
-
-'''
diff --git a/fedora/tg/templates/genshi/jsglobals.html b/fedora/tg/templates/genshi/jsglobals.html
deleted file mode 100644
index 68042856..00000000
--- a/fedora/tg/templates/genshi/jsglobals.html
+++ /dev/null
@@ -1,40 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/fedora/tg/templates/genshi/login.html b/fedora/tg/templates/genshi/login.html
deleted file mode 100644
index ee0f73ae..00000000
--- a/fedora/tg/templates/genshi/login.html
+++ /dev/null
@@ -1,105 +0,0 @@
-
-
-
-
-
-
-
- """
- params = ["widget_id", "bugs"]
-
- def __init__(self, email, widget_id=None):
- self.widget_id = widget_id
- bugzilla = Bugzilla(url='https://bugzilla.redhat.com/xmlrpc.cgi')
- # pylint: disable-msg=E1101
- # :E1101: Bugzilla class monkey patches itself with methods like
- # query.
- self.bugs = bugzilla.query({
- 'product': 'Fedora',
- 'email1': email,
- 'emailassigned_to1': True
- })[:5]
- # pylint: enable-msg=E1101
-
- def __json__(self):
- return {'id': self.widget_id, 'bugs': self.bugs}
diff --git a/fedora/tg2/__init__.py b/fedora/tg2/__init__.py
deleted file mode 100644
index f00fa0cf..00000000
--- a/fedora/tg2/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-'''
-Functions and classes to help build a Fedora Service.
-'''
-
-__all__ = ('templates', 'utils')
diff --git a/fedora/tg2/templates/__init__.py b/fedora/tg2/templates/__init__.py
deleted file mode 100644
index 6b190310..00000000
--- a/fedora/tg2/templates/__init__.py
+++ /dev/null
@@ -1,8 +0,0 @@
-'''
-Templates to make adding templates for some common services easier.
-
-
-.. moduleauthor:: Toshio Kuratomi
-
-.. versionadded:: 0.3.25
-'''
diff --git a/fedora/tg2/templates/genshi/__init__.py b/fedora/tg2/templates/genshi/__init__.py
deleted file mode 100644
index 5bc6b0f5..00000000
--- a/fedora/tg2/templates/genshi/__init__.py
+++ /dev/null
@@ -1,83 +0,0 @@
-'''
-Genshi version of templates to make adding certain Fedora widgets easier.
-
----------------------------------------------
-:mod:`fedora.tg2.templates.genshi.login.html`
----------------------------------------------
-.. module:: fedora.tg2.templates.genshi.login.html
- :synopsis: Templates related to logging in and out.
-.. moduleauthor:: Toshio Kuratomi
-.. versionadded:: 0.3.26
-
-
-Include this using::
-
-
-.. function:: loginform([message])
-
-: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)
-
-A match template for the main login form. This is a :term:`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.
-
-Typical usage would be::
-
- ${message}
-
-.. function:: logintoolitem(@href=URL)
-
-:@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.
-
-A match template to add an entry to a toolbar. The entry will contain the
-user's 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 :term:`CSRF` token, or a
-login button if the user doesn't have a session cookie.
-
-Typical usage looks like this::
-
-
-
-
-
--------------------------------------------------
-:mod:`fedora.tg2.templates.genshi.jsglobals.html`
--------------------------------------------------
-.. module:: fedora.tg2.templates.genshi.jsglobals.html
- :synopsis: Templates to get information into javascript
-.. moduleauthor:: Toshio Kuratomi
-.. versionadded:: 0.3.26
-
-
-Include this using::
-
-
-.. function:: jsglobals()
-
-A match template to add global variables to a page. Typically, you'd include
-this in your :file:`master.html` 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:
-
- :fedora.baseurl: URL fragment to prepend to any calls to the application.
- In a :term:`TurboGears` application, this is the scheme, host, and
- server.webpath. Example: https://admin.fedoraproject.org/pkgdb/.
- This may be a relative link.
- :fedora.identity.anonymous: If ``true``, there will be no other variables
- in the `fedora.identity` namespace. If ``false``, these variables are
- defined:
- :fedora.identity.userid: Numeric, unique identifier for the user
- :fedora.identity.username: Publically visible unique identifier for the
- user
- :fedora.identity.display_name: Common human name for the user
- :fedora.identity.token: csrf token for this user's session to be added to
- urls that query the server.
-
-Typical usage would be::
-
-
-'''
diff --git a/fedora/tg2/templates/genshi/jsglobals.html b/fedora/tg2/templates/genshi/jsglobals.html
deleted file mode 100644
index bdebaddb..00000000
--- a/fedora/tg2/templates/genshi/jsglobals.html
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
-
-
-
diff --git a/fedora/tg2/templates/genshi/login.html b/fedora/tg2/templates/genshi/login.html
deleted file mode 100644
index 33689b5d..00000000
--- a/fedora/tg2/templates/genshi/login.html
+++ /dev/null
@@ -1,83 +0,0 @@
-
-
-
-
-
-
-
diff --git a/fedora/tg2/templates/mako/__init__.py b/fedora/tg2/templates/mako/__init__.py
deleted file mode 100644
index 759ca5da..00000000
--- a/fedora/tg2/templates/mako/__init__.py
+++ /dev/null
@@ -1,90 +0,0 @@
-'''
-Mako version of templates to make adding certain Fedora widgets easier.
-
-------------------------------------------
-:mod:`fedora.tg2.templates.mako.login.mak`
-------------------------------------------
-.. module:: fedora.tg2.templates.mako.login.mak
- :synopsis: Templates related to logging in and out.
-.. moduleauthor:: Toshio Kuratomi
-.. versionadded:: 0.3.25
-
-
-Include this using::
-
- <%namespace name="fedora" file="${context['fedora_template']('login.mak')}" />
-
-.. function:: loginform(message='')
-
-: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)
-
-A function for generating the main login form. This is a :term:`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.
-
-Typical usage, given the above import of the :file:`login.mak` template would
-be::
-
- ${fedora.loginform()}
-
-
-.. function:: logintoolitem(href=None)
-
-: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.
-
-This function creates an entry into a toolbar for logging into a web app.
-The entry will contain the user's 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 :term:`CSRF` token, or a login button if the user doesn't have a session
-cookie.
-
-Typical usage looks like this::
-
-
-
-----------------------------------------------
-:mod:`fedora.tg2.templates.mako.jsglobals.mak`
-----------------------------------------------
-.. module:: fedora.tg2.templates.mako.jsglobals.mak
- :synopsis: Templates to get information into javascript
-.. moduleauthor:: Toshio Kuratomi
-.. versionadded:: 0.3.25
-
-
-Include this using::
-
- <%namespace name="jsglobals" file="${context['fedora_template']('jsglobals.mak')}" />
-
-.. function:: jsglobals()
-
-A function to add global variables to a page. Typically, you'd include
-this in your :file:`master.mak` template and let the javascript variables
-defined there propogate to every page on your site (since they all should
-inherit from :file:`master.mak`). This adds the following variables in the
-fedora namespace for other scripts to access:
-
- :fedora.baseurl: URL fragment to prepend to any calls to the application.
- In a :term:`TurboGears` application, this is the scheme, host, and
- server.webpath. Example: https://admin.fedoraproject.org/pkgdb/.
- This may be a relative link.
- :fedora.identity.anonymous: If ``true``, there will be no other variables
- in the `fedora.identity` namespace. If ``false``, these variables are
- defined:
- :fedora.identity.userid: Numeric, unique identifier for the user
- :fedora.identity.username: Publically visible unique identifier for the
- user
- :fedora.identity.display_name: Common human name for the user
- :fedora.identity.token: csrf token for this user's session to be added to
- urls that query the server.
-
-Typical usage would be::
-
- ${jsglobals.jsglobals()}
-'''
diff --git a/fedora/tg2/templates/mako/jsglobals.mak b/fedora/tg2/templates/mako/jsglobals.mak
deleted file mode 100644
index 7fdf44a3..00000000
--- a/fedora/tg2/templates/mako/jsglobals.mak
+++ /dev/null
@@ -1,22 +0,0 @@
-<%def name="jsglobals()">
-
- % if request.identity:
-
- % endif
-%def>
diff --git a/fedora/tg2/templates/mako/login.mak b/fedora/tg2/templates/mako/login.mak
deleted file mode 100644
index 40ad66c8..00000000
--- a/fedora/tg2/templates/mako/login.mak
+++ /dev/null
@@ -1,83 +0,0 @@
-<%def name="loginform(message='')">
-
-
${_('Log In')}
- % if message:
-
${message}
- % endif
- % if (request.identity and '_csrf_token' in request.identity) or request.environ.get('CSRF_AUTH_SESSION_ID'):
-
- % else:
-
- % endif
-
-
-% elif not request.identity and not request.environ.get('CSRF_AUTH_SESSION_ID'):
- ## If not logged in and no sign that we just lack a csrf token, offer login
-
- ${_('You are not logged in')}
-
-
-% elif not request.identity:
- ## Only CSRF_token missing
-
- ${_('CSRF protected')}
- ## Just go back to the present page using tg.url() to append the _csrf_token
-
-
-% endif
-% if request.identity or request.environ.get('CSRF_AUTH_SESSION_ID'):
-
-
-
-% endif
-%def>
diff --git a/fedora/tg2/utils.py b/fedora/tg2/utils.py
deleted file mode 100644
index d89a2dc6..00000000
--- a/fedora/tg2/utils.py
+++ /dev/null
@@ -1,251 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2009-2011 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
-#
-'''
-Miscellaneous functions of use on a TurboGears 2 Server
-
-.. versionadded:: 0.3.17
-.. versionchanged:: 0.3.25
- Moved from fedora.tg.tg2utils
- Modified fedora_template to allow dotted lookup
-
-.. moduleauthor:: Toshio Kuratomi
-'''
-
-from copy import copy
-from hashlib import sha1
-import logging
-import os
-
-from munch import Munch
-from kitchen.text.converters import to_unicode
-import pkg_resources
-from repoze.what.plugins.pylonshq import booleanize_predicates
-import tg
-from tg import config
-
-from fedora.wsgi.faswho import make_faswho_middleware
-from fedora.urlutils import update_qs
-
-tg_url = tg.url
-
-### FIXME: Need jsonify_validation_errors, json_or_redirect, request_format
-# To have all of the functions that exist for TG1
-
-
-def url(*args, **kwargs):
- '''Compute URL
-
- This is a replacement for :func:`tg.controllers.url` (aka :func:`tg.url`
- in the template). In addition to the functionality that :func:`tg.url`
- provides, it adds a token to prevent :term:`CSRF` attacks.
-
- The arguments and return value are the same as for :func:`tg.url`
-
- The original :func:`tg.url` is accessible as
- :func:`fedora.tg2.utils.tg_url`
-
- .. versionadded:: 0.3.17
- Added to enable :ref:`CSRF-Protection` for TG2
- '''
- new_url = tg_url(*args, **kwargs)
-
- # Set the current _csrf_token on the url. It will overwrite any current
- # _csrf_token
- csrf_token = None
- identity = tg.request.environ.get('repoze.who.identity')
- if identity:
- csrf_token = identity.get('_csrf_token', None)
- else:
- session_id = tg.request.environ.get('CSRF_AUTH_SESSION_ID')
- if session_id:
- csrf_token = sha1(session_id).hexdigest()
- if csrf_token:
- new_url = update_qs(new_url, {'_csrf_token': csrf_token},
- overwrite=True)
- return new_url
-
-
-def fedora_template(template, template_type='mako', dotted_lookup=True):
- '''Function to return the path to a template.
-
- :arg template: filename of the template itself. Ex: login.mak
- :kwarg template_type: template language we need the template written in
- Defaults to 'mako'
- :kwarg dotted_lookup: if True, return the resource as a dotted module path
- If False, return the resource as a filename. Default: True
- :returns: filesystem path or dotted module path equivalent to the template
-
- .. versionchanged:: 0.3.25
- Added dotted_lookup
- Made this work with tg2
- '''
- # :E1101: pkg_resources does have resource_filename
- # pylint: disable-msg=E1101
- resource = pkg_resources.resource_filename(
- 'fedora', os.path.join('tg2',
- 'templates', template_type, template)
- )
-
- # pylint: enable-msg=E1101
-
- if dotted_lookup:
- # Find the location of the base resource (fedora)
- base = pkg_resources.resource_filename('fedora', '')
- if resource.startswith(base):
- # subtract that from the resource
- resource = resource[len(base):]
- if resource[0] == '/':
- resource = 'fedora%s' % resource
- else:
- resource = 'fedora/%s' % resource
- # Strip the filename extension
- resource = os.path.splitext(resource)[0]
-
- # Turn '/' into '.'
- resource = to_unicode(resource)
- resource = resource.translate({ord(u'/'): u'.'})
-
- return resource
-
-
-def add_fas_auth_middleware(self, app, *args):
- ''' Add our FAS authentication middleware.
-
- This is a convenience method that sets up the FAS authentication
- middleware. It needs to be used in :file:`app/config/app_cfg.py` like
- this:
-
- .. code-block:: diff
-
- from myapp import model
- from myapp.lib import app_globals, helpers.
-
- -base_config = AppConfig()
- +from fedora.tg2.utils import add_fas_auth_middleware
- +
- +class MyAppConfig(AppConfig):
- + add_auth_middleware = add_fas_auth_middleware
- +
- +base_config = MyAppConfig()
- +
- base_config.renderers = []
-
- base_config.package = myapp
-
- The configuration of the faswho middleware is done via attributes on
- MyAppConfig. For instance, to change the base url for the FAS server to
- be authenticated against, set the connection to insecure for testing, and
- the url of the login form, do this::
-
- from munch import Munch
- class MyAppConfig(AppConfig):
- fas_auth = Munch(
- fas_url='https://fakefas.fedoraproject.org/accounts',
- insecure=True, login_form_url='/alternate/login')
- add_auth_middleware = add_fas_auth_middleware
-
- The complete set of values that can be set in :attr:`fas_auth` is the same
- as the parameters that can be passed to
- :func:`fedora.wsgi.faswho.faswhoplugin.make_faswho_middleware`
- '''
- # Set up csrf protection
- enable_csrf()
-
- booleanize_predicates()
-
- if not hasattr(self, 'fas_auth'):
- self.fas_auth = Munch()
-
- # Configuring auth logging:
- if 'log_stream' not in self.fas_auth:
- self.fas_auth['log_stream'] = logging.getLogger('auth')
-
- # Pull in some of the default auth arguments
- auth_args = copy(self.fas_auth)
-
- app = make_faswho_middleware(app, **auth_args)
- return app
-
-
-def enable_csrf():
- '''A startup function to setup :ref:`CSRF-Protection`.
-
- This should be run at application startup. It does three things:
-
- 1) overrides the :func:`tg.url` function with
- :func:`fedora.tg2.utils.url` so that :term:`CSRF` tokens are
- appended to URLs.
- 2) tells the TG2 app to ignore `_csrf_token`. This lets the app know not
- to throw an error if the variable is passed through to the app.
- 3) adds :func:`fedora.tg.fedora_template` function to the template
- variables. This lets us xi:include templates from python-fedora in
- our templates.
-
- Presently, this is run when the faswho middleware is invoked. See the
- documentation for :func:`fedora.tg.tgutils.add_fas_auth_middleware` for
- how to enable that.
-
- .. note::
-
- The following code is broken at least as late as
- :term:`TurboGears`-2.0.3. We need to have something similar in order
- to use CSRF protection with applications that do not always want to
- rely on FAS to authenticate.
-
- .. seealso:: http://trac.turbogears.org/ticket/2432
-
- To run this at application startup, add
- code like the following to :file:`MYAPP/config/app_config.py`::
-
- from fedora.tg2.utils import enable_csrf
- base_config.call_on_startup = [enable_csrf]
-
- If we can get the :ref:`CSRF-Protection` into upstream :term:`TurboGears`,
- we might be able to remove this in the future.
-
- .. versionadded:: 0.3.17
- Added to enable :ref:`CSRF-Protection`
- '''
- # Override the tg.url function with our own
- tg.url = url
- tg.controllers.url = url
- try:
- # TG-2.1+
- tg.controllers.util.url = url
- except AttributeError:
- # TG-2.0.x
- pass
-
- # Ignore the _csrf_token parameter
- ignore = config.get('ignore_parameters', [])
- if '_csrf_token' not in ignore:
- ignore.append('_csrf_token')
- config['ignore_parameters'] = ignore
-
- # Add a function to the template tg stdvars that looks up a template.
- var_provider = config.get('variable_provider', None)
- if var_provider:
- config['variable_provider'] = lambda: \
- var_provider().update({'fedora_template': fedora_template})
- else:
- config['variable_provider'] = lambda: {'fedora_template':
- fedora_template}
-
-__all__ = ('add_fas_auth_middleware', 'enable_csrf', 'fedora_template',
- 'tg_url', 'url')
diff --git a/fedora/wsgi/__init__.py b/fedora/wsgi/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/fedora/wsgi/csrf.py b/fedora/wsgi/csrf.py
deleted file mode 100644
index e9e9011c..00000000
--- a/fedora/wsgi/csrf.py
+++ /dev/null
@@ -1,307 +0,0 @@
-#
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2008-2011 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
-#
-'''
-Cross-site Request Forgery Protection.
-
-http://en.wikipedia.org/wiki/Cross-site_request_forgery
-
-
-.. moduleauthor:: John (J5) Palmieri
-.. moduleauthor:: Luke Macken
-
-.. versionadded:: 0.3.17
-'''
-
-from hashlib import sha1
-import logging
-
-from munch import Munch
-from kitchen.text.converters import to_bytes
-from webob import Request
-try:
- # webob > 1.0
- from webob.headers import ResponseHeaders
-except ImportError:
- # webob < 1.0
- from webob.headerdict import HeaderDict as ResponseHeaders
-from paste.httpexceptions import HTTPFound
-from paste.response import replace_header
-from repoze.who.interfaces import IMetadataProvider
-from zope.interface import implements
-
-from fedora.urlutils import update_qs
-
-log = logging.getLogger(__name__)
-
-
-class CSRFProtectionMiddleware(object):
- '''
- CSRF Protection WSGI Middleware.
-
- A layer of WSGI middleware that is responsible for making sure
- authenticated requests originated from the user inside of the app's domain
- and not a malicious website.
-
- This middleware works with the :mod:`repoze.who` middleware, and requires
- that it is placed below :mod:`repoze.who` in the WSGI stack,
- since it relies upon ``repoze.who.identity`` to exist in the environ before
- it is called.
-
- To utilize this middleware, you can just add it to your WSGI stack below
- the :mod:`repoze.who` middleware. Here is an example of utilizing the
- `CSRFProtectionMiddleware` within a TurboGears2 application.
- In your ``project/config/middleware.py``, you would wrap your main
- application with the `CSRFProtectionMiddleware`, like so:
-
- .. code-block:: python
-
- from fedora.wsgi.csrf import CSRFProtectionMiddleware
- def make_app(global_conf, full_stack=True, **app_conf):
- app = make_base_app(global_conf, wrap_app=CSRFProtectionMiddleware,
- full_stack=full_stack, **app_conf)
-
- You then need to add the CSRF token to every url that you need to be
- authenticated for. When used with TurboGears2, an overridden version of
- :func:`tg.url` is provided. You can use it directly by calling::
-
- from fedora.tg2.utils import url
- [...]
- url = url('/authentication_needed')
-
- An easier and more portable way to use that is from within TG2 to set this
- up is to use :func:`fedora.tg2.utils.enable_csrf` when you setup your
- application. This function will monkeypatch TurboGears2's :func:`tg.url`
- so that it adds a csrf token to urls. This way, you can keep the same
- code in your templates and controller methods whether or not you configure
- the CSRF middleware to provide you with protection via
- :func:`~fedora.tg2.utils.enable_csrf`.
- '''
-
- def __init__(self, application, csrf_token_id='_csrf_token',
- clear_env='repoze.who.identity repoze.what.credentials',
- token_env='CSRF_TOKEN', auth_state='CSRF_AUTH_STATE'):
- '''
- Initialize the CSRF Protection WSGI Middleware.
-
- :csrf_token_id: The name of the CSRF token variable
- :clear_env: Variables to clear out of the `environ` on invalid token
- :token_env: The name of the token variable in the environ
- :auth_state: The environ key that will be set when we are logging in
- '''
- log.info('Creating CSRFProtectionMiddleware')
- self.application = application
- self.csrf_token_id = csrf_token_id
- self.clear_env = clear_env.split()
- self.token_env = token_env
- self.auth_state = auth_state
-
- def _clean_environ(self, environ):
- ''' Delete the ``keys`` from the supplied ``environ`` '''
- log.debug('clean_environ(%s)' % to_bytes(self.clear_env))
- for key in self.clear_env:
- if key in environ:
- log.debug('Deleting %(key)s from environ' %
- {'key': to_bytes(key)})
- del(environ[key])
-
- def __call__(self, environ, start_response):
- '''
- This method is called for each request. It looks for a user-supplied
- CSRF token in the GET/POST parameters, and compares it to the token
- attached to ``environ['repoze.who.identity']['_csrf_token']``. If it
- does not match, or if a token is not provided, it will remove the
- user from the ``environ``, based on the ``clear_env`` setting.
- '''
- request = Request(environ)
- log.debug('CSRFProtectionMiddleware(%(r_path)s)' %
- {'r_path': to_bytes(request.path)})
-
- token = environ.get('repoze.who.identity', {}).get(self.csrf_token_id)
- csrf_token = environ.get(self.token_env)
-
- if token and csrf_token and token == csrf_token:
- log.debug('User supplied CSRF token matches environ!')
- else:
- if not environ.get(self.auth_state):
- log.debug('Clearing identity')
- self._clean_environ(environ)
- if 'repoze.who.identity' not in environ:
- environ['repoze.who.identity'] = Munch()
- if 'repoze.who.logins' not in environ:
- # For compatibility with friendlyform
- environ['repoze.who.logins'] = 0
- if csrf_token:
- log.warning('Invalid CSRF token. User supplied'
- ' (%(u_token)s) does not match what\'s in our'
- ' environ (%(e_token)s)' %
- {'u_token': to_bytes(csrf_token),
- 'e_token': to_bytes(token)})
-
- response = request.get_response(self.application)
-
- if environ.get(self.auth_state):
- log.debug('CSRF_AUTH_STATE; rewriting headers')
- token = environ.get('repoze.who.identity', {})\
- .get(self.csrf_token_id)
-
- loc = update_qs(
- response.location, {self.csrf_token_id: str(token)})
- response.location = loc
- log.debug('response.location = %(r_loc)s' %
- {'r_loc': to_bytes(response.location)})
- environ[self.auth_state] = None
-
- return response(environ, start_response)
-
-
-class CSRFMetadataProvider(object):
- '''
- Repoze.who CSRF Metadata Provider Plugin.
-
- This metadata provider is called with an authenticated users identity
- automatically by repoze.who. It will then take the SHA1 hash of the
- users session cookie, and set it as the CSRF token in
- ``environ['repoze.who.identity']['_csrf_token']``.
-
- This plugin will also set ``CSRF_AUTH_STATE`` in the environ if the user
- has just authenticated during this request.
-
- To enable this plugin in a TurboGears2 application, you can
- add the following to your ``project/config/app_cfg.py``
-
- .. code-block:: python
-
- from fedora.wsgi.csrf import CSRFMetadataProvider
- base_config.sa_auth.mdproviders = [('csrfmd', CSRFMetadataProvider())]
-
- Note: If you use the faswho plugin, this is turned on automatically.
- '''
- implements(IMetadataProvider)
-
- def __init__(self, csrf_token_id='_csrf_token', session_cookie='tg-visit',
- clear_env='repoze.who.identity repoze.what.credentials',
- login_handler='/post_login', token_env='CSRF_TOKEN',
- auth_session_id='CSRF_AUTH_SESSION_ID',
- auth_state='CSRF_AUTH_STATE'):
- '''
- Create the CSRF Metadata Provider Plugin.
-
- :kwarg csrf_token_id: The name of the CSRF token variable. The
- identity will contain an entry with this as key and the
- computed csrf_token as the value.
- :kwarg session_cookie: The name of the session cookie
- :kwarg login_handler: The path to the login handler, used to determine
- if the user logged in during this request
- :kwarg token_env: The name of the token variable in the environ.
- The environ will contain the token from the request
- :kwarg auth_session_id: The environ key containing an optional
- session id
- :kwarg auth_state: The environ key that indicates when we are
- logging in
- '''
- self.csrf_token_id = csrf_token_id
- self.session_cookie = session_cookie
- self.clear_env = clear_env
- self.login_handler = login_handler
- self.token_env = token_env
- self.auth_session_id = auth_session_id
- self.auth_state = auth_state
-
- def strip_script(self, environ, path):
- # Strips the script portion of a url path so the middleware works even
- # when mounted under a path other than root
- if path.startswith('/') and 'SCRIPT_NAME' in environ:
- prefix = environ.get('SCRIPT_NAME')
- if prefix.endswith('/'):
- prefix = prefix[:-1]
-
- if path.startswith(prefix):
- path = path[len(prefix):]
-
- return path
-
- def add_metadata(self, environ, identity):
- request = Request(environ)
- log.debug('CSRFMetadataProvider.add_metadata(%(r_path)s)'
- % {'r_path': to_bytes(request.path)})
-
- session_id = environ.get(self.auth_session_id)
- if not session_id:
- session_id = request.cookies.get(self.session_cookie)
- log.debug('session_id = %(s_id)r' % {'s_id':
- to_bytes(session_id)})
-
- if session_id and session_id != 'Set-Cookie:':
- environ[self.auth_session_id] = session_id
- token = sha1(session_id).hexdigest()
- identity.update({self.csrf_token_id: token})
- log.debug('Identity updated with CSRF token')
- path = self.strip_script(environ, request.path)
- if path == self.login_handler:
- log.debug('Setting CSRF_AUTH_STATE')
- environ[self.auth_state] = True
- environ[self.token_env] = token
- else:
- environ[self.token_env] = self.extract_csrf_token(request)
-
- app = environ.get('repoze.who.application')
- if app:
- # This occurs during login in some application configurations
- if isinstance(app, HTTPFound) and environ.get(self.auth_state):
- log.debug('Got HTTPFound(302) from'
- ' repoze.who.application')
- # What possessed people to make this a string or
- # a function?
- location = app.location
- if hasattr(location, '__call__'):
- location = location()
- loc = update_qs(location, {self.csrf_token_id:
- str(token)})
-
- headers = app.headers.items()
- replace_header(headers, 'location', loc)
- app.headers = ResponseHeaders(headers)
- log.debug('Altered headers: %(headers)s' % {
- 'headers': to_bytes(app.headers)})
- else:
- log.warning('Invalid session cookie %(s_id)r, not setting CSRF'
- ' token!' % {'s_id': to_bytes(session_id)})
-
- def extract_csrf_token(self, request):
- '''Extract and remove the CSRF token from a given
- :class:`webob.Request`
- '''
- csrf_token = None
-
- if self.csrf_token_id in request.GET:
- log.debug("%(token)s in GET" % {'token':
- to_bytes(self.csrf_token_id)})
- csrf_token = request.GET[self.csrf_token_id]
- del(request.GET[self.csrf_token_id])
- request.query_string = '&'.join(['%s=%s' % (k, v) for k, v in
- request.GET.items()])
-
- if self.csrf_token_id in request.POST:
- log.debug("%(token)s in POST" % {'token':
- to_bytes(self.csrf_token_id)})
- csrf_token = request.POST[self.csrf_token_id]
- del(request.POST[self.csrf_token_id])
-
- return csrf_token
diff --git a/fedora/wsgi/faswho/__init__.py b/fedora/wsgi/faswho/__init__.py
deleted file mode 100644
index 4f8e6877..00000000
--- a/fedora/wsgi/faswho/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from fedora.wsgi.faswho.faswhoplugin import (
- FASWhoPlugin,
- make_faswho_middleware
-)
-
-__all__ = (FASWhoPlugin, make_faswho_middleware)
diff --git a/fedora/wsgi/faswho/faswhoplugin.py b/fedora/wsgi/faswho/faswhoplugin.py
deleted file mode 100644
index 6ddb7b6d..00000000
--- a/fedora/wsgi/faswho/faswhoplugin.py
+++ /dev/null
@@ -1,421 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2008-2011 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
-#
-'''
-repoze.who plugin to authenticate against hte Fedora Account System
-
-.. moduleauthor:: John (J5) Palmieri
-.. moduleauthor:: Luke Macken
-.. moduleauthor:: Toshio Kuratomi
-
-.. versionadded:: 0.3.17
-.. versionchanged:: 0.3.26
- - Added secure and httponly as optional attributes to the session cookie
- - Removed too-aggressive caching (wouldn't detect logout from another app)
- - Added ability to authenticate and request a page in one request
-'''
-import os
-import sys
-import logging
-
-import pkg_resources
-
-from beaker.cache import Cache
-from munch import Munch
-from kitchen.text.converters import to_bytes, exception_to_bytes
-from paste.httpexceptions import HTTPFound
-from repoze.who.middleware import PluggableAuthenticationMiddleware
-from repoze.who.classifiers import default_request_classifier
-from repoze.who.classifiers import default_challenge_decider
-from repoze.who.interfaces import IChallenger, IIdentifier
-from repoze.who.plugins.basicauth import BasicAuthPlugin
-from repoze.who.plugins.friendlyform import FriendlyFormPlugin
-from paste.request import parse_dict_querystring, parse_formvars
-from six.moves.urllib.parse import quote_plus
-import webob
-
-from fedora.client import AuthError
-from fedora.client.fasproxy import FasProxyClient
-from fedora.wsgi.csrf import CSRFMetadataProvider, CSRFProtectionMiddleware
-
-log = logging.getLogger(__name__)
-
-FAS_URL = 'https://admin.fedoraproject.org/accounts/'
-FAS_CACHE_TIMEOUT = 900 # 15 minutes (FAS visits timeout after 20)
-
-fas_cache = Cache('fas_repozewho_cache', type='memory')
-
-
-def fas_request_classifier(environ):
- classifier = default_request_classifier(environ)
- if classifier == 'browser':
- request = webob.Request(environ)
- if not request.accept.best_match(
- ['application/xhtml+xml', 'text/html']):
- classifier = 'app'
- return classifier
-
-
-def make_faswho_middleware(
- app, log_stream=None,
- login_handler='/login_handler',
- login_form_url='/login',
- logout_handler='/logout_handler',
- post_login_url='/post_login', post_logout_url=None, fas_url=FAS_URL,
- insecure=False, ssl_cookie=True, httponly=True):
- '''
- :arg app: WSGI app that is being wrapped
- :kwarg log_stream: :class:`logging.Logger` to log auth messages
- :kwarg login_handler: URL where the login form is submitted
- :kwarg login_form_url: URL where the login form is displayed
- :kwarg logout_handler: URL where the logout form is submitted
- :kwarg post_login_url: URL to redirect the user to after login
- :kwarg post_logout_url: URL to redirect the user to after logout
- :kwarg fas_url: Base URL to the FAS server
- :kwarg insecure: Allow connecting to a fas server without checking the
- server's SSL certificate. Opens you up to MITM attacks but can be
- useful when testing. *Do not enable this in production*
- :kwarg ssl_cookie: If :data:`True` (default), tell the browser to only
- send the session cookie back over https.
- :kwarg httponly: If :data:`True` (default), tell the browser that the
- session cookie should only be read for sending to a server, not for
- access by JavaScript or other clientside technology. This prevents
- using the session cookie to pass information to JavaScript clients but
- also prevents XSS attacks from stealing the session cookie
- information.
- '''
-
- # Because of the way we override values (via a dict in AppConfig), we
- # need to make this a keyword arg and then check it here to make it act
- # like a positional arg.
- if not log_stream:
- raise TypeError(
- 'log_stream must be set when calling make_fasauth_middleware()')
-
- faswho = FASWhoPlugin(fas_url, insecure=insecure, ssl_cookie=ssl_cookie,
- httponly=httponly)
- csrf_mdprovider = CSRFMetadataProvider()
-
- form = FriendlyFormPlugin(login_form_url,
- login_handler,
- post_login_url,
- logout_handler,
- post_logout_url,
- rememberer_name='fasident',
- charset='utf-8')
-
- form.classifications = {IIdentifier: ['browser'],
- IChallenger: ['browser']} # only for browser
-
- basicauth = BasicAuthPlugin('repoze.who')
-
- identifiers = [
- ('form', form),
- ('fasident', faswho),
- ('basicauth', basicauth)
- ]
- authenticators = [('fasauth', faswho)]
- challengers = [('form', form), ('basicauth', basicauth)]
- mdproviders = [('fasmd', faswho), ('csrfmd', csrf_mdprovider)]
-
- if os.environ.get('FAS_WHO_LOG'):
- log_stream = sys.stdout
-
- app = CSRFProtectionMiddleware(app)
- app = PluggableAuthenticationMiddleware(
- app,
- identifiers,
- authenticators,
- challengers,
- mdproviders,
- fas_request_classifier,
- default_challenge_decider,
- log_stream=log_stream,
- )
-
- return app
-
-
-class FASWhoPlugin(object):
-
- def __init__(self, url, insecure=False, session_cookie='tg-visit',
- ssl_cookie=True, httponly=True):
- self.url = url
- self.insecure = insecure
- self.fas = FasProxyClient(url, insecure=insecure)
- self.session_cookie = session_cookie
- self.ssl_cookie = ssl_cookie
- self.httponly = httponly
- self._session_cache = {}
- self._metadata_plugins = []
-
- for entry in pkg_resources.iter_entry_points(
- 'fas.repoze.who.metadata_plugins'):
- self._metadata_plugins.append(entry.load())
-
- def _retrieve_user_info(self, environ, auth_params=None):
- ''' Retrieve information from fas and cache the results.
-
- We need to retrieve the user fresh every time because we need to
- know that the password hasn't changed or the session_id hasn't
- been invalidated by the user logging out.
- '''
- if not auth_params:
- return None
-
- user_data = self.fas.get_user_info(auth_params)
-
- if not user_data:
- self.forget(environ, None)
- return None
- if isinstance(user_data, tuple):
- user_data = list(user_data)
-
- # Set session_id in here so it can be found by other plugins
- user_data[1]['session_id'] = user_data[0]
-
- # we don't define permissions since we don't have any peruser data
- # though other services may wish to add another metadata plugin to do
- # so
- if not 'permissions' in user_data[1]:
- user_data[1]['permissions'] = set()
-
- # we keep the approved_memberships list because there is also an
- # unapproved_membership field. The groups field is for repoze.who
- # group checking and may include other types of groups besides
- # memberships in the future (such as special fedora community groups)
-
- groups = set()
- for g in user_data[1]['approved_memberships']:
- groups.add(g['name'])
-
- user_data[1]['groups'] = groups
- # If we have information on the user, cache it for later
- fas_cache.set_value(user_data[1]['username'], user_data,
- expiretime=FAS_CACHE_TIMEOUT)
- return user_data
-
- def identify(self, environ):
- '''Extract information to identify a user
-
- Retrieve either a username and password or a session_id that can be
- passed on to FAS to authenticate the user.
- '''
- log.info('in identify()')
-
- # friendlyform compat
- if not 'repoze.who.logins' in environ:
- environ['repoze.who.logins'] = 0
-
- req = webob.Request(environ, charset='utf-8')
- cookie = req.cookies.get(self.session_cookie)
-
- # This is compatible with TG1 and it gives us a way to authenticate
- # a user without making two requests
- query = req.GET
- form = Munch(req.POST)
- form.update(query)
- if form.get('login', None) == 'Login' and \
- 'user_name' in form and \
- 'password' in form:
- identity = {
- 'login': form['user_name'],
- 'password': form['password']
- }
- keys = ('login', 'password', 'user_name')
- for k in keys:
- if k in req.GET:
- del(req.GET[k])
- if k in req.POST:
- del(req.POST[k])
- return identity
-
- if cookie is None:
- return None
-
- log.info('Request identify for cookie %(cookie)s' %
- {'cookie': to_bytes(cookie)})
- try:
- user_data = self._retrieve_user_info(
- environ,
- auth_params={'session_id': cookie})
- except Exception as e: # pylint:disable-msg=W0703
- # For any exceptions, returning None means we failed to identify
- log.warning(e)
- return None
-
- if not user_data:
- return None
-
- # Preauthenticated
- identity = {'repoze.who.userid': user_data[1]['username'],
- 'login': user_data[1]['username'],
- 'password': user_data[1]['password']}
- return identity
-
- def remember(self, environ, identity):
- log.info('In remember()')
- result = []
-
- user_data = fas_cache.get_value(identity['login'])
- try:
- session_id = user_data[0]
- except Exception:
- return None
-
- set_cookie = ['%s=%s; Path=/;' % (self.session_cookie, session_id)]
- if self.ssl_cookie:
- set_cookie.append('Secure')
- if self.httponly:
- set_cookie.append('HttpOnly')
- set_cookie = '; '.join(set_cookie)
- result.append(('Set-Cookie', set_cookie))
- return result
-
- def forget(self, environ, identity):
- log.info('In forget()')
- # return a expires Set-Cookie header
-
- user_data = fas_cache.get_value(identity['login'])
- try:
- session_id = user_data[0]
- except Exception:
- return None
-
- log.info('Forgetting login data for cookie %(s_id)s' %
- {'s_id': to_bytes(session_id)})
-
- self.fas.logout(session_id)
-
- result = []
- fas_cache.remove_value(key=identity['login'])
- expired = '%s=\'\'; Path=/; Expires=Sun, 10-May-1971 11:59:00 GMT'\
- % self.session_cookie
- result.append(('Set-Cookie', expired))
- return result
-
- # IAuthenticatorPlugin
- def authenticate(self, environ, identity):
- log.info('In authenticate()')
-
- def set_error(msg):
- log.info(msg)
- err = 1
- environ['FAS_AUTH_ERROR'] = err
- # HTTPForbidden ?
- err_app = HTTPFound(err_goto + '?' +
- 'came_from=' + quote_plus(came_from))
- environ['repoze.who.application'] = err_app
-
- err_goto = '/login'
- default_came_from = '/'
- if 'SCRIPT_NAME' in environ:
- sn = environ['SCRIPT_NAME']
- err_goto = sn + err_goto
- default_came_from = sn + default_came_from
-
- query = parse_dict_querystring(environ)
- form = parse_formvars(environ)
- form.update(query)
- came_from = form.get('came_from', default_came_from)
-
- try:
- auth_params = {'username': identity['login'],
- 'password': identity['password']}
- except KeyError:
- try:
- auth_params = {'session_id': identity['session_id']}
- except:
- # On error we return None which means that auth failed
- set_error('Parameters for authenticating not found')
- return None
-
- try:
- user_data = self._retrieve_user_info(environ, auth_params)
- except AuthError as e:
- set_error('Authentication failed: %s' % exception_to_bytes(e))
- log.warning(e)
- return None
- except Exception as e:
- set_error('Unknown auth failure: %s' % exception_to_bytes(e))
- return None
-
- if user_data:
- try:
- del user_data[1]['password']
- environ['CSRF_AUTH_SESSION_ID'] = user_data[0]
- return user_data[1]['username']
- except ValueError:
- set_error('user information from fas not in expected format!')
- return None
- except Exception:
- pass
- set_error('An unknown error happened when trying to log you in.'
- ' Please try again.')
- return None
-
- def add_metadata(self, environ, identity):
- log.info('In add_metadata')
-
- if identity.get('error'):
- log.info('Error exists in session, no need to set metadata')
- return 'error'
-
- plugin_user_info = {}
- for plugin in self._metadata_plugins:
- plugin(plugin_user_info)
- identity.update(plugin_user_info)
- del plugin_user_info
-
- user = identity.get('repoze.who.userid')
- (session_id, user_info) = fas_cache.get_value(
- key=user,
- expiretime=FAS_CACHE_TIMEOUT)
-
- #### FIXME: Deprecate this line!!!
- # If we make a new version of fas.who middleware, get rid of saving
- # user information directly into identity. Instead, save it into
- # user, as is done below
- identity.update(user_info)
-
- identity['userdata'] = user_info
- identity['user'] = Munch()
- identity['user'].created = user_info['creation']
- identity['user'].display_name = user_info['human_name']
- identity['user'].email_address = user_info['email']
- identity['user'].groups = user_info['groups']
- identity['user'].password = None
- identity['user'].permissions = user_info['permissions']
- identity['user'].user_id = user_info['id']
- identity['user'].user_name = user_info['username']
- identity['groups'] = user_info['groups']
- identity['permissions'] = user_info['permissions']
-
- if 'repoze.what.credentials' not in environ:
- environ['repoze.what.credentials'] = {}
-
- environ['repoze.what.credentials']['groups'] = user_info['groups']
- permissions = user_info['permissions']
- environ['repoze.what.credentials']['permissions'] = permissions
-
- # Adding the userid:
- userid = identity['repoze.who.userid']
- environ['repoze.what.credentials']['repoze.what.userid'] = userid
-
- def __repr__(self):
- return '<%s %s>' % (self.__class__.__name__, id(self))
diff --git a/setup.py b/setup.py
index ee388be9..e5e361c4 100755
--- a/setup.py
+++ b/setup.py
@@ -15,7 +15,7 @@
keywords='Fedora Python Webservices',
url=URL,
packages=find_packages(),
- py_modules=['flask_fas', 'flask_fas_openid'],
+ py_modules=['flask_fas_openid'],
include_package_data=True,
# non-setuptools package. When everything we care about uses
# python-2.5 distutils we can add these:
@@ -31,7 +31,6 @@
'openidc-client',
],
extras_require={
- 'tg': ['TurboGears >= 1.0.4', 'SQLAlchemy', 'decorator'],
'wsgi': ['repoze.who', 'Beaker', 'Paste'],
'flask': [
'Flask', 'Flask_WTF', 'python-openid', 'python-openid-teams',
@@ -39,27 +38,14 @@
],
},
entry_points={
- 'turbogears.identity.provider': (
- 'jsonfas = fedora.tg.identity.jsonfasprovider1:JsonFasIdentityProvider [tg]',
- 'jsonfas2 = fedora.tg.identity.jsonfasprovider2:JsonFasIdentityProvider [tg]',
- 'sqlobjectcsrf = fedora.tg.identity.soprovidercsrf:SqlObjectCsrfIdentityProvider [tg]'),
- 'turbogears.visit.manager': (
- 'jsonfas = fedora.tg.visit.jsonfasvisit1:JsonFasVisitManager [tg]',
- 'jsonfas2 = fedora.tg.visit.jsonfasvisit2:JsonFasVisitManager [tg]'
- ),
},
message_extractors={
'fedora': [
('**.py', 'python', None),
- ('tg2/templates/mako/**.mak', 'mako', None),
- ('tg2/templates/genshi/**.html', 'mako', None),
- ('tg/templates/genshi/**.html', 'genshi', None),
],
},
classifiers=[
'Development Status :: 4 - Beta',
- 'Framework :: TurboGears',
- 'Framework :: Django',
'Intended Audience :: Developers',
'Intended Audience :: System Administrators',
'License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)',
diff --git a/tests/functional/test_openidbaseclient.py b/tests/functional/test_openidbaseclient.py
deleted file mode 100644
index 0ee37c90..00000000
--- a/tests/functional/test_openidbaseclient.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# -*- coding: utf-8 -*-
-""" Test the OpenIdBaseClient. """
-
-
-import os
-import shutil
-import tempfile
-import unittest
-
-from .functional_test_utils import networked
-
-from fedora.client import FedoraServiceError
-from fedora.client.openidbaseclient import OpenIdBaseClient
-
-BASE_URL = 'http://127.0.0.1:5000'
-BASE_URL = 'http://209.132.184.188'
-LOGIN_URL = '{}/login/'.format(BASE_URL)
-
-class OpenIdBaseClientTest(unittest.TestCase):
-
- """Test the OpenId Base Client."""
-
- def setUp(self):
- self.client = OpenIdBaseClient(BASE_URL)
- self.session_file = os.path.expanduser(
- '~/.fedora/baseclient-sessions.sqlite')
- try:
- self.backup_dir = tempfile.mkdtemp(dir='~/.fedora/')
- except OSError:
- self.backup_dir = tempfile.mkdtemp()
-
- self.saved_session_file = os.path.join(
- self.backup_dir, 'baseclient-sessions.sqlite.bak')
- self.clear_cookies()
-
- def tearDown(self):
- self.restore_cookies()
- shutil.rmtree(self.backup_dir)
-
- def clear_cookies(self):
- try:
- shutil.move(self.session_file, self.saved_session_file)
- except IOError as e:
- # No previous sessionfile is fine
- if e.errno != 2:
- raise
- # Sentinel to say that there was no previous session_file
- self.saved_session_file = None
-
- def restore_cookies(self):
- if self.saved_session_file:
- shutil.move(self.saved_session_file, self.session_file)
-
- @networked
- def test_no_openid_session(self):
- """Raise FedoraServiceError for no session on service or openid server."""
- self.assertRaises(FedoraServiceError, self.client.login, 'username', 'password')
-
- @networked
- def test_no_service_session(self):
- """Open a service session when we have an openid session."""
- pass