Enable mod_proxy to sit in front of TurboGears and pass along the

kerberos principal name
Add an identity an visit class to TurboGears that can handle the user
 without requiring a database
Update the UI to show the user correctly.
Note that this is currently disabled. It is hardcoded to always return the
 principal test@FREEIPA.ORG in proxyprovider.py
It doesn't handle an unauthorized request because that can never happen.
This commit is contained in:
rcritten 2007-09-10 16:33:01 -04:00
parent 37d10e0c51
commit 182fbe3094
9 changed files with 239 additions and 14 deletions

View File

@ -13,8 +13,19 @@
# If you have sqlite, here's a simple default to get you started
# in development
sqlobject.dburi="sqlite://%(current_dir_uri)s/devdata.sqlite"
# sqlobject.dburi="sqlite://%(current_dir_uri)s/devdata.sqlite"
# Our our sqlobject-derived proxy provider
identity.provider='proxyprovider'
# the first thing checked on any request. We want to short-circuit this
# as early as possible
identity.source = 'visit'
# Turn on identity and visit (visit is required for identity)
identity.on=True
visit.on=True
visit.manager='proxyvisit'
# if you are using a database or table type without transactions
# (MySQL default, for example), you should turn off transactions

View File

@ -4,6 +4,7 @@ start-ipagui.py
ipa_gui.egg-info/PKG-INFO
ipa_gui.egg-info/SOURCES.txt
ipa_gui.egg-info/dependency_links.txt
ipa_gui.egg-info/entry_points.txt
ipa_gui.egg-info/not-zip-safe
ipa_gui.egg-info/paster_plugins.txt
ipa_gui.egg-info/requires.txt
@ -13,8 +14,14 @@ ipagui/__init__.py
ipagui/controllers.py
ipagui/json.py
ipagui/model.py
ipagui/proxyprovider.py
ipagui/proxyvisit.py
ipagui/release.py
ipagui/config/__init__.py
ipagui/forms/__init__.py
ipagui/forms/user.py
ipagui/helpers/__init__.py
ipagui/helpers/userhelper.py
ipagui/templates/__init__.py
ipagui/tests/__init__.py
ipagui/tests/test_controllers.py

View File

@ -0,0 +1,6 @@
[turbogears.identity.provider]
proxyprovider = ipagui.proxyprovider:ProxyIdentityProvider
[turbogears.visit.manager]
proxyvisit = ipagui.proxyvisit:ProxyVisitManager

View File

