Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/rest_framework/request.py: 60%
239 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1"""
2The Request class is used as a wrapper around the standard request object.
4The wrapped request then offers a richer API, in particular :
6 - content automatically parsed according to `Content-Type` header,
7 and available as `request.data`
8 - full support of PUT method, including support for file uploads
9 - form overloading of HTTP method, content type and content
10"""
11import io
12import sys
13from contextlib import contextmanager
15from django.conf import settings
16from django.http import HttpRequest, QueryDict
17from django.http.multipartparser import parse_header
18from django.http.request import RawPostDataException
19from django.utils.datastructures import MultiValueDict
21from rest_framework import HTTP_HEADER_ENCODING, exceptions
22from rest_framework.settings import api_settings
25def is_form_media_type(media_type):
26 """
27 Return True if the media type is a valid form media type.
28 """
29 base_media_type, params = parse_header(media_type.encode(HTTP_HEADER_ENCODING))
30 return (base_media_type == 'application/x-www-form-urlencoded' or
31 base_media_type == 'multipart/form-data')
34class override_method:
35 """
36 A context manager that temporarily overrides the method on a request,
37 additionally setting the `view.request` attribute.
39 Usage:
41 with override_method(view, request, 'POST') as request:
42 ... # Do stuff with `view` and `request`
43 """
45 def __init__(self, view, request, method):
46 self.view = view
47 self.request = request
48 self.method = method
49 self.action = getattr(view, 'action', None)
51 def __enter__(self):
52 self.view.request = clone_request(self.request, self.method)
53 # For viewsets we also set the `.action` attribute.
54 action_map = getattr(self.view, 'action_map', {})
55 self.view.action = action_map.get(self.method.lower())
56 return self.view.request
58 def __exit__(self, *args, **kwarg):
59 self.view.request = self.request
60 self.view.action = self.action
63class WrappedAttributeError(Exception):
64 pass
67@contextmanager
68def wrap_attributeerrors():
69 """
70 Used to re-raise AttributeErrors caught during authentication, preventing
71 these errors from otherwise being handled by the attribute access protocol.
72 """
73 try:
74 yield
75 except AttributeError:
76 info = sys.exc_info()
77 exc = WrappedAttributeError(str(info[1]))
78 raise exc.with_traceback(info[2])
81class Empty:
82 """
83 Placeholder for unset attributes.
84 Cannot use `None`, as that may be a valid value.
85 """
86 pass
89def _hasattr(obj, name):
90 return not getattr(obj, name) is Empty
93def clone_request(request, method):
94 """
95 Internal helper method to clone a request, replacing with a different
96 HTTP method. Used for checking permissions against other methods.
97 """
98 ret = Request(request=request._request,
99 parsers=request.parsers,
100 authenticators=request.authenticators,
101 negotiator=request.negotiator,
102 parser_context=request.parser_context)
103 ret._data = request._data
104 ret._files = request._files
105 ret._full_data = request._full_data
106 ret._content_type = request._content_type
107 ret._stream = request._stream
108 ret.method = method
109 if hasattr(request, '_user'):
110 ret._user = request._user
111 if hasattr(request, '_auth'):
112 ret._auth = request._auth
113 if hasattr(request, '_authenticator'):
114 ret._authenticator = request._authenticator
115 if hasattr(request, 'accepted_renderer'):
116 ret.accepted_renderer = request.accepted_renderer
117 if hasattr(request, 'accepted_media_type'):
118 ret.accepted_media_type = request.accepted_media_type
119 if hasattr(request, 'version'):
120 ret.version = request.version
121 if hasattr(request, 'versioning_scheme'):
122 ret.versioning_scheme = request.versioning_scheme
123 return ret
126class ForcedAuthentication:
127 """
128 This authentication class is used if the test client or request factory
129 forcibly authenticated the request.
130 """
132 def __init__(self, force_user, force_token):
133 self.force_user = force_user
134 self.force_token = force_token
136 def authenticate(self, request):
137 return (self.force_user, self.force_token)
140class Request:
141 """
142 Wrapper allowing to enhance a standard `HttpRequest` instance.
144 Kwargs:
145 - request(HttpRequest). The original request instance.
146 - parsers(list/tuple). The parsers to use for parsing the
147 request content.
148 - authenticators(list/tuple). The authenticators used to try
149 authenticating the request's user.
150 """
152 def __init__(self, request, parsers=None, authenticators=None,
153 negotiator=None, parser_context=None):
154 assert isinstance(request, HttpRequest), (
155 'The `request` argument must be an instance of '
156 '`django.http.HttpRequest`, not `{}.{}`.'
157 .format(request.__class__.__module__, request.__class__.__name__)
158 )
160 self._request = request
161 self.parsers = parsers or ()
162 self.authenticators = authenticators or ()
163 self.negotiator = negotiator or self._default_negotiator()
164 self.parser_context = parser_context
165 self._data = Empty
166 self._files = Empty
167 self._full_data = Empty
168 self._content_type = Empty
169 self._stream = Empty
171 if self.parser_context is None: 171 ↛ 172line 171 didn't jump to line 172, because the condition on line 171 was never true
172 self.parser_context = {}
173 self.parser_context['request'] = self
174 self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET
176 force_user = getattr(request, '_force_auth_user', None)
177 force_token = getattr(request, '_force_auth_token', None)
178 if force_user is not None or force_token is not None: 178 ↛ 179line 178 didn't jump to line 179, because the condition on line 178 was never true
179 forced_auth = ForcedAuthentication(force_user, force_token)
180 self.authenticators = (forced_auth,)
182 def __repr__(self):
183 return '<%s.%s: %s %r>' % (
184 self.__class__.__module__,
185 self.__class__.__name__,
186 self.method,
187 self.get_full_path())
189 def _default_negotiator(self):
190 return api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS()
192 @property
193 def content_type(self):
194 meta = self._request.META
195 return meta.get('CONTENT_TYPE', meta.get('HTTP_CONTENT_TYPE', ''))
197 @property
198 def stream(self):
199 """
200 Returns an object that may be used to stream the request content.
201 """
202 if not _hasattr(self, '_stream'): 202 ↛ 204line 202 didn't jump to line 204, because the condition on line 202 was never false
203 self._load_stream()
204 return self._stream
206 @property
207 def query_params(self):
208 """
209 More semantically correct name for request.GET.
210 """
211 return self._request.GET
213 @property
214 def data(self):
215 if not _hasattr(self, '_full_data'): 215 ↛ 217line 215 didn't jump to line 217, because the condition on line 215 was never false
216 self._load_data_and_files()
217 return self._full_data
219 @property
220 def user(self):
221 """
222 Returns the user associated with the current request, as authenticated
223 by the authentication classes provided to the request.
224 """
225 if not hasattr(self, '_user'):
226 with wrap_attributeerrors():
227 self._authenticate()
228 return self._user
230 @user.setter
231 def user(self, value):
232 """
233 Sets the user on the current request. This is necessary to maintain
234 compatibility with django.contrib.auth where the user property is
235 set in the login and logout functions.
237 Note that we also set the user on Django's underlying `HttpRequest`
238 instance, ensuring that it is available to any middleware in the stack.
239 """
240 self._user = value
241 self._request.user = value
243 @property
244 def auth(self):
245 """
246 Returns any non-user authentication information associated with the
247 request, such as an authentication token.
248 """
249 if not hasattr(self, '_auth'):
250 with wrap_attributeerrors():
251 self._authenticate()
252 return self._auth
254 @auth.setter
255 def auth(self, value):
256 """
257 Sets any non-user authentication information associated with the
258 request, such as an authentication token.
259 """
260 self._auth = value
261 self._request.auth = value
263 @property
264 def successful_authenticator(self):
265 """
266 Return the instance of the authentication instance class that was used
267 to authenticate the request, or `None`.
268 """
269 if not hasattr(self, '_authenticator'): 269 ↛ 270line 269 didn't jump to line 270, because the condition on line 269 was never true
270 with wrap_attributeerrors():
271 self._authenticate()
272 return self._authenticator
274 def _load_data_and_files(self):
275 """
276 Parses the request content into `self.data`.
277 """
278 if not _hasattr(self, '_data'): 278 ↛ exitline 278 didn't return from function '_load_data_and_files', because the condition on line 278 was never false
279 self._data, self._files = self._parse()
280 if self._files:
281 self._full_data = self._data.copy()
282 self._full_data.update(self._files)
283 else:
284 self._full_data = self._data
286 # if a form media type, copy data & files refs to the underlying
287 # http request so that closable objects are handled appropriately.
288 if is_form_media_type(self.content_type):
289 self._request._post = self.POST
290 self._request._files = self.FILES
292 def _load_stream(self):
293 """
294 Return the content body of the request, as a stream.
295 """
296 meta = self._request.META
297 try:
298 content_length = int(
299 meta.get('CONTENT_LENGTH', meta.get('HTTP_CONTENT_LENGTH', 0))
300 )
301 except (ValueError, TypeError):
302 content_length = 0
304 if content_length == 0: 304 ↛ 305line 304 didn't jump to line 305, because the condition on line 304 was never true
305 self._stream = None
306 elif not self._request._read_started: 306 ↛ 309line 306 didn't jump to line 309, because the condition on line 306 was never false
307 self._stream = self._request
308 else:
309 self._stream = io.BytesIO(self.body)
311 def _supports_form_parsing(self):
312 """
313 Return True if this requests supports parsing form data.
314 """
315 form_media = (
316 'application/x-www-form-urlencoded',
317 'multipart/form-data'
318 )
319 return any(parser.media_type in form_media for parser in self.parsers)
321 def _parse(self):
322 """
323 Parse the request content, returning a two-tuple of (data, files)
325 May raise an `UnsupportedMediaType`, or `ParseError` exception.
326 """
327 media_type = self.content_type
328 try:
329 stream = self.stream
330 except RawPostDataException:
331 if not hasattr(self._request, '_post'):
332 raise
333 # If request.POST has been accessed in middleware, and a method='POST'
334 # request was made with 'multipart/form-data', then the request stream
335 # will already have been exhausted.
336 if self._supports_form_parsing():
337 return (self._request.POST, self._request.FILES)
338 stream = None
340 if stream is None or media_type is None: 340 ↛ 341line 340 didn't jump to line 341, because the condition on line 340 was never true
341 if media_type and is_form_media_type(media_type):
342 empty_data = QueryDict('', encoding=self._request._encoding)
343 else:
344 empty_data = {}
345 empty_files = MultiValueDict()
346 return (empty_data, empty_files)
348 parser = self.negotiator.select_parser(self, self.parsers)
350 if not parser: 350 ↛ 351line 350 didn't jump to line 351, because the condition on line 350 was never true
351 raise exceptions.UnsupportedMediaType(media_type)
353 try:
354 parsed = parser.parse(stream, media_type, self.parser_context)
355 except Exception:
356 # If we get an exception during parsing, fill in empty data and
357 # re-raise. Ensures we don't simply repeat the error when
358 # attempting to render the browsable renderer response, or when
359 # logging the request or similar.
360 self._data = QueryDict('', encoding=self._request._encoding)
361 self._files = MultiValueDict()
362 self._full_data = self._data
363 raise
365 # Parser classes may return the raw data, or a
366 # DataAndFiles object. Unpack the result as required.
367 try:
368 return (parsed.data, parsed.files)
369 except AttributeError:
370 empty_files = MultiValueDict()
371 return (parsed, empty_files)
373 def _authenticate(self):
374 """
375 Attempt to authenticate the request using each authentication instance
376 in turn.
377 """
378 for authenticator in self.authenticators:
379 try:
380 user_auth_tuple = authenticator.authenticate(self)
381 except exceptions.APIException:
382 self._not_authenticated()
383 raise
385 if user_auth_tuple is not None:
386 self._authenticator = authenticator
387 self.user, self.auth = user_auth_tuple
388 return
390 self._not_authenticated()
392 def _not_authenticated(self):
393 """
394 Set authenticator, user & authtoken representing an unauthenticated request.
396 Defaults are None, AnonymousUser & None.
397 """
398 self._authenticator = None
400 if api_settings.UNAUTHENTICATED_USER: 400 ↛ 403line 400 didn't jump to line 403, because the condition on line 400 was never false
401 self.user = api_settings.UNAUTHENTICATED_USER()
402 else:
403 self.user = None
405 if api_settings.UNAUTHENTICATED_TOKEN: 405 ↛ 406line 405 didn't jump to line 406, because the condition on line 405 was never true
406 self.auth = api_settings.UNAUTHENTICATED_TOKEN()
407 else:
408 self.auth = None
410 def __getattr__(self, attr):
411 """
412 If an attribute does not exist on this instance, then we also attempt
413 to proxy it to the underlying HttpRequest object.
414 """
415 try:
416 return getattr(self._request, attr)
417 except AttributeError:
418 return self.__getattribute__(attr)
420 @property
421 def DATA(self):
422 raise NotImplementedError(
423 '`request.DATA` has been deprecated in favor of `request.data` '
424 'since version 3.0, and has been fully removed as of version 3.2.'
425 )
427 @property
428 def POST(self):
429 # Ensure that request.POST uses our request parsing.
430 if not _hasattr(self, '_data'): 430 ↛ 431line 430 didn't jump to line 431, because the condition on line 430 was never true
431 self._load_data_and_files()
432 if is_form_media_type(self.content_type): 432 ↛ 434line 432 didn't jump to line 434, because the condition on line 432 was never false
433 return self._data
434 return QueryDict('', encoding=self._request._encoding)
436 @property
437 def FILES(self):
438 # Leave this one alone for backwards compat with Django's request.FILES
439 # Different from the other two cases, which are not valid property
440 # names on the WSGIRequest class.
441 if not _hasattr(self, '_files'): 441 ↛ 442line 441 didn't jump to line 442, because the condition on line 441 was never true
442 self._load_data_and_files()
443 return self._files
445 @property
446 def QUERY_PARAMS(self):
447 raise NotImplementedError(
448 '`request.QUERY_PARAMS` has been deprecated in favor of `request.query_params` '
449 'since version 3.0, and has been fully removed as of version 3.2.'
450 )
452 def force_plaintext_errors(self, value):
453 # Hack to allow our exception handler to force choice of
454 # plaintext or html error responses.
455 self._request.is_ajax = lambda: value