summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrcritten@redhat.com <rcritten@redhat.com>2007-09-05 13:14:23 -0400
committerrcritten@redhat.com <rcritten@redhat.com>2007-09-05 13:14:23 -0400
commit9b30f4674465b8e5f9bfcb359a9a9336dec0d120 (patch)
tree6cd0e8e521b315e17484445cd9b6507b5727244c
parent82943c31de32b0388a7c23e6e6a18cc9c99ac502 (diff)
downloadfreeipa-9b30f4674465b8e5f9bfcb359a9a9336dec0d120.tar.gz
freeipa-9b30f4674465b8e5f9bfcb359a9a9336dec0d120.tar.xz
freeipa-9b30f4674465b8e5f9bfcb359a9a9336dec0d120.zip
Enable LDAP SASL authentication using a forwarded kerberos ticket
Handle both SASL auth and proxied authentication Refactor LDAP connection code to be simpler Other small bug fixes
-rw-r--r--ipa-admintools/ipa-finduser4
-rw-r--r--ipa-admintools/ipa-usermod6
-rw-r--r--ipa-server/ipaserver/ipaldap.py55
-rw-r--r--ipa-server/xmlrpc-server/funcs.py237
-rw-r--r--ipa-server/xmlrpc-server/ipaxmlrpc.py12
5 files changed, 176 insertions, 138 deletions
diff --git a/ipa-admintools/ipa-finduser b/ipa-admintools/ipa-finduser
index 167ac23d7..409d2e3de 100644
--- a/ipa-admintools/ipa-finduser
+++ b/ipa-admintools/ipa-finduser
@@ -50,7 +50,9 @@ def main():
client = ipaclient.IPAClient()
users = client.find_users(args[1], sattrs=['dn','uid','cn','homeDirectory'])
- if len(users) == 0:
+ counter = users[0]
+ users = users[1:]
+ if counter == 0:
print "No entries found for", args[1]
return 0
diff --git a/ipa-admintools/ipa-usermod b/ipa-admintools/ipa-usermod
index 0c61f4097..317289a60 100644
--- a/ipa-admintools/ipa-usermod
+++ b/ipa-admintools/ipa-usermod
@@ -59,6 +59,9 @@ def main():
except ipa.ipaerror.IPAError, e:
print "%s" % e.message
return 1
+ except kerberos.GSSError, e:
+ print "Could not initialize GSSAPI: %s/%s" % (e[0][0][0], e[0][1][0])
+ return 1
if options.gecos:
user.setValue('gecos', options.gecos)
@@ -79,6 +82,9 @@ def main():
except xmlrpclib.ProtocolError, e:
print "Unable to connect to IPA server: %s" % (e.errmsg)
return 1
+ except ipa.ipaerror.IPAError, e:
+ print "%s" % (e.message)
+ return 1
return 0
diff --git a/ipa-server/ipaserver/ipaldap.py b/ipa-server/ipaserver/ipaldap.py
index 164509263..c0452b05a 100644
--- a/ipa-server/ipaserver/ipaldap.py
+++ b/ipa-server/ipaserver/ipaldap.py
@@ -35,13 +35,14 @@ import cStringIO
import time
import operator
import struct
+import ldap.sasl
from ldap.controls import LDAPControl,DecodeControlTuples,EncodeControlTuples
-from ldap.modlist import modifyModlist
-
from ldap.ldapobject import SimpleLDAPObject
-
from ipa import ipaerror, ipautil
+# Global variable to define SASL auth
+sasl_auth = ldap.sasl.sasl({},'GSSAPI')
+
class Entry:
"""This class represents an LDAP Entry object. An LDAP entry consists of a DN
and a list of attributes. Each attribute consists of a name and a list of
@@ -196,22 +197,34 @@ class IPAdmin(SimpleLDAPObject):
raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e)
def __localinit__(self):
- SimpleLDAPObject.__init__(self,'ldaps://%s:%d' % (self.host,self.port))
+ """If a CA certificate is provided then it is assumed that we are
+ doing SSL client authentication with proxy auth.
+
+ If a CA certificate is not present then it is assumed that we are
+ using a forwarded kerberos ticket for SASL auth. SASL provides
+ its own encryption.
+ """
+ if self.cacert is not None:
+ SimpleLDAPObject.__init__(self,'ldaps://%s:%d' % (self.host,self.port))
+ else:
+ SimpleLDAPObject.__init__(self,'ldap://%s:%d' % (self.host,self.port))
def __init__(self,host,port,cacert,bindcert,bindkey,proxydn=None):
- """We just set our instance variables and wrap the methods - the real work is
- done in __localinit__ and __initPart2 - these are separated out this way so
- that we can call them from places other than instance creation e.g. when
- using the start command, we just need to reconnect, not create a new instance"""
+ """We just set our instance variables and wrap the methods - the real
+ work is done in __localinit__ and __initPart2 - these are separated
+ out this way so that we can call them from places other than
+ instance creation e.g. when we just need to reconnect, not create a
+ new instance"""
# ldap.set_option(ldap.OPT_DEBUG_LEVEL,255)
- ldap.set_option(ldap.OPT_X_TLS_CACERTFILE,cacert)
- ldap.set_option(ldap.OPT_X_TLS_CERTFILE,bindcert)
- ldap.set_option(ldap.OPT_X_TLS_KEYFILE,bindkey)
+ if cacert is not None:
+ ldap.set_option(ldap.OPT_X_TLS_CACERTFILE,cacert)
+ ldap.set_option(ldap.OPT_X_TLS_CERTFILE,bindcert)
+ ldap.set_option(ldap.OPT_X_TLS_KEYFILE,bindkey)
self.__wrapmethods()
self.port = port or 389
- self.sslport = 0
self.host = host
+ self.cacert = cacert
self.bindcert = bindcert
self.bindkey = bindkey
self.proxydn = proxydn
@@ -251,6 +264,12 @@ class IPAdmin(SimpleLDAPObject):
def set_proxydn(self, proxydn):
self.proxydn = proxydn
+ def set_keytab(self, keytab):
+ if keytab is not None:
+ os.environ["KRB5CCNAME"] = keytab
+ self.sasl_interactive_bind_s("", sasl_auth)
+ self.proxydn = None
+
def getEntry(self,*args):
"""This wraps the search function. It is common to just get one entry"""
@@ -346,7 +365,8 @@ class IPAdmin(SimpleLDAPObject):
sctrl = self.__get_server_controls__()
try:
- self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
+ if sctrl is not None:
+ self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
self.add_s(*args)
except ldap.ALREADY_EXISTS:
raise ipaerror.gen_exception(ipaerror.LDAP_DUPLICATE)
@@ -366,7 +386,8 @@ class IPAdmin(SimpleLDAPObject):
raise ipaerror.gen_exception(ipaerror.LDAP_EMPTY_MODLIST)
try:
- self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
+ if sctrl is not None:
+ self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
self.modify_s(dn, modlist)
# this is raised when a 'delete' attribute isn't found.
# it indicates the previous attribute was removed by another
@@ -428,7 +449,8 @@ class IPAdmin(SimpleLDAPObject):
modlist.append((operation, "nsAccountlock", "true"))
try:
- self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
+ if sctrl is not None:
+ self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
self.modify_s(dn, modlist)
except ldap.LDAPError, e:
raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e)
@@ -440,7 +462,8 @@ class IPAdmin(SimpleLDAPObject):
sctrl = self.__get_server_controls__()
try:
- self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
+ if sctrl is not None:
+ self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
self.delete_s(*args)
except ldap.LDAPError, e:
raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e)
diff --git a/ipa-server/xmlrpc-server/funcs.py b/ipa-server/xmlrpc-server/funcs.py
index f4e01b342..294b5b5f7 100644
--- a/ipa-server/xmlrpc-server/funcs.py
+++ b/ipa-server/xmlrpc-server/funcs.py
@@ -47,20 +47,31 @@ DefaultGroupContainer = "cn=groups,cn=accounts"
# this is not anticipated.
class IPAConnPool:
def __init__(self):
- self.numentries = 0
self.freelist = []
- def getConn(self, host, port, bindca, bindcert, bindkey, proxydn=None):
- self.numentries = self.numentries + 1
+ def getConn(self, host, port, bindca, bindcert, bindkey, proxydn=None, keytab=None):
+ conn = None
if len(self.freelist) > 0:
- conn = self.freelist.pop()
- else:
+ for i in range(len(self.freelist)):
+ c = self.freelist[i]
+ if ((c.host == host) and (c.port == port)):
+ conn = self.freelist.pop(i)
+ break
+ if conn is None:
conn = ipaserver.ipaldap.IPAdmin(host,port,bindca,bindcert,bindkey)
- conn.set_proxydn(proxydn)
+ if proxydn is not None:
+ conn.set_proxydn(proxydn)
+ else:
+ conn.set_keytab(keytab)
return conn
def releaseConn(self, conn):
- self.freelist.append(conn)
+ # We can't re-use SASL connections. If proxydn is None it means
+ # we have a keytab set. See ipaldap.set_keytab
+ if conn.proxydn is None:
+ conn.unbind_s()
+ else:
+ self.freelist.append(conn)
class IPAServer:
@@ -68,7 +79,8 @@ class IPAServer:
global _LDAPPool
# FIXME, this needs to be auto-discovered
self.host = 'localhost'
- self.port = 636
+ self.port = 389
+ self.sslport = 636
self.bindcert = "/usr/share/ipa/cert.pem"
self.bindkey = "/usr/share/ipa/key.pem"
self.bindca = "/usr/share/ipa/cacert.asc"
@@ -79,24 +91,84 @@ class IPAServer:
self.basedn = ipa.ipautil.realm_to_suffix(ipa.config.config.get_realm())
self.scope = ldap.SCOPE_SUBTREE
self.princ = None
+ self.keytab = None
def set_principal(self, princ):
self.princ = princ
+
+ def set_keytab(self, keytab):
+ self.keytab = keytab
def get_dn_from_principal(self, princ):
- """Given a kerberls principal get the LDAP uid"""
+ """Given a kerberos principal get the LDAP uid"""
global _LDAPPool
filter = "(krbPrincipalName=" + princ + ")"
# The only anonymous search we should have
- m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,None)
+ conn = _LDAPPool.getConn(self.host,self.sslport,self.bindca,self.bindcert,self.bindkey,None,None)
try:
- ent = m1.getEntry(self.basedn, self.scope, filter, ['dn'])
+ ent = conn.getEntry(self.basedn, self.scope, filter, ['dn'])
finally:
- _LDAPPool.releaseConn(m1)
+ _LDAPPool.releaseConn(conn)
return "dn:" + ent.dn
+ def __setup_connection(self, opts):
+ """Set up common things done in the connection.
+ If there is a keytab then return None as the proxy dn and the keytab
+ otherwise return the proxy dn and None as the keytab.
+
+ We only want one or the other used at one time and we prefer
+ the keytab. So if there is a keytab, return that and None for
+ proxy dn to make calling getConn() easier.
+ """
+
+ if opts:
+ if opts.get('keytab'):
+ self.set_keytab(opts['keytab'])
+ self.set_principal(None)
+ else:
+ self.set_keytab(None)
+ self.set_principal(opts['remoteuser'])
+ else:
+ self.set_keytab(None)
+ # The caller should have already set the principal
+
+ if self.princ is not None:
+ return self.get_dn_from_principal(self.princ), None
+ else:
+ return None, self.keytab
+
+ def getConnection(self, opts):
+ """Wrapper around IPAConnPool.getConn() so we don't have to pass
+ around self.* every time a connection is needed.
+
+ For SASL connections (where we have a keytab) we can't set
+ the SSL variables for certificates. It confuses the ldap
+ module.
+ """
+ global _LDAPPool
+
+ (proxy_dn, keytab) = self.__setup_connection(opts)
+
+ if keytab is not None:
+ bindca = None
+ bindcert = None
+ bindkey = None
+ port = self.port
+ else:
+ bindca = self.bindca
+ bindcert = self.bindcert
+ bindkey = self.bindkey
+ port = self.sslport
+
+ return _LDAPPool.getConn(self.host,port,bindca,bindcert,bindkey,proxy_dn,keytab)
+
+ def releaseConnection(self, conn):
+ global _LDAPPool
+
+ _LDAPPool.releaseConn(conn)
+
def convert_entry(self, ent):
entry = dict(ent.data)
entry['dn'] = ent.dn
@@ -110,24 +182,17 @@ class IPAServer:
entry[key] = value[0]
return entry
-
def __get_entry (self, base, filter, sattrs=None, opts=None):
"""Get a specific entry. Return as a dict of values.
Multi-valued fields are represented as lists.
"""
- global _LDAPPool
ent=""
- if opts:
- self.set_principal(opts['remoteuser'])
-
- dn = self.get_dn_from_principal(self.princ)
-
- m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
+ conn = self.getConnection(opts)
try:
- ent = m1.getEntry(base, self.scope, filter, sattrs)
+ ent = conn.getEntry(base, self.scope, filter, sattrs)
finally:
- _LDAPPool.releaseConn(m1)
+ self.releaseConnection(conn)
return self.convert_entry(ent)
@@ -137,8 +202,6 @@ class IPAServer:
oldentry is a dict
newentry is a dict
"""
- global _LDAPPool
-
oldentry = self.convert_scalar_values(oldentry)
newentry = self.convert_scalar_values(newentry)
@@ -150,16 +213,11 @@ class IPAServer:
except KeyError, e:
raise ipaerror.gen_exception(ipaerror.LDAP_MISSING_DN)
- if opts:
- self.set_principal(opts['remoteuser'])
-
- proxydn = self.get_dn_from_principal(self.princ)
-
- m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,proxydn)
+ conn = self.getConnection(opts)
try:
- res = m1.updateEntry(moddn, oldentry, newentry)
+ res = conn.updateEntry(moddn, oldentry, newentry)
finally:
- _LDAPPool.releaseConn(m1)
+ self.releaseConnection(conn)
return res
def __safe_filter(self, criteria):
@@ -234,8 +292,6 @@ class IPAServer:
attribute name and the value is either a string or in the case
of a multi-valued field a list of values. user_container sets
where in the tree the user is placed."""
- global _LDAPPool
-
if user_container is None:
user_container = DefaultUserContainer
@@ -288,16 +344,11 @@ class IPAServer:
for u in user:
entry.setValues(u, user[u])
- if opts:
- self.set_principal(opts['remoteuser'])
-
- dn = self.get_dn_from_principal(self.princ)
-
- m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
+ conn = self.getConnection(opts)
try:
- res = m1.addEntry(entry)
+ res = conn.addEntry(entry)
finally:
- _LDAPPool.releaseConn(m1)
+ self.releaseConnection(conn)
return res
def get_add_schema (self):
@@ -348,20 +399,13 @@ class IPAServer:
"""Return a list containing a User object for each
existing user.
"""
- global _LDAPPool
-
- if opts:
- self.set_principal(opts['remoteuser'])
-
- dn = self.get_dn_from_principal(self.princ)
-
filter = "(objectclass=posixAccount)"
- m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
+ conn = self.getConnection(opts)
try:
- all_users = m1.getList(self.basedn, self.scope, filter, None)
+ all_users = conn.getList(self.basedn, self.scope, filter, None)
finally:
- _LDAPPool.releaseConn(m1)
+ self.releaseConnection(conn)
users = []
for u in all_users:
@@ -372,13 +416,6 @@ class IPAServer:
def find_users (self, criteria, sattrs=None, opts=None):
"""Returns a list: counter followed by the results.
If the results are truncated, counter will be set to -1."""
- global _LDAPPool
-
- if opts:
- self.set_principal(opts['remoteuser'])
-
- dn = self.get_dn_from_principal(self.princ)
-
# Assume the list of fields to search will come from a central
# configuration repository. A good format for that would be
# a comma-separated list of fields
@@ -394,21 +431,21 @@ class IPAServer:
(exact_match_filter, partial_match_filter) = self.__generate_match_filters(
search_fields, criteria_words)
- m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
+ conn = self.getConnection(opts)
try:
try:
- exact_results = m1.getListAsync(self.basedn, self.scope,
+ exact_results = conn.getListAsync(self.basedn, self.scope,
exact_match_filter, sattrs)
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
exact_results = [0]
try:
- partial_results = m1.getListAsync(self.basedn, self.scope,
+ partial_results = conn.getListAsync(self.basedn, self.scope,
partial_match_filter, sattrs)
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
partial_results = [0]
finally:
- _LDAPPool.releaseConn(m1)
+ self.releaseConnection(conn)
exact_counter = exact_results[0]
partial_counter = partial_results[0]
@@ -450,13 +487,6 @@ class IPAServer:
def mark_user_deleted (self, uid, opts=None):
"""Mark a user as inactive in LDAP. We aren't actually deleting
users here, just making it so they can't log in, etc."""
- global _LDAPPool
-
- if opts:
- self.set_principal(opts['remoteuser'])
-
- proxydn = self.get_dn_from_principal(self.princ)
-
user = self.get_user_by_uid(uid, ['dn', 'uid', 'nsAccountlock'], opts)
# Are we doing an add or replace operation?
@@ -467,11 +497,11 @@ class IPAServer:
else:
has_key = False
- m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,proxydn)
+ conn = self.getConnection(opts)
try:
- res = m1.inactivateEntry(user['dn'], has_key)
+ res = conn.inactivateEntry(user['dn'], has_key)
finally:
- _LDAPPool.releaseConn(m1)
+ self.releaseConnection(conn)
return res
def delete_user (self, uid, opts=None):
@@ -483,18 +513,15 @@ class IPAServer:
The memberOf plugin handles removing the user from any other
groups.
"""
- if opts:
- self.set_principal(opts['remoteuser'])
-
- dn = self.get_dn_from_principal(self.princ)
-
user_dn = self.get_user_by_uid(uid, ['dn', 'uid', 'objectclass'], opts)
if user_dn is None:
raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND)
- m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
- res = m1.deleteEntry(user_dn['dn'])
- _LDAPPool.releaseConn(m1)
+ conn = self.getConnection(opts)
+ try:
+ res = conn.deleteEntry(user_dn['dn'])
+ finally:
+ self.releaseConnection(conn)
return res
# Group support
@@ -532,8 +559,6 @@ class IPAServer:
attribute name and the value is either a string or in the case
of a multi-valued field a list of values. group_container sets
where in the tree the group is placed."""
- global _LDAPPool
-
if group_container is None:
group_container = DefaultGroupContainer
@@ -554,38 +579,26 @@ class IPAServer:
for g in group:
entry.setValues(g, group[g])
- if opts:
- self.set_principal(opts['remoteuser'])
-
- dn = self.get_dn_from_principal(self.princ)
-
- m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
+ conn = self.getConnection(opts)
try:
- res = m1.addEntry(entry)
+ res = conn.addEntry(entry)
finally:
- _LDAPPool.releaseConn(m1)
+ self.releaseConnection(conn)
def find_groups (self, criteria, sattrs=None, opts=None):
"""Return a list containing a User object for each
existing group that matches the criteria.
"""
- global _LDAPPool
-
- if opts:
- self.set_principal(opts['remoteuser'])
-
- dn = self.get_dn_from_principal(self.princ)
-
criteria = self.__safe_filter(criteria)
filter = "(&(cn=%s)(objectClass=posixGroup))" % criteria
- m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
+ conn = self.getConnection(opts)
try:
- results = m1.getList(self.basedn, self.scope, filter, sattrs)
+ results = conn.getList(self.basedn, self.scope, filter, sattrs)
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
results = []
finally:
- _LDAPPool.releaseConn(m1)
+ self.releaseConnection(conn)
groups = []
for u in results:
@@ -599,9 +612,6 @@ class IPAServer:
group is the cn of the group to be added to
"""
- if opts:
- self.set_principal(opts['remoteuser'])
-
old_group = self.get_group_by_cn(group, None, opts)
if old_group is None:
raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND)
@@ -652,9 +662,6 @@ class IPAServer:
group is the cn of the group to be removed from
"""
- if opts:
- self.set_principal(opts['remoteuser'])
-
old_group = self.get_group_by_cn(group, None, opts)
if old_group is None:
raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND)
@@ -718,19 +725,16 @@ class IPAServer:
The memberOf plugin handles removing the group from any other
groups.
"""
- if opts:
- self.set_principal(opts['remoteuser'])
-
- dn = self.get_dn_from_principal(self.princ)
-
group = self.get_group_by_cn(group_cn, ['dn', 'cn'], opts)
if len(group) != 1:
raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND)
- m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
- res = m1.deleteEntry(group[0]['dn'])
- _LDAPPool.releaseConn(m1)
+ conn = self.getConnection(opts)
+ try:
+ res = conn.deleteEntry(group[0]['dn'])
+ finally:
+ self.releaseConnection(conn)
return res
def add_group_to_group(self, group, tgroup, opts=None):
@@ -739,9 +743,6 @@ class IPAServer:
tgroup is the cn of the group to be added to
"""
- if opts:
- self.set_principal(opts['remoteuser'])
-
old_group = self.get_group_by_cn(tgroup, None, opts)
if old_group is None:
raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND)
diff --git a/ipa-server/xmlrpc-server/ipaxmlrpc.py b/ipa-server/xmlrpc-server/ipaxmlrpc.py
index 5dc60b51b..f2ddd35e8 100644
--- a/ipa-server/xmlrpc-server/ipaxmlrpc.py
+++ b/ipa-server/xmlrpc-server/ipaxmlrpc.py
@@ -126,13 +126,19 @@ class ModXMLRPCRequestHandler(object):
def register_instance(self,instance):
self.register_module(instance)
- def _marshaled_dispatch(self, data, remoteuser):
+ def _marshaled_dispatch(self, data, req):
"""Dispatches an XML-RPC method from marshalled (XML) data."""
params, method = loads(data)
+ # Populate the Apache environment variables
+ req.add_common_vars()
+
opts={}
- opts['remoteuser'] = remoteuser
+ opts['remoteuser'] = req.user
+
+ if req.subprocess_env.get("KRB5CCNAME") is not None:
+ opts['keytab'] = req.subprocess_env.get("KRB5CCNAME")
# Tack onto the end of the passed-in arguments any options we also
# need
@@ -263,7 +269,7 @@ class ModXMLRPCRequestHandler(object):
req.allow_methods(['POST'],1)
raise apache.SERVER_RETURN, apache.HTTP_METHOD_NOT_ALLOWED
- response = self._marshaled_dispatch(req.read(), req.user)
+ response = self._marshaled_dispatch(req.read(), req)
req.content_type = "text/xml"
req.set_content_length(len(response))