@ -8,6 +8,7 @@ from turbogears import controllers, expose, flash
from turbogears import validators, validate
from turbogears import widgets, paginate
from turbogears import error_handler
from turbogears import identity
# from model import *
# import logging
# log = logging.getLogger("ipagui.controllers")
@ -27,7 +28,6 @@ user_edit_form = forms.user.UserEditForm()
password_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
client = ipa.ipaclient.IPAClient(True)
client.set_principal("test@FREEIPA.ORG")
user_fields = ['*', 'nsAccountLock']
@ -45,10 +45,12 @@ def utf8_encode(value):
class Root(controllers.RootController):
@expose(template="ipagui.templates.welcome")
@identity.require(identity.not_anonymous())
def index(self):
return dict()
@expose()
@identity.require(identity.not_anonymous())
def topsearch(self, **kw):
if kw.get('searchtype') == "Users":
return self.userlist(uid=kw.get('searchvalue'))
@ -62,6 +64,7 @@ class Root(controllers.RootController):
########
@expose("ipagui.templates.usernew")
@identity.require(identity.not_anonymous())
def usernew(self, tg_errors=None):
"""Displays the new user form"""
if tg_errors:
@ -70,9 +73,11 @@ class Root(controllers.RootController):
return dict(form=user_new_form)
@expose()
@identity.require(identity.not_anonymous())
def usercreate(self, **kw):
"""Creates a new user"""
restrict_post()
client.set_principal(identity.current.user_name)
if kw.get('submit') == 'Cancel':
turbogears.flash("Add user cancelled")
raise turbogears.redirect('/userlist')
@ -104,11 +109,13 @@ class Root(controllers.RootController):
@expose("ipagui.templates.useredit")
@identity.require(identity.not_anonymous())
def useredit(self, uid, tg_errors=None):
"""Displays the edit user form"""
if tg_errors:
turbogears.flash("There was a problem with the form!")
client.set_principal(identity.current.user_name)
user = client.get_user_by_uid(uid, user_fields)
user_dict = user.toDict()
# Edit shouldn't fill in the password field.
@ -121,9 +128,11 @@ class Root(controllers.RootController):
return dict(form=user_edit_form, user=user_dict)
@expose()
@identity.require(identity.not_anonymous())
def userupdate(self, **kw):
"""Updates an existing user"""
restrict_post()
client.set_principal(identity.current.user_name)
if kw.get('submit') == 'Cancel Edit':
turbogears.flash("Edit user cancelled")
raise turbogears.redirect('/usershow', uid=kw.get('uid'))
@ -169,8 +178,10 @@ class Root(controllers.RootController):
@expose("ipagui.templates.userlist")
@identity.require(identity.not_anonymous())
def userlist(self, **kw):
"""Retrieve a list of all users and display them in one huge list"""
client.set_principal(identity.current.user_name)
users = None
counter = 0
uid = kw.get('uid')
@ -190,8 +201,10 @@ class Root(controllers.RootController):
@expose("ipagui.templates.usershow")
@identity.require(identity.not_anonymous())
def usershow(self, uid):
"""Retrieve a single user for display"""
client.set_principal(identity.current.user_name)
try:
user = client.get_user_by_uid(uid, user_fields)
return dict(user=user.toDict(), fields=forms.user.UserFields())
@ -200,10 +213,12 @@ class Root(controllers.RootController):
raise turbogears.redirect("/")
@validate(form=user_new_form)
@identity.require(identity.not_anonymous())
def usercreatevalidate(self, tg_errors=None, **kw):
return tg_errors, kw
@validate(form=user_edit_form)
@identity.require(identity.not_anonymous())
def userupdatevalidate(self, tg_errors=None, **kw):
return tg_errors, kw
@ -222,10 +237,12 @@ class Root(controllers.RootController):
return password
@expose()
@identity.require(identity.not_anonymous())
def suggest_uid(self, givenname, sn):
if (len(givenname) == 0) or (len(sn) == 0):
return ""
client.set_principal(identity.current.user_name)
givenname = givenname.lower()
sn = sn.lower()
@ -309,7 +326,9 @@ class Root(controllers.RootController):
#########
@expose("ipagui.templates.groupindex")
@identity.require(identity.not_anonymous())
def groupindex(self, tg_errors=None):
client.set_principal(identity.current.user_name)
return dict()
@ -318,5 +337,7 @@ class Root(controllers.RootController):
############
@expose("ipagui.templates.resindex")
@identity.require(identity.not_anonymous())
def resindex(self, tg_errors=None):
client.set_principal(identity.current.user_name)
return dict()

View File

@ -0,0 +1,118 @@
from turbogears.identity.soprovider import *
from turbogears.identity.visitor import *
import logging
log = logging.getLogger("turbogears.identity")
class IPA_User(object):
'''
Shell of a User definition. We don't really need much here.
'''
def __init__(self, user_name):
self.user_name = user_name
self.display_name = user_name
self.permissions = None
self.groups = None
return
class ProxyIdentity(object):
def __init__(self, visit_key, user=None):
if user:
self._user= user
self.visit_key= visit_key
def _get_user(self):
try:
return self._user
except AttributeError:
# User hasn't already been set
return None
user= property(_get_user)
def _get_user_name(self):
if not self.user:
return None
return self.user.user_name
user_name= property(_get_user_name)
def _get_name(self):
if not self.user:
return None
return self.user.name
user_name= property(_get_name)
def _get_anonymous(self):
return not self.user
anonymous= property(_get_anonymous)
def _get_permissions(self):
try:
return self._permissions
except AttributeError:
# Permissions haven't been computed yet
return None
permissions= property(_get_permissions)
def _get_groups(self):
try:
return self._groups
except AttributeError:
# Groups haven't been computed yet
return None
groups= property(_get_groups)
def logout(self):
'''
Remove the link between this identity and the visit.
'''
# Clear the current identity
anon= ProxyObjectIdentity(None,None)
#XXX if user is None anonymous will be true, no need to set attr.
#anon.anonymous= True
identity.set_current_identity( anon )
class ProxyIdentityProvider(SqlObjectIdentityProvider):
'''
IdentityProvider that uses REMOTE_USER from Apache
'''
def __init__(self):
super(ProxyIdentityProvider, self).__init__()
get = turbogears.config.get
# We can get any config variables here
log.info( "Proxy Identity starting" )
def create_provider_model(self):
pass
def validate_identity(self, user_name, password, visit_key):
user = IPA_User(user_name)
log.debug( "validate_identity %s" % user_name)
return ProxyIdentity(visit_key, user)
def validate_password(self, user, user_name, password):
'''Validation has already occurred in the proxy'''
return True
def load_identity(self, visit_key):
try:
# user_name= cherrypy.request.headers['X-FORWARDED-USER']
user_name= "test@FREEIPA.ORG"
except KeyError:
return None
set_login_attempted( True )
return self.validate_identity( user_name, None, visit_key )
def anonymous_identity( self ):
'''
This shouldn't ever happen in IPA but including it to include the
entire identity API.
'''
return ProxyIdentity( None )
def authenticated_identity(self, user):
'''
Constructs Identity object for user that has no associated visit_key.
'''
return ProxyIdentity(None, user)

