Remove jsonfas providers and client.py module. These have been merged back to
python-fedora.
This commit is contained in:
parent
9916019c9c
commit
780389dd9c
3 changed files with 0 additions and 544 deletions
|
@ -1,228 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2007 Red Hat, Inc. All rights reserved.
|
||||
#
|
||||
# This copyrighted material is made available to anyone wishing to use, modify,
|
||||
# copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
# General Public License v.2. This program is distributed in the hope that it
|
||||
# will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the
|
||||
# implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU General Public License for more details. You should have
|
||||
# received a copy of the GNU General Public License along with this program;
|
||||
# if not, write to the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
# Fifth Floor, Boston, MA 02110-1301, USA. Any Red Hat trademarks that are
|
||||
# incorporated in the source code or documentation are not subject to the GNU
|
||||
# General Public License and may only be used or replicated with the express
|
||||
# permission of Red Hat, Inc.
|
||||
#
|
||||
# Red Hat Author(s): Luke Macken <lmacken@redhat.com>
|
||||
# Toshio Kuratomi <tkuratom@redhat.com>
|
||||
#
|
||||
|
||||
'''
|
||||
python-fedora, python module to interact with Fedora Infrastructure Services
|
||||
'''
|
||||
|
||||
import Cookie
|
||||
import urllib
|
||||
import urllib2
|
||||
import logging
|
||||
import cPickle as pickle
|
||||
import re
|
||||
import inspect
|
||||
import simplejson
|
||||
from os import path
|
||||
from urlparse import urljoin
|
||||
|
||||
import gettext
|
||||
t = gettext.translation('python-fedora', '/usr/share/locale', fallback=True)
|
||||
_ = t.ugettext
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
SESSION_FILE = path.join(path.expanduser('~'), '.fedora_session')
|
||||
|
||||
class ServerError(Exception):
|
||||
pass
|
||||
|
||||
class AuthError(ServerError):
|
||||
pass
|
||||
|
||||
class BaseClient(object):
|
||||
'''
|
||||
A command-line client to interact with Fedora TurboGears Apps.
|
||||
'''
|
||||
def __init__(self, baseURL, username=None, password=None, debug=False):
|
||||
self.baseURL = baseURL
|
||||
self.username = username
|
||||
self.password = password
|
||||
self._sessionCookie = None
|
||||
|
||||
# Setup our logger
|
||||
sh = logging.StreamHandler()
|
||||
if debug:
|
||||
log.setLevel(logging.DEBUG)
|
||||
sh.setLevel(logging.DEBUG)
|
||||
else:
|
||||
log.setLevel(logging.INFO)
|
||||
sh.setLevel(logging.INFO)
|
||||
format = logging.Formatter("%(message)s")
|
||||
sh.setFormatter(format)
|
||||
log.addHandler(sh)
|
||||
|
||||
self._load_session()
|
||||
if username and password:
|
||||
self._authenticate(force=True)
|
||||
|
||||
def _authenticate(self, force=False):
|
||||
'''
|
||||
Return an authenticated session cookie.
|
||||
'''
|
||||
if not force and self._sessionCookie:
|
||||
return self._sessionCookie
|
||||
if not self.username:
|
||||
raise AuthError, _('username must be set')
|
||||
if not self.password:
|
||||
raise AuthError, _('password must be set')
|
||||
|
||||
req = urllib2.Request(urljoin(self.baseURL, 'login?tg_format=json'))
|
||||
req.add_header('Cookie', self._sessionCookie.output(attrs=[],
|
||||
header='').strip())
|
||||
req.add_data(urllib.urlencode({
|
||||
'user_name' : self.username,
|
||||
'password' : self.password,
|
||||
'login' : 'Login'
|
||||
}))
|
||||
|
||||
try:
|
||||
loginPage = urllib2.urlopen(req)
|
||||
except urllib2.HTTPError, e:
|
||||
if e.msg == 'Forbidden':
|
||||
raise AuthError, _('Invalid username/password')
|
||||
else:
|
||||
raise
|
||||
|
||||
loginData = simplejson.load(loginPage)
|
||||
|
||||
if 'message' in loginData:
|
||||
raise AuthError, _('Unable to login to server: %(message)s') \
|
||||
% loginData
|
||||
|
||||
self._sessionCookie = Cookie.SimpleCookie()
|
||||
try:
|
||||
self._sessionCookie.load(loginPage.headers['set-cookie'])
|
||||
except KeyError:
|
||||
self._sessionCookie = None
|
||||
raise AuthError, _('Unable to login to the server. Server did' \
|
||||
' not send back a cookie')
|
||||
self._save_session()
|
||||
|
||||
return self._sessionCookie
|
||||
session = property(_authenticate)
|
||||
|
||||
def _save_session(self):
|
||||
'''
|
||||
Store our pickled session cookie.
|
||||
|
||||
This method loads our existing session file and modified our
|
||||
current user's cookie. This allows us to retain cookies for
|
||||
multiple users.
|
||||
'''
|
||||
save = {}
|
||||
if path.isfile(SESSION_FILE):
|
||||
sessionFile = file(SESSION_FILE, 'r')
|
||||
try:
|
||||
save = pickle.load(sessionFile)
|
||||
except:
|
||||
pass
|
||||
sessionFile.close()
|
||||
save[self.username] = self._sessionCookie
|
||||
sessionFile = file(SESSION_FILE, 'w')
|
||||
pickle.dump(save, sessionFile)
|
||||
sessionFile.close()
|
||||
|
||||
def _load_session(self):
|
||||
'''
|
||||
Load a stored session cookie.
|
||||
'''
|
||||
if path.isfile(SESSION_FILE):
|
||||
sessionFile = file(SESSION_FILE, 'r')
|
||||
try:
|
||||
savedSession = pickle.load(sessionFile)
|
||||
self._sessionCookie = savedSession[self.username]
|
||||
log.debug(_('Loaded session %(cookie)s') % \
|
||||
{'cookie': self._sessionCookie})
|
||||
except EOFError:
|
||||
log.error(_('Unable to load session from %(file)s') % \
|
||||
{'file': SESSION_FILE})
|
||||
except KeyError:
|
||||
log.debug(_('Session is for a different user'))
|
||||
sessionFile.close()
|
||||
|
||||
def send_request(self, method, auth=False, input=None):
|
||||
'''
|
||||
Send a request to the server. The given method is called with any
|
||||
keyword parameters in **kw. If auth is True, then the request is
|
||||
made with an authenticated session cookie.
|
||||
'''
|
||||
url = urljoin(self.baseURL, method + '?tg_format=json')
|
||||
|
||||
response = None # the JSON that we get back from the server
|
||||
data = None # decoded JSON via simplejson.load()
|
||||
|
||||
log.debug(_('Creating request %(url)s') % {'url': url})
|
||||
req = urllib2.Request(url)
|
||||
if input:
|
||||
req.add_data(urllib.urlencode(input))
|
||||
|
||||
if auth:
|
||||
req.add_header('Cookie', self.session.output(attrs=[],
|
||||
header='').strip())
|
||||
elif self._sessionCookie:
|
||||
# If the cookie exists, send it so that visit tracking works.
|
||||
req.add_header('Cookie', self._sessionCookie.output(attrs=[],
|
||||
header='').strip())
|
||||
try:
|
||||
response = urllib2.urlopen(req)
|
||||
except urllib2.HTTPError, e:
|
||||
if e.msg == 'Forbidden':
|
||||
if (inspect.currentframe().f_back.f_code !=
|
||||
inspect.currentframe().f_code):
|
||||
self._authenticate(force=True)
|
||||
data = self.send_request(method, auth, input)
|
||||
else:
|
||||
# We actually shouldn't ever reach here. Unless something
|
||||
# goes drastically wrong _authenticate should raise an
|
||||
# AuthError
|
||||
raise AuthError, _('Unable to log into server: %(error)s') \
|
||||
% {'error': str(e)}
|
||||
log.error(e)
|
||||
raise ServerError, str(e)
|
||||
|
||||
# In case the server returned a new session cookie to us
|
||||
try:
|
||||
self._sessionCookie.load(response.headers['set-cookie'])
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
try:
|
||||
data = simplejson.load(response)
|
||||
except Exception, e:
|
||||
regex = re.compile('<span class="fielderror">(.*)</span>')
|
||||
match = regex.search(response)
|
||||
if match and len(match.groups()):
|
||||
return dict(tg_flash=match.groups()[0])
|
||||
else:
|
||||
raise ServerError, e.message
|
||||
|
||||
if 'logging_in' in data:
|
||||
if (inspect.currentframe().f_back.f_code !=
|
||||
inspect.currentframe().f_code):
|
||||
self._authenticate(force=True)
|
||||
data = self.send_request(method, auth, input)
|
||||
else:
|
||||
# We actually shouldn't ever reach here. Unless something goes
|
||||
# drastically wrong _authenticate should raise an AuthError
|
||||
raise AuthError, _('Unable to log into server: %(message)s') \
|
||||
% data
|
||||
return data
|
|
@ -1,234 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2007-2008 Red Hat, Inc. All rights reserved.
|
||||
#
|
||||
# This copyrighted material is made available to anyone wishing to use, modify,
|
||||
# copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
# General Public License v.2. This program is distributed in the hope that it
|
||||
# will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the
|
||||
# implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU General Public License for more details. You should have
|
||||
# received a copy of the GNU General Public License along with this program;
|
||||
# if not, write to the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
# Fifth Floor, Boston, MA 02110-1301, USA. Any Red Hat trademarks that are
|
||||
# incorporated in the source code or documentation are not subject to the GNU
|
||||
# General Public License and may only be used or replicated with the express
|
||||
# permission of Red Hat, Inc.
|
||||
#
|
||||
# Red Hat Author(s): Toshio Kuratomi <tkuratom@redhat.com>
|
||||
#
|
||||
|
||||
'''
|
||||
This plugin provides integration with the Fedora Account
|
||||
System using JSON calls.
|
||||
'''
|
||||
|
||||
import Cookie
|
||||
|
||||
from cherrypy import request
|
||||
from sqlalchemy.orm import class_mapper
|
||||
from turbogears import config, identity
|
||||
from turbogears.identity.saprovider import SqlAlchemyIdentity, \
|
||||
SqlAlchemyIdentityProvider
|
||||
from turbogears.database import session
|
||||
from turbogears.util import load_class
|
||||
|
||||
# Once this works, propogate the changes back to python-fedora and import as
|
||||
# from fedora.tg.client import BaseClient
|
||||
from client import BaseClient
|
||||
|
||||
import gettext
|
||||
t = gettext.translation('python-fedora', '/usr/share/locale', fallback=True)
|
||||
_ = t.ugettext
|
||||
|
||||
import crypt
|
||||
|
||||
import logging
|
||||
log = logging.getLogger('turbogears.identity.safasprovider')
|
||||
|
||||
try:
|
||||
set, frozenset
|
||||
except NameError:
|
||||
from sets import Set as set, ImmutableSet as frozenset
|
||||
|
||||
class JsonFasIdentity(BaseClient):
|
||||
'''Associate an identity with a person in the auth system.
|
||||
'''
|
||||
cookieName = config.get('visit.cookie.name', 'tg-visit')
|
||||
fasURL = config.get('fas.url', 'https://admin.fedoraproject.org/admin/fas/')
|
||||
|
||||
def __init__(self, visit_key, user=None, username=None, password=None,
|
||||
debug=False):
|
||||
super(JsonFasIdentity, self).__init__(self.fasURL, debug=debug)
|
||||
if user:
|
||||
self._user = user
|
||||
self._groups = frozenset(
|
||||
[g['name'] for g in data['person']['approved_memberships']]
|
||||
)
|
||||
self.visit_key = visit_key
|
||||
# It's allowed to use a null value for a visit_key if we know we're
|
||||
# generating an anonymous user. The json interface doesn't handle
|
||||
# that, though, and there's no reason for us to make it.
|
||||
if not visit_key:
|
||||
return
|
||||
|
||||
# Set the cookie to the user's tg_visit key before requesting
|
||||
# authentication. That way we link the two together.
|
||||
self._sessionCookie = Cookie.SimpleCookie()
|
||||
self._sessionCookie[self.cookieName] = self.visit_key
|
||||
self.username = username
|
||||
self.password = password
|
||||
if username and password:
|
||||
self._authenticate(force=True)
|
||||
|
||||
def _authenticate(self, force=False):
|
||||
'''Override BaseClient so we can keep visit_key in sync.
|
||||
'''
|
||||
super(JsonFasIdentity, self)._authenticate(force)
|
||||
if self._sessionCookie[self.cookieName].value != 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._sessionCookie[self.cookieName].value
|
||||
cookies = request.simple_cookie
|
||||
cookies[self.cookieName] = self.visit_key
|
||||
return self._sessionCookie
|
||||
session = property(_authenticate)
|
||||
|
||||
def _get_user(self):
|
||||
'''Retrieve information about the user from cache or network.'''
|
||||
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.
|
||||
# Query the account system URL for our given user's sessionCookie
|
||||
# FAS returns user and group listing
|
||||
data = self.send_request('user/view', auth=True)
|
||||
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):
|
||||
if not self.user:
|
||||
return None
|
||||
return self.user['username']
|
||||
user_name = property(_get_user_name)
|
||||
|
||||
def _get_groups(self):
|
||||
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)
|
||||
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
|
||||
'''
|
||||
try:
|
||||
user = JsonFasIdentity(visit_key, username=user_name,
|
||||
password=password)
|
||||
except AuthError, e:
|
||||
log.warning('Error logging in %(user)s: %(error)s' % {
|
||||
'user': username, 'error': e})
|
||||
return None
|
||||
|
||||
return JsonFasIdentity(visit_key, 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.
|
||||
|
||||
Arguments:
|
||||
:user: User information. Not used.
|
||||
:user_name: Given username.
|
||||
: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.
|
||||
'''
|
||||
|
||||
return user.password == crypt.crypt(password, user.password)
|
||||
|
||||
def load_identity(self, visit_key):
|
||||
'''Lookup the principal represented by visit_key.
|
||||
|
||||
Arguments:
|
||||
:visit_key: The session key for whom we're looking up an 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 IDs
|
||||
permissions: a set of permission IDs
|
||||
'''
|
||||
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
|
||||
'''
|
||||
|
||||
return JsonFasIdentity(None)
|
||||
|
||||
def authenticated_identity(self, user):
|
||||
'''
|
||||
Constructs Identity object for user that has no associated visit_key.
|
||||
'''
|
||||
return JsonFasIdentity(None, user)
|
|
@ -1,82 +0,0 @@
|
|||
'''
|
||||
This plugin provides integration with the Fedora Account System using JSON
|
||||
calls to the account system server.
|
||||
'''
|
||||
|
||||
import Cookie
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import class_mapper
|
||||
|
||||
from turbogears import config
|
||||
from turbogears.visit.api import BaseVisitManager, Visit
|
||||
from turbogears.database import get_engine, metadata, session, mapper
|
||||
from turbogears.util import load_class
|
||||
|
||||
# Once this works, propogate the changes back to python-fedora and import as
|
||||
# from fedora.tg.client import BaseClient
|
||||
from client import BaseClient
|
||||
|
||||
import gettext
|
||||
t = gettext.translation('python-fedora', '/usr/share/locale', fallback=True)
|
||||
_ = t.ugettext
|
||||
|
||||
import logging
|
||||
log = logging.getLogger("turbogears.identity.savisit")
|
||||
|
||||
class JsonFasVisitManager(BaseClient):
|
||||
'''
|
||||
This proxies visit requests to the Account System Server running remotely.
|
||||
|
||||
We don't need to worry about threading and other concerns because our proxy
|
||||
doesn't cause any asynchronous calls.
|
||||
'''
|
||||
fasURL = config.get('fas.url', 'https://admin.fedoraproject.org/admin/fas')
|
||||
cookieName = config.get('visit.cookie.name', 'tg-visit')
|
||||
|
||||
def __init__(self, timeout, debug=None):
|
||||
super(JsonFasVisitManager,self).__init__(self.fasURL, debug=debug)
|
||||
|
||||
def create_model(self):
|
||||
'''
|
||||
Create the Visit table if it doesn't already exist
|
||||
'''
|
||||
# Not needed as the visit tables reside remotely in the FAS2 database.
|
||||
pass
|
||||
|
||||
def new_visit_with_key(self, visit_key):
|
||||
# Hit any URL in fas2 with the visit_key set. That will call the
|
||||
# new_visit method in fas2
|
||||
self._sessionCookie = Cookie.SimpleCookie()
|
||||
self._sessionCookie[self.cookieName] = visit_key
|
||||
data = self.send_request('', auth=True)
|
||||
return Visit(self._sessionCookie[self.cookieName].value, True)
|
||||
|
||||
def visit_for_key(self, visit_key):
|
||||
'''
|
||||
Return the visit for this key or None if the visit doesn't exist or has
|
||||
expired.
|
||||
'''
|
||||
# Hit any URL in fas2 with the visit_key set. That will call the
|
||||
# new_visit method in fas2
|
||||
self._sessionCookie = Cookie.SimpleCookie()
|
||||
self._sessionCookie[self.cookieName] = visit_key
|
||||
data = self.send_request('', auth=True)
|
||||
# Knowing what happens in turbogears/visit/api.py when this is called,
|
||||
# we can shortcircuit this step and avoid a round trip to the FAS
|
||||
# server.
|
||||
# if visit_key != self._sessionCookie[self.cookieName].value:
|
||||
# # visit has expired
|
||||
# return None
|
||||
# # Hitting FAS has already updated the visit.
|
||||
# return Visit(visit_key, False)
|
||||
if visit_key != self._sessionCookie[self.cookieName].value:
|
||||
return Visit(self._sessionCookie[self.cookieName].value, True)
|
||||
else:
|
||||
return Visit(visit_key, False)
|
||||
|
||||
def update_queued_visits(self, queue):
|
||||
# Let the visit_manager on the FAS server manage this
|
||||
pass
|
Loading…
Add table
Add a link
Reference in a new issue