1
2
3
4 """
5 Remoting server implementations.
6
7 @since: 0.1.0
8 """
9
10 import sys
11 import types
12 import datetime
13
14 import pyamf
15 from pyamf import remoting, util
16
17 try:
18 from platform import python_implementation
19 impl = python_implementation()
20 except ImportError:
21 impl = 'Python'
22
23 SERVER_NAME = 'PyAMF/%s %s/%s' % (
24 '.'.join(map(lambda x: str(x), pyamf.__version__)), impl,
25 '.'.join(map(lambda x: str(x), sys.version_info[0:3]))
26 )
27
28
30 """
31 Base service error.
32 """
33
34
36 """
37 Client made a request for an unknown service.
38 """
39 _amf_code = 'Service.ResourceNotFound'
40
41
43 """
44 Client made a request for an unknown method.
45 """
46 _amf_code = 'Service.MethodNotFound'
47
48
50 """
51 Client made a request for an invalid methodname.
52 """
53 _amf_code = 'Service.MethodInvalid'
54
55
57 """
58 Wraps a supplied service with extra functionality.
59
60 @ivar service: The original service.
61 @type service: C{callable}
62 @ivar description: A description of the service.
63 @type description: C{str}
64 """
65 - def __init__(self, service, description=None, authenticator=None,
66 expose_request=None, preprocessor=None):
67 self.service = service
68 self.description = description
69 self.authenticator = authenticator
70 self.expose_request = expose_request
71 self.preprocessor = preprocessor
72
74 if isinstance(other, ServiceWrapper):
75 return cmp(self.__dict__, other.__dict__)
76
77 return cmp(self.service, other)
78
80 """
81 @raise InvalidServiceMethodError: Calls to private methods are not
82 allowed.
83 @raise UnknownServiceMethodError: Unknown method.
84 @raise InvalidServiceMethodError: Service method must be callable.
85 """
86 service = None
87
88 if isinstance(self.service, (type, types.ClassType)):
89 service = self.service()
90 else:
91 service = self.service
92
93 if method is not None:
94 method = str(method)
95
96 if method.startswith('_'):
97 raise InvalidServiceMethodError(
98 "Calls to private methods are not allowed")
99
100 try:
101 func = getattr(service, method)
102 except AttributeError:
103 raise UnknownServiceMethodError(
104 "Unknown method %s" % str(method))
105
106 if not callable(func):
107 raise InvalidServiceMethodError(
108 "Service method %s must be callable" % str(method))
109
110 return func
111
112 if not callable(service):
113 raise UnknownServiceMethodError(
114 "Unknown method %s" % str(self.service))
115
116 return service
117
119 """
120 Executes the service.
121
122 If the service is a class, it will be instantiated.
123
124 @param method: The method to call on the service.
125 @type method: C{None} or C{mixed}
126 @param params: The params to pass to the service.
127 @type params: C{list} or C{tuple}
128 @return: The result of the execution.
129 @rtype: C{mixed}
130 """
131 func = self._get_service_func(method, params)
132
133 return func(*params)
134
136 """
137 Gets a C{dict} of valid method callables for the underlying service
138 object.
139 """
140 callables = {}
141
142 for name in dir(self.service):
143 method = getattr(self.service, name)
144
145 if name.startswith('_') or not callable(method):
146 continue
147
148 callables[name] = method
149
150 return callables
151
153 if service_request == None:
154 return self.authenticator
155
156 methods = self.getMethods()
157
158 if service_request.method is None:
159 if hasattr(self.service, '_pyamf_authenticator'):
160 return self.service._pyamf_authenticator
161
162 if service_request.method not in methods:
163 return self.authenticator
164
165 method = methods[service_request.method]
166
167 if hasattr(method, '_pyamf_authenticator'):
168 return method._pyamf_authenticator
169
170 return self.authenticator
171
173 if service_request == None:
174 return self.expose_request
175
176 methods = self.getMethods()
177
178 if service_request.method is None:
179 if hasattr(self.service, '_pyamf_expose_request'):
180 return self.service._pyamf_expose_request
181
182 return self.expose_request
183
184 if service_request.method not in methods:
185 return self.expose_request
186
187 method = methods[service_request.method]
188
189 if hasattr(method, '_pyamf_expose_request'):
190 return method._pyamf_expose_request
191
192 return self.expose_request
193
195 if service_request == None:
196 return self.preprocessor
197
198 methods = self.getMethods()
199
200 if service_request.method is None:
201 if hasattr(self.service, '_pyamf_preprocessor'):
202 return self.service._pyamf_preprocessor
203
204 if service_request.method not in methods:
205 return self.preprocessor
206
207 method = methods[service_request.method]
208
209 if hasattr(method, '_pyamf_preprocessor'):
210 return method._pyamf_preprocessor
211
212 return self.preprocessor
213
214
216 """
217 Remoting service request.
218
219 @ivar request: The request to service.
220 @type request: L{Envelope<pyamf.remoting.Envelope>}
221 @ivar service: Facilitates the request.
222 @type service: L{ServiceWrapper}
223 @ivar method: The method to call on the service. A value of C{None}
224 means that the service will be called directly.
225 @type method: C{None} or C{str}
226 """
227 - def __init__(self, amf_request, service, method):
228 self.request = amf_request
229 self.service = service
230 self.method = method
231
233 return self.service(self.method, args)
234
235
237 """
238 I hold a collection of services, mapping names to objects.
239 """
241 if isinstance(value, basestring):
242 return value in self.keys()
243
244 return value in self.values()
245
246
248 """
249 Generic Remoting gateway.
250
251 @ivar services: A map of service names to callables.
252 @type services: L{ServiceCollection}
253 @ivar authenticator: A callable that will check the credentials of
254 the request before allowing access to the service. Will return a
255 C{bool} value.
256 @type authenticator: C{Callable} or C{None}
257 @ivar preprocessor: Called before the actual service method is invoked.
258 Useful for setting up sessions etc.
259 @type preprocessor: C{Callable} or C{None}
260 @ivar logger: A logging instance.
261 @ivar strict: Defines whether the gateway should use strict en/decoding.
262 @type strict: C{bool}
263 @ivar timezone_offset: A L{datetime.timedelta} between UTC and the
264 timezone to be encoded. Most dates should be handled as UTC to avoid
265 confusion but for older legacy systems this is not an option. Supplying
266 an int as this will be interpretted in seconds.
267 """
268
269 _request_class = ServiceRequest
270
271 - def __init__(self, services={}, **kwargs):
272 self.services = ServiceCollection()
273 self.authenticator = kwargs.pop('authenticator', None)
274 self.preprocessor = kwargs.pop('preprocessor', None)
275 self.expose_request = kwargs.pop('expose_request', False)
276 self.strict = kwargs.pop('strict', False)
277 self.logger = kwargs.pop('logger', None)
278 self.timezone_offset = kwargs.pop('timezone_offset', None)
279
280 debug = kwargs.pop('debug', False)
281
282 if kwargs:
283 raise TypeError('Unknown kwargs: %r' % (kwargs,))
284
285 self.debug = debug
286
287 if not hasattr(services, 'iteritems'):
288 raise TypeError("dict type required for services")
289
290 for name, service in services.iteritems():
291 self.addService(service, name)
292
293 - def addService(self, service, name=None, description=None,
294 authenticator=None, expose_request=None, preprocessor=None):
295 """
296 Adds a service to the gateway.
297
298 @param service: The service to add to the gateway.
299 @type service: C{callable}, class instance, or a module
300 @param name: The name of the service.
301 @type name: C{str}
302 @raise pyamf.remoting.RemotingError: Service already exists.
303 @raise TypeError: C{service} cannot be a scalar value.
304 @raise TypeError: C{service} must be C{callable} or a module.
305 """
306 if isinstance(service, (int, long, float, basestring)):
307 raise TypeError("Service cannot be a scalar value")
308
309 allowed_types = (types.ModuleType, types.FunctionType, types.DictType,
310 types.MethodType, types.InstanceType, types.ObjectType)
311
312 if not callable(service) and not isinstance(service, allowed_types):
313 raise TypeError("Service must be a callable, module, or an object")
314
315 if name is None:
316
317 if isinstance(service, (type, types.ClassType)):
318 name = service.__name__
319 elif isinstance(service, types.FunctionType):
320 name = service.func_name
321 elif isinstance(service, types.ModuleType):
322 name = service.__name__
323 else:
324 name = str(service)
325
326 if name in self.services:
327 raise remoting.RemotingError("Service %s already exists" % name)
328
329 self.services[name] = ServiceWrapper(service, description,
330 authenticator, expose_request, preprocessor)
331
333 if self.timezone_offset is None:
334 return None
335
336 if isinstance(self.timezone_offset, datetime.timedelta):
337 return self.timezone_offset
338
339 return datetime.timedelta(seconds=self.timezone_offset)
340
342 """
343 Removes a service from the gateway.
344
345 @param service: The service to remove from the gateway.
346 @type service: C{callable} or a class instance
347 @raise NameError: Service not found.
348 """
349 if service not in self.services:
350 raise NameError("Service %s not found" % str(service))
351
352 for name, wrapper in self.services.iteritems():
353 if isinstance(service, basestring) and service == name:
354 del self.services[name]
355
356 return
357 elif isinstance(service, ServiceWrapper) and wrapper == service:
358 del self.services[name]
359
360 return
361 elif isinstance(service, (type, types.ClassType,
362 types.FunctionType)) and wrapper.service == service:
363 del self.services[name]
364
365 return
366
367
368 raise RuntimeError("Something went wrong ...")
369
371 """
372 Returns a service based on the message.
373
374 @raise UnknownServiceError: Unknown service.
375 @param request: The AMF request.
376 @type request: L{Request<pyamf.remoting.Request>}
377 @rtype: L{ServiceRequest}
378 """
379 try:
380 return self._request_class(
381 request.envelope, self.services[target], None)
382 except KeyError:
383 pass
384
385 try:
386 sp = target.split('.')
387 name, meth = '.'.join(sp[:-1]), sp[-1]
388
389 return self._request_class(
390 request.envelope, self.services[name], meth)
391 except (ValueError, KeyError):
392 pass
393
394 raise UnknownServiceError("Unknown service %s" % target)
395
411
413 """
414 Returns the response to the request.
415
416 Any implementing gateway must define this function.
417
418 @param amf_request: The AMF request.
419 @type amf_request: L{Envelope<pyamf.remoting.Envelope>}
420
421 @return: The AMF response.
422 @rtype: L{Envelope<pyamf.remoting.Envelope>}
423 """
424 raise NotImplementedError
425
427 """
428 Decides whether the underlying http request should be exposed as the
429 first argument to the method call. This is granular, looking at the
430 service method first, then at the service level and finally checking
431 the gateway.
432
433 @rtype: C{bool}
434 """
435 expose_request = service_request.service.mustExposeRequest(service_request)
436
437 if expose_request is None:
438 if self.expose_request is None:
439 return False
440
441 return self.expose_request
442
443 return expose_request
444
446 """
447 Gets an authenticator callable based on the service_request. This is
448 granular, looking at the service method first, then at the service
449 level and finally to see if there is a global authenticator function
450 for the gateway. Returns C{None} if one could not be found.
451 """
452 auth = service_request.service.getAuthenticator(service_request)
453
454 if auth is None:
455 return self.authenticator
456
457 return auth
458
460 """
461 Processes an authentication request. If no authenticator is supplied,
462 then authentication succeeds.
463
464 @return: Returns a C{bool} based on the result of authorization. A
465 value of C{False} will stop processing the request and return an
466 error to the client.
467 @rtype: C{bool}
468 """
469 authenticator = self.getAuthenticator(service_request)
470
471 if authenticator is None:
472 return True
473
474 args = (username, password)
475
476 if hasattr(authenticator, '_pyamf_expose_request'):
477 http_request = kwargs.get('http_request', None)
478 args = (http_request,) + args
479
480 return authenticator(*args) == True
481
483 """
484 Gets a preprocessor callable based on the service_request. This is
485 granular, looking at the service method first, then at the service
486 level and finally to see if there is a global preprocessor function
487 for the gateway. Returns C{None} if one could not be found.
488 """
489 preproc = service_request.service.getPreprocessor(service_request)
490
491 if preproc is None:
492 return self.preprocessor
493
494 return preproc
495
497 """
498 Preprocesses a request.
499 """
500 processor = self.getPreprocessor(service_request)
501
502 if processor is None:
503 return
504
505 args = (service_request,) + args
506
507 if hasattr(processor, '_pyamf_expose_request'):
508 http_request = kwargs.get('http_request', None)
509 args = (http_request,) + args
510
511 return processor(*args)
512
514 """
515 Executes the service_request call
516 """
517 if self.mustExposeRequest(service_request):
518 http_request = kwargs.get('http_request', None)
519 args = (http_request,) + args
520
521 return service_request(*args)
522
523
525 """
526 A decorator that facilitates authentication per method. Setting
527 C{expose_request} to C{True} will set the underlying request object (if
528 there is one), usually HTTP and set it to the first argument of the
529 authenticating callable. If there is no request object, the default is
530 C{None}.
531
532 @raise TypeError: C{func} and authenticator must be callable.
533 """
534 if not callable(func):
535 raise TypeError('func must be callable')
536
537 if not callable(c):
538 raise TypeError('Authenticator must be callable')
539
540 attr = func
541
542 if isinstance(func, types.UnboundMethodType):
543 attr = func.im_func
544
545 if expose_request is True:
546 c = globals()['expose_request'](c)
547
548 setattr(attr, '_pyamf_authenticator', c)
549
550 return func
551
552
554 """
555 A decorator that adds an expose_request flag to the underlying callable.
556
557 @raise TypeError: C{func} must be callable.
558 """
559 if not callable(func):
560 raise TypeError("func must be callable")
561
562 if isinstance(func, types.UnboundMethodType):
563 setattr(func.im_func, '_pyamf_expose_request', True)
564 else:
565 setattr(func, '_pyamf_expose_request', True)
566
567 return func
568
569
571 """
572 A decorator that facilitates preprocessing per method. Setting
573 C{expose_request} to C{True} will set the underlying request object (if
574 there is one), usually HTTP and set it to the first argument of the
575 preprocessing callable. If there is no request object, the default is
576 C{None}.
577
578 @raise TypeError: C{func} and preprocessor must be callable.
579 """
580 if not callable(func):
581 raise TypeError('func must be callable')
582
583 if not callable(c):
584 raise TypeError('Preprocessor must be callable')
585
586 attr = func
587
588 if isinstance(func, types.UnboundMethodType):
589 attr = func.im_func
590
591 if expose_request is True:
592 c = globals()['expose_request'](c)
593
594 setattr(attr, '_pyamf_preprocessor', c)
595
596 return func
597
598
607