[22] | 1 | """ |
---|
| 2 | RPCAuth is a pretraversal hook that checks for xmlrpc calls and |
---|
| 3 | tries to extract a username and password from the arguements passed |
---|
| 4 | """ |
---|
| 5 | |
---|
| 6 | import sys |
---|
| 7 | from base64 import encodestring |
---|
| 8 | from urllib import quote, unquote |
---|
| 9 | from os import path |
---|
| 10 | import string |
---|
| 11 | from DateTime import DateTime |
---|
| 12 | from utils import SimpleItemWithProperties |
---|
| 13 | from AccessControl import ClassSecurityInfo, Permissions |
---|
| 14 | from ZPublisher import BeforeTraverse |
---|
| 15 | import Globals |
---|
| 16 | from zLOG import LOG, ERROR |
---|
| 17 | from App.Common import package_home |
---|
| 18 | from ZPublisher.HTTPRequest import HTTPRequest |
---|
| 19 | |
---|
| 20 | # Constants. |
---|
| 21 | ATTEMPT_DISABLED = -1 |
---|
| 22 | ATTEMPT_NONE = 0 |
---|
| 23 | ATTEMPT_LOGIN = 1 |
---|
| 24 | ATTEMPT_CONT = 2 |
---|
| 25 | |
---|
| 26 | ModifyRPCAuth = 'Change RPC Auth' |
---|
| 27 | |
---|
| 28 | |
---|
| 29 | class RPCAuth(SimpleItemWithProperties): |
---|
| 30 | ''' |
---|
| 31 | Reads xmlrpc args during traversal and simulates the HTTP |
---|
| 32 | authentication headers. |
---|
| 33 | ''' |
---|
| 34 | meta_type = 'RPC Auth' |
---|
| 35 | security = ClassSecurityInfo() |
---|
| 36 | security.declareProtected(ModifyRPCAuth, |
---|
| 37 | 'manage_editProperties', |
---|
| 38 | 'manage_changeProperties') |
---|
| 39 | security.declareProtected(Permissions.view_management_screens, |
---|
| 40 | 'manage_propertiesForm') |
---|
| 41 | |
---|
| 42 | |
---|
| 43 | _properties = ({'id':'uname_arg', 'type': 'string', 'mode':'w', |
---|
| 44 | 'label':'User name arguement prefix'}, |
---|
| 45 | {'id':'pword_arg', 'type': 'string', 'mode':'w', |
---|
| 46 | 'label':'User password arguement prefix'}, |
---|
| 47 | {'id':'remove_args','type':'boolean','mode':'w', |
---|
| 48 | 'label':'Remove password username arguements'}, |
---|
| 49 | ) |
---|
| 50 | |
---|
| 51 | def __init__(self): |
---|
| 52 | self.remove_args = 1 |
---|
| 53 | self.uname_arg = 'zid' |
---|
| 54 | self.pword_arg = 'zpw' |
---|
| 55 | self._authProviders = {'absglob':{},'abs':{},'rel':{}} |
---|
| 56 | |
---|
| 57 | |
---|
| 58 | security.declareProtected(ModifyRPCAuth,'addAuthProvider') |
---|
| 59 | def addAuthProvider(self,objectPaths,function): |
---|
| 60 | """takes a list of paths and a function as an arguements""" |
---|
| 61 | aps = getattr(self,'_authProviders',{'absglob':{},'abs':{},'rel':{}}) |
---|
| 62 | for name in list(objectPaths): |
---|
| 63 | if name[-1] == '/' and name[0] == '/': # absolute glob |
---|
| 64 | aps['absglob'][tuple(string.split(name,'/')[1:-1])] = function |
---|
| 65 | if name[0] == '/': # absolute url |
---|
| 66 | aps['abs'][tuple(string.split(name,'/')[1:])] = function |
---|
| 67 | if name[0] != '/' and name[-1] != '/' and '/' in name: # relative |
---|
| 68 | aps['rel'][tuple(string.split(name,'/'))] = function |
---|
| 69 | self._authProviders = aps |
---|
| 70 | self._p_changed = 1 |
---|
| 71 | |
---|
| 72 | security.declareProtected(ModifyRPCAuth,'listAuthProviders') |
---|
| 73 | def listAuthProviders(self): |
---|
| 74 | """a list of objects with auth providers""" |
---|
| 75 | aps = getattr(self,'_authProviders',{'absglob':{},'abs':{},'rel':{}}) |
---|
| 76 | return aps['absglob'].keys()+\ |
---|
| 77 | aps['abs'].keys()+\ |
---|
| 78 | aps['rel'].keys() |
---|
| 79 | |
---|
| 80 | security.declareProtected(ModifyRPCAuth,'removeAuthProvider') |
---|
| 81 | def removeAuthProvider(self,objectPaths): |
---|
| 82 | """remove auth providers objectPaths should be a list""" |
---|
| 83 | for objectPath in list(objectPaths): |
---|
| 84 | if type(objectPath) == type('s'): |
---|
| 85 | objectPath = string.split(objectPath,'/') |
---|
| 86 | while '' in objectPath: del objectPath[objectPath.index('')] |
---|
| 87 | tuple(objectPath) |
---|
| 88 | if objectPath in self._authProviders['absglob'].keys(): |
---|
| 89 | del self._authProviders['absglob'][objectPath] |
---|
| 90 | elif objectPath in self._authProviders['abs'].keys(): |
---|
| 91 | del self._authProviders['abs'][objectPath] |
---|
| 92 | elif objectPath in self._authProviders['rel'].keys(): |
---|
| 93 | del self._authProviders['rel'][objectPath] |
---|
| 94 | |
---|
| 95 | |
---|
| 96 | def _identify(self,arg_tuple): |
---|
| 97 | arg_list = list(arg_tuple) |
---|
| 98 | zusername,zpassword = None,None |
---|
| 99 | try: |
---|
| 100 | for item in arg_tuple: #use the tuple here so we can delete items as we iterate |
---|
| 101 | if type(item) == type('') and len(item) > len(self.uname_arg) and item[:len(self.uname_arg)] == self.uname_arg: |
---|
| 102 | zusername = item[len(self.uname_arg):] |
---|
| 103 | del arg_list[arg_list.index(item)] |
---|
| 104 | continue |
---|
| 105 | if type(item) == type('') and len(item) > len(self.uname_arg) and item[:len(self.pword_arg)] == self.pword_arg: |
---|
| 106 | zpassword = item[len(self.pword_arg):] |
---|
| 107 | del arg_list[arg_list.index(item)] |
---|
| 108 | if zusername and zpassword: |
---|
| 109 | arg_tuple = tuple(arg_list) |
---|
| 110 | return zusername,zpassword,arg_tuple |
---|
| 111 | else: |
---|
| 112 | return None,None,None |
---|
| 113 | except 'none': |
---|
| 114 | return None |
---|
| 115 | |
---|
| 116 | def _registryIdenitfy(self,targetPath): |
---|
| 117 | targetPath = list(targetPath) |
---|
| 118 | targetPath.reverse() |
---|
| 119 | targetPath = [a.encode('utf-8') for a in targetPath |
---|
| 120 | if type(a) == type(u'')] |
---|
| 121 | targetPath = tuple(targetPath) |
---|
| 122 | # first check absolute path cuz it's easy |
---|
| 123 | if targetPath in self._authProviders['abs'].keys(): |
---|
| 124 | return self._authProviders['abs'][targetPath] |
---|
| 125 | # check relative paths |
---|
| 126 | relPath = targetPath[:] |
---|
| 127 | while len(relPath) > 1: |
---|
| 128 | if relPath in self._authProviders['rel'].keys(): |
---|
| 129 | return self._authProviders['rel'][relPath] |
---|
| 130 | relPath= relPath[1:] |
---|
| 131 | # check absolute globs |
---|
| 132 | globPath = targetPath[:-1] |
---|
| 133 | while len(globPath) > 1: |
---|
| 134 | if globPath in self._authProviders['absglob'].keys(): |
---|
| 135 | return self._authProviders['absglob'][globPath] |
---|
| 136 | globPath=globPath[:-1] |
---|
| 137 | return None |
---|
| 138 | |
---|
| 139 | security.declarePrivate('modifyRequest') |
---|
| 140 | def modifyRequest(self, req, resp): |
---|
| 141 | # Returns flags indicating what the user is trying to do. |
---|
| 142 | |
---|
| 143 | if req.__class__ is not HTTPRequest: |
---|
| 144 | return ATTEMPT_DISABLED |
---|
| 145 | |
---|
| 146 | if not req._auth: |
---|
| 147 | # Attempt to log in. |
---|
| 148 | targetPath = tuple(req['TraversalRequestNameStack']) |
---|
| 149 | authProvider = self._registryIdenitfy(targetPath) |
---|
| 150 | if authProvider: |
---|
| 151 | name,pw,arg_tuple = authProvider(req.args) |
---|
| 152 | else: |
---|
| 153 | name,pw,arg_tuple = self._identify(req.args) |
---|
| 154 | if name and pw: |
---|
| 155 | ac = encodestring('%s:%s' % (name, pw)) |
---|
| 156 | req._auth = 'basic %s' % ac |
---|
| 157 | resp._auth = 1 |
---|
| 158 | if self.remove_args or authProvider: # if we set remove_args or an authProvider is used |
---|
| 159 | req.args = arg_tuple |
---|
| 160 | return ATTEMPT_LOGIN |
---|
| 161 | return ATTEMPT_NONE |
---|
| 162 | |
---|
| 163 | def __call__(self, container, req): |
---|
| 164 | '''The __before_publishing_traverse__ hook.''' |
---|
| 165 | resp = self.REQUEST['RESPONSE'] |
---|
| 166 | attempt = self.modifyRequest(req, resp) |
---|
| 167 | |
---|
| 168 | |
---|
| 169 | def _cleanupResponse(self): |
---|
| 170 | resp = self.REQUEST['RESPONSE'] |
---|
| 171 | try: del resp.unauthorized |
---|
| 172 | except: pass |
---|
| 173 | try: del resp._unauthorized |
---|
| 174 | except: pass |
---|
| 175 | return resp |
---|
| 176 | |
---|
| 177 | |
---|
| 178 | |
---|
| 179 | security.declarePrivate('unauthorized') |
---|
| 180 | def unauthorized(self): |
---|
| 181 | resp.unauthorized() |
---|
| 182 | |
---|
| 183 | def _unauthorized(self): |
---|
| 184 | resp._unauthorized() |
---|
| 185 | |
---|
| 186 | |
---|
| 187 | # Installation and removal of traversal hooks. |
---|
| 188 | |
---|
| 189 | def manage_beforeDelete(self, item, container): |
---|
| 190 | if item is self: |
---|
| 191 | handle = self.meta_type + '/' + self.getId() |
---|
| 192 | BeforeTraverse.unregisterBeforeTraverse(container, handle) |
---|
| 193 | |
---|
| 194 | def manage_afterAdd(self, item, container): |
---|
| 195 | if item is self: |
---|
| 196 | handle = self.meta_type + '/' + self.getId() |
---|
| 197 | container = container.this() |
---|
| 198 | nc = BeforeTraverse.NameCaller(self.getId()) |
---|
| 199 | BeforeTraverse.registerBeforeTraverse(container, nc, handle) |
---|
| 200 | |
---|
| 201 | Globals.InitializeClass(RPCAuth) |
---|
| 202 | |
---|
| 203 | def manage_addRAForm(self): |
---|
| 204 | "this is a form to get the id" |
---|
| 205 | return """<html> |
---|
| 206 | <head> |
---|
| 207 | <title>Setup RPC Auth</title> |
---|
| 208 | </head> |
---|
| 209 | <body> |
---|
| 210 | Please type the id of the RPC Auth:<br> |
---|
| 211 | <form name="form" action="manage_addRPCAuth"><br> |
---|
| 212 | <input type="text" name="id"><br> |
---|
| 213 | <input type="submit" value="add"> |
---|
| 214 | </form> |
---|
| 215 | </body> |
---|
| 216 | </html>""" |
---|
| 217 | |
---|
| 218 | def manage_addRPCAuth(self, id, REQUEST=None): |
---|
| 219 | ' ' |
---|
| 220 | ob = RPCAuth() |
---|
| 221 | ob.id = id |
---|
| 222 | self._setObject(id, ob) |
---|
| 223 | if REQUEST is not None: |
---|
| 224 | return self.manage_main(self, REQUEST) |
---|