~jnaous/rpc4django/urlgroups

« back to all changes in this revision

Viewing changes to rpc4django/views.py

  • Committer: Jad Naous
  • Date: 2010-11-20 07:43:51 UTC
  • Revision ID: jnaous@jadsm-20101120074351-u40j1micda55hzqd
Implement isolation across URLs

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
'''
2
2
The main entry point for RPC4Django. Usually, the user simply puts
3
 
:meth:`serve_rpc_request <rpc4django.views.serve_rpc_request>` into ``urls.py``
 
3
:meth:`rpc_url <rpc4django.utils.rpc_url>` into ``urls.py``
4
4
 
5
5
::
6
6
 
7
7
    urlpatterns = patterns('', 
8
8
        # rpc4django will need to be in your Python path  
9
 
        (r'^RPC2$', 'rpc4django.views.serve_rpc_request'), 
 
9
        rpc_url(r'^RPC2$', name="my_url_name"), 
10
10
    )
11
11
    
12
12
'''
13
13
 
14
14
import logging
 
15
import types
15
16
from xml.dom.minidom import parseString
16
17
from xml.parsers.expat import ExpatError
17
18
from django.http import HttpResponse, Http404, HttpResponseForbidden
18
19
from django.shortcuts import render_to_response
19
20
from django.conf import settings
20
 
from django.core.urlresolvers import reverse, NoReverseMatch
21
21
from rpcdispatcher import RPCDispatcher
22
22
from __init__ import version
23
23
 
45
45
# these will be scanned for @rpcmethod decorators
46
46
APPS = getattr(settings, 'INSTALLED_APPS', [])
47
47
 
48
 
def check_request_permission(request, request_format='xml'):
 
48
logger = logging.getLogger("rpc4django.views")
 
49
 
 
50
class NonExistingDispatcher(Exception):
 
51
    """Raised when the dispatcher for a particular name is not found."""
 
52
    def __init__(self, path, url_name):
 
