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) |
---|