Index: RPCAuth/current/CHANGLOG.txt
===================================================================
--- RPCAuth/current/CHANGLOG.txt	(revision 22)
+++ RPCAuth/current/CHANGLOG.txt	(revision 22)
@@ -0,0 +1,10 @@
+version 0.0.1 Initial release: works for me
+
+version 0.0.2 Changed detection in modifyRequest to check Content-Type
+and Request-Method
+
+version 0.0.3 Added add, list, remove methods so product coders can
+register their functions to avoid prefixing username and passwords.
+Changed modifyRequest to not check Content-Type.
+
+version 0.4 Zope 2.7 support. Work if unicode path is passed in.
Index: RPCAuth/current/README.txt
===================================================================
--- RPCAuth/current/README.txt	(revision 22)
+++ RPCAuth/current/README.txt	(revision 22)
@@ -0,0 +1,6 @@
+RPCAuth is a rip off of the great CookieCrumbler product.  It is
+designed to allow users of XML-RPC methods to pass their username and
+password as arguments.  It works on the same principle as the
+CookieCrumbler allowing the transparent use of existing UserFolders,
+just add a RPCAuth instance to the folder where your acl_users is
+located.
Index: RPCAuth/current/RPCAuth.py
===================================================================
--- RPCAuth/current/RPCAuth.py	(revision 22)
+++ RPCAuth/current/RPCAuth.py	(revision 22)
@@ -0,0 +1,224 @@
+"""
+RPCAuth is a pretraversal hook that checks for xmlrpc calls and
+tries to extract a username and password from the arguements passed
+"""
+
+import sys
+from base64 import encodestring
+from urllib import quote, unquote
+from os import path
+import string
+from DateTime import DateTime
+from utils import SimpleItemWithProperties
+from AccessControl import ClassSecurityInfo, Permissions
+from ZPublisher import BeforeTraverse
+import Globals
+from zLOG import LOG, ERROR
+from App.Common import package_home
+from ZPublisher.HTTPRequest import HTTPRequest
+
+# Constants.
+ATTEMPT_DISABLED = -1
+ATTEMPT_NONE = 0
+ATTEMPT_LOGIN = 1
+ATTEMPT_CONT = 2
+
+ModifyRPCAuth = 'Change RPC Auth'
+
+
+class RPCAuth(SimpleItemWithProperties):
+    '''
+    Reads xmlrpc args during traversal and simulates the HTTP
+    authentication headers.
+    '''
+    meta_type = 'RPC Auth'
+    security = ClassSecurityInfo()
+    security.declareProtected(ModifyRPCAuth,
+                              'manage_editProperties',
+                              'manage_changeProperties')
+    security.declareProtected(Permissions.view_management_screens,
+                              'manage_propertiesForm')
+
+
+    _properties = ({'id':'uname_arg', 'type': 'string', 'mode':'w',
+                    'label':'User name arguement prefix'},
+                   {'id':'pword_arg', 'type': 'string', 'mode':'w',
+                    'label':'User password arguement prefix'},
+                   {'id':'remove_args','type':'boolean','mode':'w',
+                    'label':'Remove password username arguements'},
+                   )
+
+    def __init__(self):
+        self.remove_args = 1
+        self.uname_arg = 'zid'
+        self.pword_arg = 'zpw'
+        self._authProviders = {'absglob':{},'abs':{},'rel':{}}
+
+
+    security.declareProtected(ModifyRPCAuth,'addAuthProvider')
+    def addAuthProvider(self,objectPaths,function):
+        """takes a list of paths and a function as an arguements"""
+        aps = getattr(self,'_authProviders',{'absglob':{},'abs':{},'rel':{}})
+        for name in list(objectPaths):
+            if name[-1] == '/' and name[0] == '/': # absolute glob
+                aps['absglob'][tuple(string.split(name,'/')[1:-1])] = function
+            if name[0] == '/':  # absolute url
+                aps['abs'][tuple(string.split(name,'/')[1:])] = function
+            if name[0] != '/' and name[-1] != '/' and '/' in name: # relative
+                aps['rel'][tuple(string.split(name,'/'))] = function
+        self._authProviders = aps
+        self._p_changed = 1
+
+    security.declareProtected(ModifyRPCAuth,'listAuthProviders')
+    def listAuthProviders(self):
+        """a list of objects with auth providers"""
+        aps = getattr(self,'_authProviders',{'absglob':{},'abs':{},'rel':{}})
+        return aps['absglob'].keys()+\
+               aps['abs'].keys()+\
+               aps['rel'].keys()
+
+    security.declareProtected(ModifyRPCAuth,'removeAuthProvider')
+    def removeAuthProvider(self,objectPaths):
+        """remove auth providers objectPaths should be a list"""
+        for objectPath in list(objectPaths):
+                if type(objectPath) == type('s'):
+                    objectPath = string.split(objectPath,'/')
+                    while '' in objectPath: del objectPath[objectPath.index('')]
+                    tuple(objectPath)
+                if objectPath in self._authProviders['absglob'].keys():
+                    del self._authProviders['absglob'][objectPath]
+                elif objectPath in self._authProviders['abs'].keys():
+                    del self._authProviders['abs'][objectPath]
+                elif objectPath in self._authProviders['rel'].keys():
+                    del self._authProviders['rel'][objectPath]
+
+
+    def _identify(self,arg_tuple):
+        arg_list = list(arg_tuple)
+        zusername,zpassword = None,None
+        try:
+            for item in arg_tuple: #use the tuple here so we can delete items as we iterate
+                if type(item) == type('') and len(item) > len(self.uname_arg) and item[:len(self.uname_arg)] == self.uname_arg:
+                    zusername = item[len(self.uname_arg):]
+                    del arg_list[arg_list.index(item)]
+                    continue
+                if type(item) == type('') and len(item) > len(self.uname_arg) and item[:len(self.pword_arg)] == self.pword_arg:
+                    zpassword = item[len(self.pword_arg):]
+                    del arg_list[arg_list.index(item)]
+            if zusername and zpassword:
+                arg_tuple = tuple(arg_list)
+                return zusername,zpassword,arg_tuple
+            else:
+                return None,None,None
+        except 'none':
+            return None
+
+    def _registryIdenitfy(self,targetPath):
+        targetPath = list(targetPath)
+        targetPath.reverse()
+        targetPath = [a.encode('utf-8') for a in targetPath
+                      if type(a) == type(u'')]
+        targetPath = tuple(targetPath)
+        # first check absolute path cuz it's easy
+        if targetPath in self._authProviders['abs'].keys():
+            return self._authProviders['abs'][targetPath]
+        # check relative paths
+        relPath = targetPath[:]
+        while len(relPath) > 1:
+            if relPath in self._authProviders['rel'].keys():
+                return self._authProviders['rel'][relPath]
+            relPath= relPath[1:]
+        # check absolute globs
+        globPath = targetPath[:-1]
+        while len(globPath) > 1:
+            if globPath in self._authProviders['absglob'].keys():
+                return self._authProviders['absglob'][globPath]
+            globPath=globPath[:-1]
+        return None
+
+    security.declarePrivate('modifyRequest')
+    def modifyRequest(self, req, resp):
+        # Returns flags indicating what the user is trying to do.
+
+        if req.__class__ is not HTTPRequest:
+            return ATTEMPT_DISABLED
+
+        if not req._auth:
+                # Attempt to log in.
+                targetPath = tuple(req['TraversalRequestNameStack'])
+                authProvider = self._registryIdenitfy(targetPath)
+                if authProvider:
+                    name,pw,arg_tuple = authProvider(req.args)
+                else:
+                    name,pw,arg_tuple = self._identify(req.args)
+                if name and pw:
+                    ac = encodestring('%s:%s' % (name, pw))
+                    req._auth = 'basic %s' % ac
+                    resp._auth = 1
+                    if self.remove_args or authProvider: # if we set remove_args or an authProvider is used
+                        req.args = arg_tuple
+                    return ATTEMPT_LOGIN
+        return ATTEMPT_NONE
+
+    def __call__(self, container, req):
+        '''The __before_publishing_traverse__ hook.'''
+        resp = self.REQUEST['RESPONSE']
+        attempt = self.modifyRequest(req, resp)
+
+
+    def _cleanupResponse(self):
+        resp = self.REQUEST['RESPONSE']
+        try: del resp.unauthorized
+        except: pass
+        try: del resp._unauthorized
+        except: pass
+        return resp
+
+
+
+    security.declarePrivate('unauthorized')
+    def unauthorized(self):
+        resp.unauthorized()
+
+    def _unauthorized(self):
+        resp._unauthorized()
+
+
+    # Installation and removal of traversal hooks.
+
+    def manage_beforeDelete(self, item, container):
+        if item is self:
+            handle = self.meta_type + '/' + self.getId()
+            BeforeTraverse.unregisterBeforeTraverse(container, handle)
+
+    def manage_afterAdd(self, item, container):
+        if item is self:
+            handle = self.meta_type + '/' + self.getId()
+            container = container.this()
+            nc = BeforeTraverse.NameCaller(self.getId())
+            BeforeTraverse.registerBeforeTraverse(container, nc, handle)
+
+Globals.InitializeClass(RPCAuth)
+
+def manage_addRAForm(self):
+        "this is a form to get the id"
+        return """<html>
+        <head>
+           <title>Setup RPC Auth</title>
+        </head>
+        <body>
+        Please type the id of the RPC Auth:<br>
+            <form name="form" action="manage_addRPCAuth"><br>
+            <input type="text" name="id"><br>
+            <input type="submit" value="add">
+            </form>
+        </body>
+        </html>"""
+
+def manage_addRPCAuth(self, id, REQUEST=None):
+    ' '
+    ob = RPCAuth()
+    ob.id = id
+    self._setObject(id, ob)
+    if REQUEST is not None:
+        return self.manage_main(self, REQUEST)
Index: RPCAuth/current/__init__.py
===================================================================
--- RPCAuth/current/__init__.py	(revision 22)
+++ RPCAuth/current/__init__.py	(revision 22)
@@ -0,0 +1,10 @@
+import RPCAuth 
+
+def initialize(context):
+    context.registerClass(
+        RPCAuth.RPCAuth,
+        constructors=(RPCAuth.manage_addRAForm,
+                      RPCAuth.manage_addRPCAuth),
+        icon = 'RPCAuth.gif'
+        )
+
Index: RPCAuth/current/utils.py
===================================================================
--- RPCAuth/current/utils.py	(revision 22)
+++ RPCAuth/current/utils.py	(revision 22)
@@ -0,0 +1,127 @@
+##############################################################################
+# 
+# Zope Public License (ZPL) Version 1.0
+# -------------------------------------
+# 
+# Copyright (c) Digital Creations.  All rights reserved.
+# 
+# This license has been certified as Open Source(tm).
+# 
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+# 
+# 1. Redistributions in source code must retain the above copyright
+#    notice, this list of conditions, and the following disclaimer.
+# 
+# 2. 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.
+# 
+# 3. Digital Creations requests that attribution be given to Zope
+#    in any manner possible. Zope includes a "Powered by Zope"
+#    button that is installed by default. While it is not a license
+#    violation to remove this button, it is requested that the
+#    attribution remain. A significant investment has been put
+#    into Zope, and this effort will continue if the Zope community
+#    continues to grow. This is one way to assure that growth.
+# 
+# 4. All advertising materials and documentation mentioning
+#    features derived from or use of this software must display
+#    the following acknowledgement:
+# 
+#      "This product includes software developed by Digital Creations
+#      for use in the Z Object Publishing Environment
+#      (http://www.zope.org/)."
+# 
+#    In the event that the product being advertised includes an
+#    intact Zope distribution (with copyright and license included)
+#    then this clause is waived.
+# 
+# 5. Names associated with Zope or Digital Creations must not be used to
+#    endorse or promote products derived from this software without
+#    prior written permission from Digital Creations.
+# 
+# 6. Modified redistributions of any form whatsoever must retain
+#    the following acknowledgment:
+# 
+#      "This product includes software developed by Digital Creations
+#      for use in the Z Object Publishing Environment
+#      (http://www.zope.org/)."
+# 
+#    Intact (re-)distributions of any official Zope release do not
+#    require an external acknowledgement.
+# 
+# 7. Modifications are encouraged but must be packaged separately as
+#    patches to official Zope releases.  Distributions that do not
+#    clearly separate the patches from the original work must be clearly
+#    labeled as unofficial distributions.  Modifications which do not
+#    carry the name Zope may be packaged in any form, as long as they
+#    conform to all of the clauses above.
+# 
+# 
+# Disclaimer
+# 
+#   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
+#   EXPRESSED 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 DIGITAL CREATIONS OR ITS
+#   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 software consists of contributions made by Digital Creations and
+# many individuals on behalf of Digital Creations.  Specific
+# attributions are listed in the accompanying credits file.
+# 
+##############################################################################
+'''$Id: $
+'''
+
+import Globals
+from AccessControl import ClassSecurityInfo
+from OFS.PropertyManager import PropertyManager
+from OFS.SimpleItem import SimpleItem
+
+
+class SimpleItemWithProperties (PropertyManager, SimpleItem):
+    '''
+    A common base class for objects with configurable
+    properties in a fixed schema.
+    '''
+    manage_options = (
+        PropertyManager.manage_options
+        + SimpleItem.manage_options)
+
+    security = ClassSecurityInfo()
+    security.declarePrivate(
+        'manage_addProperty',
+        'manage_delProperties',
+        'manage_changePropertyTypes',
+        )
+
+    def manage_propertiesForm(self, REQUEST, *args, **kw):
+        'An override that makes the schema fixed.'
+        my_kw = kw.copy()
+        my_kw['property_extensible_schema__'] = 0
+        return apply(PropertyManager.manage_propertiesForm,
+                     (self, self, REQUEST,) + args, my_kw)
+
+    security.declarePublic('propertyLabel')
+    def propertyLabel(self, id):
+        """Return a label for the given property id
+        """
+        for p in self._properties:
+            if p['id'] == id:
+                return p.get('label', id)
+        return id
+
+Globals.InitializeClass(SimpleItemWithProperties)
+
Index: RPCAuth/current/version.txt
===================================================================
--- RPCAuth/current/version.txt	(revision 22)
+++ RPCAuth/current/version.txt	(revision 22)
@@ -0,0 +1,1 @@
+RPCAuth-0.0.4