53
        super(NonExistingDispatcher, self).__init__(
 
54
            "URL name '%s' is not used in any rpcmethod,\
 
55
 however, the URL '%s' uses it." % (url_name, path))
 
56
        self.path = path
 
57
        self.url_name = url_name
 
58
 
 
59
def get_dispatcher(path, url_name):
 
60
    try:
 
61
        dispatcher = dispatchers[url_name]
 
62
    except KeyError:
 
63
        raise NonExistingDispatcher(path, url_name)
 
64
    return dispatcher
 
65
 
 
66
def check_request_permission(request, request_format='xml', url_name="root"):
49
67
    '''
50
68
    Checks whether this user has permission to call a particular method
51
69
    This method does not check method call validity. That is done later
54
72
    
55
73
    - ``request`` - a django HttpRequest object
56
74
    - ``request_format`` - the request type: 'json' or 'xml' 
 
75
    - ``url_name`` - the name of the url at which this request was received
57
76
    
58
77
    Returns ``False`` if permission is denied and ``True`` otherwise
59
78
    '''
60
79
    
61
80
    user = getattr(request, 'user', None)
 
81
    dispatcher = get_dispatcher(request.path, url_name)
62
82
    methods = dispatcher.list_methods()
63
83
    method_name = dispatcher.get_method_name(request.raw_post_data, \
64
84
                                             request_format)
69
89
            # this is the method the user is calling
70
90
            # time to check the permissions
71
91
            if method.permission is not None:
72
 
                logging.debug('Method "%s" is protected by permission "%s"' \
 
92
                logger.debug('Method "%s" is protected by permission "%s"' \
73
93
                              %(method.name, method.permission))
74
94
                if user is None:
75
95
                    # user is only none if not using AuthenticationMiddleware
76
 
                    logging.warn('AuthenticationMiddleware is not enabled')
 
96
                    logger.warn('AuthenticationMiddleware is not enabled')
77
97
                    response = False
78
98
                elif not user.has_perm(method.permission):
79
99
                    # check the permission against the permission database
80
 
                    logging.info('User "%s" is NOT authorized' %(str(user)))
 
100
                    logger.info('User "%s" is NOT authorized' %(str(user)))
81
101
                    response = False
82
102
                else:
83
 
                    logging.debug('User "%s" is authorized' %(str(user)))
 
103
                    logger.debug('User "%s" is authorized' %(str(user)))
84
104
            else:
85
 
                logging.debug('Method "%s" is unprotected' %(method.name))
 
105
                logger.debug('Method "%s" is unprotected' %(method.name))
86
106
                
87
107
            break
88
108
    
111
131
        return False
112
132
    
113
133
    if LOG_REQUESTS_RESPONSES:
114
 
        logging.info('Unrecognized content-type "%s"' %conttype)
115
 
        logging.info('Analyzing rpc request data to get content type')
 
134
        logger.info('Unrecognized content-type "%s"' %conttype)
 
135
        logger.info('Analyzing rpc request data to get content type')
116
136
    
117
137
    # analyze post data to see whether it is xml or json
118
138
    # this is slower than if the content-type was set properly
124
144
    
125
145
    return False
126
146
 
127
 
def serve_rpc_request(request):
 
147
def serve_rpc_request(request, url_name="root", **kwargs):
128
148
    '''
129
149
    Handles rpc calls based on the content type of the request or
130
150
    returns the method documentation page if the request
134
154
    
135
155
    ``request``
136
156
        the Django HttpRequest object
137
 
        
 
157
    ``url_name``
 
158
        the name of the url at which the request arrived
 
159
    
138
160
    '''
139
 
 
 
161
    
 
162
    dispatcher = get_dispatcher(request.path, url_name)
 
163
    
140
164
    if request.method == "POST" and len(request.POST) > 0:
141
165
        # Handle POST request with RPC payload
142
166
        
143
167
        if LOG_REQUESTS_RESPONSES:
144
 
            logging.debug('Incoming request: %s' %str(request.raw_post_data))
 
168
            logger.debug('Incoming request: %s' % str(request.raw_post_data))
145
169
            
146
170
        if is_xmlrpc_request(request):
147
171
            if RESTRICT_XML:
148
172
                raise Http404
149
173
            
150
 
            if not check_request_permission(request, 'xml'):
 
174
            if not check_request_permission(request, 'xml', url_name=url_name):
151
175
                return HttpResponseForbidden()
152
176
            
153
 
            resp = dispatcher.xmldispatch(request.raw_post_data, \
154
 
                                          request=request)
 
177
            resp = dispatcher.xmldispatch(request.raw_post_data,
 
178
                                          request=request, **kwargs)
155
179
            response_type = 'text/xml'
156
180
        else:
157
181
            if RESTRICT_JSON:
158
182
                raise Http404
159
183
            
160
 
            if not check_request_permission(request, 'json'):
 
184
            if not check_request_permission(request, 'json', url_name=url_name):
161
185
                return HttpResponseForbidden()
162
186
            
163
 
            resp = dispatcher.jsondispatch(request.raw_post_data, \
164
 
                                           request=request)
 
187
            resp = dispatcher.jsondispatch(request.raw_post_data,
 
188
                                           request=request, **kwargs)
165
189
            response_type = 'application/json'
166
190
            
167
191
        if LOG_REQUESTS_RESPONSES:
168
 
            logging.debug('Outgoing %s response: %s' %(response_type, resp))
 
192
            logger.debug('Outgoing %s response: %s' %(response_type, resp))
169
193
        
170
194
        return HttpResponse(resp, response_type)
171
195
    elif request.method == 'OPTIONS':
185
209
                    request.META.get('HTTP_ACCESS_CONTROL_REQUEST_HEADERS', '')
186
210
                    
187
211
        if LOG_REQUESTS_RESPONSES:
188
 
            logging.debug('Outgoing HTTP access response to: %s' %(origin))
 
212
            logger.debug('Outgoing HTTP access response to: %s' %(origin))
189
213
                    
190
214
        return response
191
215
    else:
199
223
        methods = dispatcher.list_methods()
200
224
        template_data = {
201
225
            'methods': methods,
202
 
            'url': URL,
 
226
            'url': request.path,
203
227
            
204
228
            # rpc4django version
205
229
            'version': version(),
225
249
if csrf_exempt is not None:
226
250
    serve_rpc_request = csrf_exempt(serve_rpc_request)
227
251
 
228
 
# reverse the method for use with system.describe and ajax
229
 
try:
230
 
    URL = reverse(serve_rpc_request)
231
 
except NoReverseMatch:
232
 
    URL = ''
 
252
def _register_rpcmethods(apps, restrict_introspection=False, restrict_ootb_auth=True, dispatchers={}):
 
253
    '''
 
254
    Scans the installed apps for methods with the rpcmethod decorator
 
255
    Adds these methods to the list of methods callable via RPC
 
256
    '''
233
257
    
234
 
# instantiate the rpcdispatcher -- this examines the INSTALLED_APPS
235
 
# for any @rpcmethod decorators and adds them to the callable methods
236
 
dispatcher = RPCDispatcher(URL, APPS, RESTRICT_INTROSPECTION, RESTRICT_OOTB_AUTH) 
 
258
    for appname in apps:
 
259
        # check each app for any rpcmethods
 
260
        app = __import__(appname, globals(), locals(), ['*'])
 
261
        for obj in dir(app):
 
262
            method = getattr(app, obj)
 
263
            if callable(method) and \
 
264
               getattr(method, 'is_rpcmethod', False) == True:
 
265
                # if this method is callable and it has the rpcmethod
 
266
                # decorator, add it to the dispatcher
 
267
                if method.url_name not in dispatchers:
 
268
                    logger.debug("Registered URL name '%s'" % method.url_name)
 
269
                    dispatchers[method.url_name] = RPCDispatcher(
 
270
                        method.url_name, restrict_introspection,
 
271
                        restrict_ootb_auth)
 
272
                logger.debug(
 
273
                    "Registered method '%s' to URL name '%s'"
 
274
                    % (method.external_name, method.url_name))
 
275
                dispatchers[method.url_name].register_method(
 
276
                    method, method.external_name)
 
277
            elif isinstance(method, types.ModuleType):
 
278
                # if this is not a method and instead a sub-module,
 
279
                # scan the module for methods with @rpcmethod
 
280
                try:
 
281
                    _register_rpcmethods(
 
282
                        ["%s.%s" % (appname, obj)], 
 
283
                        restrict_introspection, restrict_ootb_auth, dispatchers)
 
284
                except ImportError:
 
285
                    pass
 
286
    return dispatchers
 
287
 
 
288
dispatchers = _register_rpcmethods(APPS, RESTRICT_INTROSPECTION, RESTRICT_OOTB_AUTH)
237
289