Merge pull request #7 from channelcat/master
merge upstreaming master branch
This commit is contained in:
		| @@ -12,7 +12,7 @@ _address_dict = { | ||||
|     'Windows': ('localhost', 514), | ||||
|     'Darwin': '/var/run/syslog', | ||||
|     'Linux': '/dev/log', | ||||
|     'FreeBSD': '/dev/log' | ||||
|     'FreeBSD': '/var/run/log' | ||||
| } | ||||
|  | ||||
| LOGGING = { | ||||
|   | ||||
| @@ -209,25 +209,43 @@ class Unauthorized(SanicException): | ||||
|     Unauthorized exception (401 HTTP status code). | ||||
|  | ||||
|     :param scheme: Name of the authentication scheme to be used. | ||||
|     :param realm: Description of the protected area. (optional) | ||||
|     :param challenge: A dict containing values to add to the WWW-Authenticate | ||||
|                       header that is generated. This is especially useful when | ||||
|                       dealing with the Digest scheme. (optional) | ||||
|         header that is generated. This is especially useful when dealing with | ||||
|         the Digest scheme. (optional) | ||||
|  | ||||
|     Examples:: | ||||
|  | ||||
|         # With a Basic auth-scheme, realm MUST be present: | ||||
|         challenge = {"realm": "Restricted Area"} | ||||
|         raise Unauthorized("Auth required.", "Basic", challenge) | ||||
|  | ||||
|         # With a Digest auth-scheme, things are a bit more complicated: | ||||
|         challenge = { | ||||
|             "realm": "Restricted Area", | ||||
|             "qop": "auth, auth-int", | ||||
|             "algorithm": "MD5", | ||||
|             "nonce": "abcdef", | ||||
|             "opaque": "zyxwvu" | ||||
|         } | ||||
|         raise Unauthorized("Auth required.", "Digest", challenge) | ||||
|  | ||||
|         # With a Bearer auth-scheme, realm is optional: | ||||
|         challenge = {"realm": "Restricted Area"} | ||||
|         raise Unauthorized("Auth required.", "Bearer", challenge) | ||||
|     """ | ||||
|     pass | ||||
|  | ||||
|     def __init__(self, message, scheme, realm="", challenge=None): | ||||
|     def __init__(self, message, scheme, challenge=None): | ||||
|         super().__init__(message) | ||||
|  | ||||
|         adds = "" | ||||
|         chal = "" | ||||
|  | ||||
|         if challenge is not None: | ||||
|             values = ["{!s}={!r}".format(k, v) for k, v in challenge.items()] | ||||
|             adds = ', '.join(values) | ||||
|             adds = ', {}'.format(adds) | ||||
|             chal = ', '.join(values) | ||||
|  | ||||
|         self.headers = { | ||||
|             "WWW-Authenticate": "{} realm='{}'{}".format(scheme, realm, adds) | ||||
|             "WWW-Authenticate": "{} {}".format(scheme, chal).rstrip() | ||||
|         } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -45,7 +45,7 @@ class Request(dict): | ||||
|     __slots__ = ( | ||||
|         'app', 'headers', 'version', 'method', '_cookies', 'transport', | ||||
|         'body', 'parsed_json', 'parsed_args', 'parsed_form', 'parsed_files', | ||||
|         '_ip', '_parsed_url', 'uri_template', 'stream' | ||||
|         '_ip', '_parsed_url', 'uri_template', 'stream', '_remote_addr' | ||||
|     ) | ||||
|  | ||||
|     def __init__(self, url_bytes, headers, version, method, transport): | ||||
| @@ -142,7 +142,7 @@ class Request(dict): | ||||
|     @property | ||||
|     def cookies(self): | ||||
|         if self._cookies is None: | ||||
|             cookie = self.headers.get('Cookie') or self.headers.get('cookie') | ||||
|             cookie = self.headers.get('Cookie') | ||||
|             if cookie is not None: | ||||
|                 cookies = SimpleCookie() | ||||
|                 cookies.load(cookie) | ||||
| @@ -159,6 +159,25 @@ class Request(dict): | ||||
|                         (None, None)) | ||||
|         return self._ip | ||||
|  | ||||
|     @property | ||||
|     def remote_addr(self): | ||||
|         """Attempt to return the original client ip based on X-Forwarded-For. | ||||
|  | ||||
|         :return: original client ip. | ||||
|         """ | ||||
|         if not hasattr(self, '_remote_addr'): | ||||
|             forwarded_for = self.headers.get('X-Forwarded-For', '').split(',') | ||||
|             remote_addrs = [ | ||||
|                 addr for addr in [ | ||||
|                     addr.strip() for addr in forwarded_for | ||||
|                 ] if addr | ||||
|             ] | ||||
|             if len(remote_addrs) > 0: | ||||
|                 self._remote_addr = remote_addrs[0] | ||||
|             else: | ||||
|                 self._remote_addr = '' | ||||
|         return self._remote_addr | ||||
|  | ||||
|     @property | ||||
|     def scheme(self): | ||||
|         if self.app.websocket_enabled \ | ||||
|   | ||||
| @@ -33,17 +33,22 @@ def exception_app(): | ||||
|  | ||||
|     @app.route('/401/basic') | ||||
|     def handler_401_basic(request): | ||||
|         raise Unauthorized("Unauthorized", "Basic", "Sanic") | ||||
|         raise Unauthorized("Unauthorized", "Basic", {"realm": "Sanic"}) | ||||
|  | ||||
|     @app.route('/401/digest') | ||||
|     def handler_401_digest(request): | ||||
|         challenge = { | ||||
|             "realm": "Sanic", | ||||
|             "qop": "auth, auth-int", | ||||
|             "algorithm": "MD5", | ||||
|             "nonce": "abcdef", | ||||
|             "opaque": "zyxwvu", | ||||
|         } | ||||
|         raise Unauthorized("Unauthorized", "Digest", "Sanic", challenge) | ||||
|         raise Unauthorized("Unauthorized", "Digest", challenge) | ||||
|  | ||||
|     @app.route('/401/bearer') | ||||
|     def handler_401_bearer(request): | ||||
|         raise Unauthorized("Unauthorized", "Bearer") | ||||
|  | ||||
|     @app.route('/invalid') | ||||
|     def handler_invalid(request): | ||||
| @@ -136,6 +141,10 @@ def test_unauthorized_exception(exception_app): | ||||
|     assert "nonce='abcdef'" in auth_header | ||||
|     assert "opaque='zyxwvu'" in auth_header | ||||
|  | ||||
|     request, response = exception_app.test_client.get('/401/bearer') | ||||
|     assert response.status == 401 | ||||
|     assert response.headers.get('WWW-Authenticate') == "Bearer" | ||||
|  | ||||
|  | ||||
| def test_handled_unhandled_exception(exception_app): | ||||
|     """Test that an exception not built into sanic is handled""" | ||||
|   | ||||
| @@ -211,6 +211,32 @@ def test_content_type(): | ||||
|     assert response.text == 'application/json' | ||||
|  | ||||
|  | ||||
| def test_remote_addr(): | ||||
|     app = Sanic('test_content_type') | ||||
|  | ||||
|     @app.route('/') | ||||
|     async def handler(request): | ||||
|         return text(request.remote_addr) | ||||
|  | ||||
|     headers = { | ||||
|         'X-Forwarded-For': '127.0.0.1, 127.0.1.2' | ||||
|     } | ||||
|     request, response = app.test_client.get('/', headers=headers) | ||||
|     assert request.remote_addr == '127.0.0.1' | ||||
|     assert response.text == '127.0.0.1' | ||||
|  | ||||
|     request, response = app.test_client.get('/') | ||||
|     assert request.remote_addr == '' | ||||
|     assert response.text == '' | ||||
|  | ||||
|     headers = { | ||||
|         'X-Forwarded-For': '127.0.0.1, ,   ,,127.0.1.2' | ||||
|     } | ||||
|     request, response = app.test_client.get('/', headers=headers) | ||||
|     assert request.remote_addr == '127.0.0.1' | ||||
|     assert response.text == '127.0.0.1' | ||||
|  | ||||
|  | ||||
| def test_match_info(): | ||||
|     app = Sanic('test_match_info') | ||||
|  | ||||
| @@ -259,6 +285,7 @@ def test_post_form_urlencoded(): | ||||
|  | ||||
|     assert request.form.get('test') == 'OK' | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize( | ||||
|     'payload', [ | ||||
|         '------sanic\r\n' \ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 7
					7