def test_collapse_slashes(self): self.assertEqual(collapse_slashes('/'), '/') self.assertEqual(collapse_slashes('/foo/'), '/foo/') self.assertEqual(collapse_slashes('/foo'), '/foo') self.assertEqual(collapse_slashes('//'), '/') self.assertEqual(collapse_slashes('///'), '/') self.assertEqual(collapse_slashes('///foo//////bar//'), '/foo/bar/') self.assertEqual(collapse_slashes('///foo// ///bar//'), '/foo/ /bar/')
def get(self, request): """Invoked by the HTTP server with a single path argument. The HTTP server should pass the path argument verbatim, without any transformations or decoding. Access control mechanism should work on user visible paths, not paths after internal rewrites performed by the server. At the moment, the path is allowed to contain a query part, which is ignored (this is because nginx does not expose encoded path without the query part). The method follows be conservative in what you accept principle. The path should be absolute and normalized, without fragment id, otherwise access is denied. Browsers in normal operations perform path normalization and do not send fragment id. Multiple consecutive '/' separators are permitted, because these are not normalized by browsers, and are used by legitimate applications. Paths with '/./' and '/../', should not be normally sent by browsers and can be a sign of something suspicious happening. It is extremely important that wwwhisper does not perform any path transformations that are not be compatible with transformations done by the HTTP server. """ encoded_path = self._extract_encoded_path_argument(request) if encoded_path is None: return http.HttpResponseBadRequest( "Auth request should have 'path' argument.") # Do not allow requests that contain the 'User' header. The # header is passed to backends and must be guaranteed to be # set by wwwhisper. # This check should already be performed by HTTP server. if 'HTTP_USER' in request.META: return http.HttpResponseBadRequest( "Client can not set the 'User' header") debug_msg = "Auth request to '%s'" % (encoded_path) path_validation_error = None if url_utils.contains_fragment(encoded_path): path_validation_error = "Path should not include fragment ('#')" else: stripped_path = url_utils.strip_query(encoded_path) decoded_path = url_utils.decode(stripped_path) decoded_path = url_utils.collapse_slashes(decoded_path) if not url_utils.is_canonical(decoded_path): path_validation_error = 'Path should be absolute and ' \ 'normalized (starting with / without /../ or /./ or //).' if path_validation_error is not None: logger.debug('%s: incorrect path.' % (debug_msg)) return http.HttpResponseBadRequest(path_validation_error) user = _get_user(request) location = request.site.locations.find_location(decoded_path) if user is not None: debug_msg += " by '%s'" % (user.email) respone = None if location is not None and location.can_access(user): logger.debug('%s: access granted.' % (debug_msg)) response = http.HttpResponseOK('Access granted.') else: logger.debug('%s: access denied.' % (debug_msg)) response = http.HttpResponseNotAuthorized( _html_or_none(request, 'not_authorized.html', {'email': user.email})) response['User'] = user.email return response if (location is not None and location.open_access_granted() and not location.open_access_requires_login()): logger.debug('%s: authentication not required, access granted.' % (debug_msg)) return http.HttpResponseOK('Access granted.') logger.debug('%s: user not authenticated.' % (debug_msg)) return http.HttpResponseNotAuthenticated( _html_or_none(request, 'login.html', request.site.skin()))
def get(self, request): """Invoked by the HTTP server with a single path argument. The HTTP server should pass the path argument verbatim, without any transformations or decoding. Access control mechanism should work on user visible paths, not paths after internal rewrites performed by the server. At the moment, the path is allowed to contain a query part, which is ignored (this is because nginx does not expose encoded path without the query part). The method follows be conservative in what you accept principle. The path should be absolute and normalized, without fragment id, otherwise access is denied. Browsers in normal operations perform path normalization and do not send fragment id. Multiple consecutive '/' separators are permitted, because these are not normalized by browsers, and are used by legitimate applications. Paths with '/./' and '/../', should not be normally sent by browsers and can be a sign of something suspicious happening. It is extremely important that wwwhisper does not perform any path transformations that are not be compatible with transformations done by the HTTP server. """ encoded_path = self._extract_encoded_path_argument(request) if encoded_path is None: return http.HttpResponseBadRequest( "Auth request should have 'path' argument.") # Do not allow requests that contain the 'User' header. The # header is passed to backends and must be guaranteed to be # set by wwwhisper. # This check should already be performed by HTTP server. if 'HTTP_USER' in request.META: return http.HttpResponseBadRequest( "Client can not set the 'User' header") debug_msg = "Auth request to '%s'" % (encoded_path) path_validation_error = None if url_utils.contains_fragment(encoded_path): path_validation_error = "Path should not include fragment ('#')" else: stripped_path = url_utils.strip_query(encoded_path) decoded_path = url_utils.decode(stripped_path) decoded_path = url_utils.collapse_slashes(decoded_path) if not url_utils.is_canonical(decoded_path): path_validation_error = 'Path should be absolute and ' \ 'normalized (starting with / without /../ or /./ or //).' if path_validation_error is not None: logger.debug('%s: incorrect path.' % (debug_msg)) return http.HttpResponseBadRequest(path_validation_error) user = _get_user(request) location = request.site.locations.find_location(decoded_path) if user is not None: debug_msg += " by '%s'" % (user.email) respone = None if location is not None and location.can_access(user): logger.debug('%s: access granted.' % (debug_msg)) response = http.HttpResponseOK('Access granted.') else: logger.debug('%s: access denied.' % (debug_msg)) response = http.HttpResponseNotAuthorized( _html_or_none(request, 'not_authorized.html', {'email' : user.email})) response['User'] = user.email return response if (location is not None and location.open_access_granted() and not location.open_access_requires_login()): logger.debug('%s: authentication not required, access granted.' % (debug_msg)) return http.HttpResponseOK('Access granted.') logger.debug('%s: user not authenticated.' % (debug_msg)) return http.HttpResponseNotAuthenticated( _html_or_none(request, 'login.html', request.site.skin()))