View File

@ -0,0 +1,25 @@
from turbogears.visit.api import BaseVisitManager, Visit
from turbogears import config
import logging
log = logging.getLogger("turbogears.visit.proxyvisit")
class ProxyVisitManager(BaseVisitManager):
"""Virtually empty class just so can avoid saving this stuff in a
database."""
def __init__(self, timeout):
super(ProxyVisitManager,self).__init__(timeout)
return
def create_model(self):
return
def new_visit_with_key(self, visit_key):
return Visit(visit_key, True)
def visit_for_key(self, visit_key):
return Visit(visit_key, False)
def update_queued_visits(self, queue):
return None

View File

@ -14,15 +14,6 @@
</head>
<body py:match="item.tag=='{http://www.w3.org/1999/xhtml}body'" py:attrs="item.items()">
<div py:if="tg.config('identity.on') and not defined('logging_in')" id="pageLogin">
<span py:if="tg.identity.anonymous">
<a href="${tg.url('/login')}">Login</a>
</span>
<span py:if="not tg.identity.anonymous">
Welcome ${tg.identity.user.display_name}.
<a href="${tg.url('/logout')}">Logout</a>
</span>
</div>
<div id="header">
<div id="logo">
@ -33,7 +24,15 @@
</div>
<div id="headerinfo">
<div id="login">
Logged in as: ace
<div py:if="tg.config('identity.on') and not defined('logging_in')" id="pageLogin">
<span py:if="tg.identity.anonymous">
<a href="${tg.url('/login')}">Login</a>
</span>
<span py:if="not tg.identity.anonymous">
Logged in as: ${tg.identity.user.display_name}
</span>
</div>
</div>
<div id="topsearch">
<form action="${tg.url('/topsearch')}" method="post">

View File

@ -58,5 +58,11 @@ setup(
# 'Framework :: TurboGears :: Widgets',
],
test_suite = 'nose.collector',
entry_points = """
[turbogears.identity.provider]
proxyprovider = ipagui.proxyprovider:ProxyIdentityProvider
[turbogears.visit.manager]
proxyvisit = ipagui.proxyvisit:ProxyVisitManager
""",
)

View File

@ -1,8 +1,8 @@
# LoadModule auth_kerb_module modules/mod_auth_kerb.so
Alias /ipa "/usr/share/ipa/ipaserver/XMLRPC"
# Require kerberos authentication for the entire server
<Directory "/usr/share/ipa/ipaserver">
<LocationMatch />
AuthType Kerberos
AuthName "Kerberos Login"
KrbMethodNegotiate on
@ -13,6 +13,37 @@ Alias /ipa "/usr/share/ipa/ipaserver/XMLRPC"
KrbSaveCredentials on
Require valid-user
ErrorDocument 401 /errors/unauthorized.html
</LocationMatch>
ProxyRequests Off
<Proxy *>
RewriteEngine on
Order deny,allow
Allow from all
# We create a subrequest to find REMOTE_USER. Don't do this for every
# subrequest too (slow and huge logs result)
RewriteCond %{IS_SUBREQ}% false
RewriteRule .* - [E=RU:%{LA-U:REMOTE_USER}]
RequestHeader set X-Forwarded-User %{RU}e
# RequestHeader unset Authorization
</Proxy>
# The URI's with a trailing ! are those that aren't handled by the proxy
ProxyPass /errors/ !
ProxyPass /ipa !
ProxyPass / http://localhost:8080/
ProxyPassReverse /errors !
ProxyPassReverse /ipa !
ProxyPassReverse / http://localhost:8080/
# Configure the XML-RPC service
Alias /ipa "/usr/share/ipa/ipaserver/XMLRPC"
<Directory "/usr/share/ipa/ipaserver">
SetHandler mod_python
PythonHandler ipaxmlrpc
@ -22,3 +53,4 @@ Alias /ipa "/usr/share/ipa/ipaserver/XMLRPC"
# this is pointless to use since it would just reload ipaxmlrpc.py
PythonAutoReload Off
</Directory>