Beispiel #1
0
def _oauth10a_signature(
    consumer_token: Dict[str, Any],
    method: str,
    url: str,
    parameters: Dict[str, Any] = {},
    token: Optional[Dict[str, Any]] = None,
) -> bytes:
    """Calculates the HMAC-SHA1 OAuth 1.0a signature for the given request.

    See http://oauth.net/core/1.0a/#signing_process
    """
    parts = urllib.parse.urlparse(url)
    scheme, netloc, path = parts[:3]
    normalized_url = scheme.lower() + "://" + netloc.lower() + path

    base_elems = []
    base_elems.append(method.upper())
    base_elems.append(normalized_url)
    base_elems.append(
        "&".join(
            "%s=%s" % (k, _oauth_escape(str(v))) for k, v in sorted(parameters.items())
        )
    )

    base_string = "&".join(_oauth_escape(e) for e in base_elems)
    key_elems = [escape.utf8(urllib.parse.quote(consumer_token["secret"], safe="~"))]
    key_elems.append(
        escape.utf8(urllib.parse.quote(token["secret"], safe="~") if token else "")
    )
    key = b"&".join(key_elems)

    hash = hmac.new(key, escape.utf8(base_string), hashlib.sha1)
    return binascii.b2a_base64(hash.digest())[:-1]
Beispiel #2
0
def encode_username_password(username: Union[str, bytes],
                             password: Union[str, bytes]) -> bytes:
    """Encodes a username/password pair in the format used by HTTP auth.

    The return value is a byte string in the form ``username:password``.

    .. versionadded:: 5.1
    """
    if isinstance(username, unicode_type):
        username = unicodedata.normalize("NFC", username)
    if isinstance(password, unicode_type):
        password = unicodedata.normalize("NFC", password)
    return utf8(username) + b":" + utf8(password)
Beispiel #3
0
 def resolve(
         self,
         host: str,
         port: int,
         family: int = 0) -> "Generator[Any, Any, List[Tuple[int, Any]]]":
     # getHostByName doesn't accept IP addresses, so if the input
     # looks like an IP address just return it immediately.
     if twisted.internet.abstract.isIPAddress(host):
         resolved = host
         resolved_family = socket.AF_INET
     elif twisted.internet.abstract.isIPv6Address(host):
         resolved = host
         resolved_family = socket.AF_INET6
     else:
         deferred = self.resolver.getHostByName(utf8(host))
         fut = Future()  # type: Future[Any]
         deferred.addBoth(fut.set_result)
         resolved = yield fut
         if isinstance(resolved, failure.Failure):
             try:
                 resolved.raiseException()
             except twisted.names.error.DomainError as e:
                 raise IOError(e)
         elif twisted.internet.abstract.isIPAddress(resolved):
             resolved_family = socket.AF_INET
         elif twisted.internet.abstract.isIPv6Address(resolved):
             resolved_family = socket.AF_INET6
         else:
             resolved_family = socket.AF_UNSPEC
     if family != socket.AF_UNSPEC and family != resolved_family:
         raise Exception("Requested socket family %d but got %d" %
                         (family, resolved_family))
     result = [(typing.cast(int, resolved_family), (resolved, port))]
     return result
Beispiel #4
0
    async def get_authenticated_user(
        self,
        http_client: Optional[httpclient.AsyncHTTPClient] = None
    ) -> Dict[str, Any]:
        """Gets the OAuth authorized user and access token.

        This method should be called from the handler for your
        OAuth callback URL to complete the registration process. We run the
        callback with the authenticated user dictionary.  This dictionary
        will contain an ``access_key`` which can be used to make authorized
        requests to this service on behalf of the user.  The dictionary will
        also contain other fields such as ``name``, depending on the service
        used.

        .. versionchanged:: 6.0

           The ``callback`` argument was removed. Use the returned
           awaitable object instead.
        """
        handler = cast(RequestHandler, self)
        request_key = escape.utf8(handler.get_argument("oauth_token"))
        oauth_verifier = handler.get_argument("oauth_verifier", None)
        request_cookie = handler.get_cookie("_oauth_request_token")
        if not request_cookie:
            raise AuthError("Missing OAuth request token cookie")
        handler.clear_cookie("_oauth_request_token")
        cookie_key, cookie_secret = [
            base64.b64decode(escape.utf8(i)) for i in request_cookie.split("|")
        ]
        if cookie_key != request_key:
            raise AuthError("Request token does not match cookie")
        token = dict(
            key=cookie_key,
            secret=cookie_secret)  # type: Dict[str, Union[str, bytes]]
        if oauth_verifier:
            token["verifier"] = oauth_verifier
        if http_client is None:
            http_client = self.get_auth_http_client()
        assert http_client is not None
        response = await http_client.fetch(self._oauth_access_token_url(token))
        access_token = _oauth_parse_response(response.body)
        user = await self._oauth_get_user_future(access_token)
        if not user:
            raise AuthError("Error getting user")
        user["access_token"] = access_token
        return user
Beispiel #5
0
    def generate(self, writer: "_CodeWriter") -> None:
        value = self.value

        # Compress whitespace if requested, with a crude heuristic to avoid
        # altering preformatted whitespace.
        if "<pre>" not in value:
            value = filter_whitespace(self.whitespace, value)

        if value:
            writer.write_line("_tt_append(%r)" % escape.utf8(value), self.line)
Beispiel #6
0
 def _on_request_token(
     self,
     authorize_url: str,
     callback_uri: Optional[str],
     response: httpclient.HTTPResponse,
 ) -> None:
     handler = cast(RequestHandler, self)
     request_token = _oauth_parse_response(response.body)
     data = (base64.b64encode(escape.utf8(request_token["key"])) + b"|" +
             base64.b64encode(escape.utf8(request_token["secret"])))
     handler.set_cookie("_oauth_request_token", data)
     args = dict(oauth_token=request_token["key"])
     if callback_uri == "oob":
         handler.finish(authorize_url + "?" + urllib.parse.urlencode(args))
         return
     elif callback_uri:
         args["oauth_callback"] = urllib.parse.urljoin(
             handler.request.full_url(), callback_uri)
     handler.redirect(authorize_url + "?" + urllib.parse.urlencode(args))
Beispiel #7
0
 def reverse(self, *args: Any) -> Optional[str]:
     if self._path is None:
         raise ValueError("Cannot reverse url regex " + self.regex.pattern)
     assert len(args) == self._group_count, ("required number of arguments "
                                             "not found")
     if not len(args):
         return self._path
     converted_args = []
     for a in args:
         if not isinstance(a, (unicode_type, bytes)):
             a = str(a)
         converted_args.append(url_escape(utf8(a), plus=False))
     return self._path % tuple(converted_args)
Beispiel #8
0
 def _format_chunk(self, chunk: bytes) -> bytes:
     if self._expected_content_remaining is not None:
         self._expected_content_remaining -= len(chunk)
         if self._expected_content_remaining < 0:
             # Close the stream now to stop further framing errors.
             self.stream.close()
             raise httputil.HTTPOutputError(
                 "Tried to write more data than Content-Length")
     if self._chunking_output and chunk:
         # Don't write out empty chunks because that means END-OF-STREAM
         # with chunked encoding
         return utf8("%x" % len(chunk)) + b"\r\n" + chunk + b"\r\n"
     else:
         return chunk
Beispiel #9
0
def parse_body_arguments(
    content_type: str,
    body: bytes,
    arguments: Dict[str, List[bytes]],
    files: Dict[str, List[HTTPFile]],
    headers: Optional[HTTPHeaders] = None,
) -> None:
    """Parses a form request body.

    Supports ``application/x-www-form-urlencoded`` and
    ``multipart/form-data``.  The ``content_type`` parameter should be
    a string and ``body`` should be a byte string.  The ``arguments``
    and ``files`` parameters are dictionaries that will be updated
    with the parsed contents.
    """
    if content_type.startswith("application/x-www-form-urlencoded"):
        if headers and "Content-Encoding" in headers:
            gen_log.warning("Unsupported Content-Encoding: %s",
                            headers["Content-Encoding"])
            return
        try:
            # real charset decoding will happen in RequestHandler.decode_argument()
            uri_arguments = parse_qs_bytes(body, keep_blank_values=True)
        except Exception as e:
            gen_log.warning("Invalid x-www-form-urlencoded body: %s", e)
            uri_arguments = {}
        for name, values in uri_arguments.items():
            if values:
                arguments.setdefault(name, []).extend(values)
    elif content_type.startswith("multipart/form-data"):
        if headers and "Content-Encoding" in headers:
            gen_log.warning("Unsupported Content-Encoding: %s",
                            headers["Content-Encoding"])
            return
        try:
            fields = content_type.split(";")
            for field in fields:
                k, sep, v = field.strip().partition("=")
                if k == "boundary" and v:
                    parse_multipart_form_data(utf8(v), body, arguments, files)
                    break
            else:
                raise ValueError("multipart boundary not found")
        except Exception as e:
            gen_log.warning("Invalid multipart/form-data: %s", e)
Beispiel #10
0
    def _curl_setup_request(
        self,
        curl: pycurl.Curl,
        request: HTTPRequest,
        buffer: BytesIO,
        headers: httputil.HTTPHeaders,
    ) -> None:
        curl.setopt(pycurl.URL, native_str(request.url))

        # libcurl's magic "Expect: 100-continue" behavior causes delays
        # with servers that don't support it (which include, among others,
        # Google's OpenID endpoint).  Additionally, this behavior has
        # a bug in conjunction with the curl_multi_socket_action API
        # (https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3039744&group_id=976),
        # which increases the delays.  It's more trouble than it's worth,
        # so just turn off the feature (yes, setting Expect: to an empty
        # value is the official way to disable this)
        if "Expect" not in request.headers:
            request.headers["Expect"] = ""

        # libcurl adds Pragma: no-cache by default; disable that too
        if "Pragma" not in request.headers:
            request.headers["Pragma"] = ""

        curl.setopt(
            pycurl.HTTPHEADER,
            [
                "%s: %s" % (native_str(k), native_str(v))
                for k, v in request.headers.get_all()
            ],
        )

        curl.setopt(
            pycurl.HEADERFUNCTION,
            functools.partial(self._curl_header_callback, headers,
                              request.header_callback),
        )
        if request.streaming_callback:

            def write_function(b: Union[bytes, bytearray]) -> int:
                assert request.streaming_callback is not None
                self.io_loop.add_callback(request.streaming_callback, b)
                return len(b)

        else:
            write_function = buffer.write
        curl.setopt(pycurl.WRITEFUNCTION, write_function)
        curl.setopt(pycurl.FOLLOWLOCATION, request.follow_redirects)
        curl.setopt(pycurl.MAXREDIRS, request.max_redirects)
        assert request.connect_timeout is not None
        curl.setopt(pycurl.CONNECTTIMEOUT_MS,
                    int(1000 * request.connect_timeout))
        assert request.request_timeout is not None
        curl.setopt(pycurl.TIMEOUT_MS, int(1000 * request.request_timeout))
        if request.user_agent:
            curl.setopt(pycurl.USERAGENT, native_str(request.user_agent))
        else:
            curl.setopt(pycurl.USERAGENT, "Mozilla/5.0 (compatible; pycurl)")
        if request.network_interface:
            curl.setopt(pycurl.INTERFACE, request.network_interface)
        if request.decompress_response:
            curl.setopt(pycurl.ENCODING, "gzip,deflate")
        else:
            curl.setopt(pycurl.ENCODING, None)
        if request.proxy_host and request.proxy_port:
            curl.setopt(pycurl.PROXY, request.proxy_host)
            curl.setopt(pycurl.PROXYPORT, request.proxy_port)
            if request.proxy_username:
                assert request.proxy_password is not None
                credentials = httputil.encode_username_password(
                    request.proxy_username, request.proxy_password)
                curl.setopt(pycurl.PROXYUSERPWD, credentials)

            if request.proxy_auth_mode is None or request.proxy_auth_mode == "basic":
                curl.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_BASIC)
            elif request.proxy_auth_mode == "digest":
                curl.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_DIGEST)
            else:
                raise ValueError("Unsupported proxy_auth_mode %s" %
                                 request.proxy_auth_mode)
        else:
            try:
                curl.unsetopt(pycurl.PROXY)
            except TypeError:  # not supported, disable proxy
                curl.setopt(pycurl.PROXY, "")
            curl.unsetopt(pycurl.PROXYUSERPWD)
        if request.validate_cert:
            curl.setopt(pycurl.SSL_VERIFYPEER, 1)
            curl.setopt(pycurl.SSL_VERIFYHOST, 2)
        else:
            curl.setopt(pycurl.SSL_VERIFYPEER, 0)
            curl.setopt(pycurl.SSL_VERIFYHOST, 0)
        if request.ca_certs is not None:
            curl.setopt(pycurl.CAINFO, request.ca_certs)
        else:
            # There is no way to restore pycurl.CAINFO to its default value
            # (Using unsetopt makes it reject all certificates).
            # I don't see any way to read the default value from python so it
            # can be restored later.  We'll have to just leave CAINFO untouched
            # if no ca_certs file was specified, and require that if any
            # request uses a custom ca_certs file, they all must.
            pass

        if request.allow_ipv6 is False:
            # Curl behaves reasonably when DNS resolution gives an ipv6 address
            # that we can't reach, so allow ipv6 unless the user asks to disable.
            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4)
        else:
            curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER)

        # Set the request method through curl's irritating interface which makes
        # up names for almost every single method
        curl_options = {
            "GET": pycurl.HTTPGET,
            "POST": pycurl.POST,
            "PUT": pycurl.UPLOAD,
            "HEAD": pycurl.NOBODY,
        }
        custom_methods = set(["DELETE", "OPTIONS", "PATCH"])
        for o in curl_options.values():
            curl.setopt(o, False)
        if request.method in curl_options:
            curl.unsetopt(pycurl.CUSTOMREQUEST)
            curl.setopt(curl_options[request.method], True)
        elif request.allow_nonstandard_methods or request.method in custom_methods:
            curl.setopt(pycurl.CUSTOMREQUEST, request.method)
        else:
            raise KeyError("unknown method " + request.method)

        body_expected = request.method in ("POST", "PATCH", "PUT")
        body_present = request.body is not None
        if not request.allow_nonstandard_methods:
            # Some HTTP methods nearly always have bodies while others
            # almost never do. Fail in this case unless the user has
            # opted out of sanity checks with allow_nonstandard_methods.
            if (body_expected and not body_present) or (body_present
                                                        and not body_expected):
                raise ValueError(
                    "Body must %sbe None for method %s (unless "
                    "allow_nonstandard_methods is true)" %
                    ("not " if body_expected else "", request.method))

        if body_expected or body_present:
            if request.method == "GET":
                # Even with `allow_nonstandard_methods` we disallow
                # GET with a body (because libcurl doesn't allow it
                # unless we use CUSTOMREQUEST). While the spec doesn't
                # forbid clients from sending a body, it arguably
                # disallows the server from doing anything with them.
                raise ValueError("Body must be None for GET request")
            request_buffer = BytesIO(utf8(request.body or ""))

            def ioctl(cmd: int) -> None:
                if cmd == curl.IOCMD_RESTARTREAD:  # type: ignore
                    request_buffer.seek(0)

            curl.setopt(pycurl.READFUNCTION, request_buffer.read)
            curl.setopt(pycurl.IOCTLFUNCTION, ioctl)
            if request.method == "POST":
                curl.setopt(pycurl.POSTFIELDSIZE, len(request.body or ""))
            else:
                curl.setopt(pycurl.UPLOAD, True)
                curl.setopt(pycurl.INFILESIZE, len(request.body or ""))

        if request.auth_username is not None:
            assert request.auth_password is not None
            if request.auth_mode is None or request.auth_mode == "basic":
                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)
            elif request.auth_mode == "digest":
                curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_DIGEST)
            else:
                raise ValueError("Unsupported auth_mode %s" %
                                 request.auth_mode)

            userpwd = httputil.encode_username_password(
                request.auth_username, request.auth_password)
            curl.setopt(pycurl.USERPWD, userpwd)
            curl_log.debug(
                "%s %s (username: %r)",
                request.method,
                request.url,
                request.auth_username,
            )
        else:
            curl.unsetopt(pycurl.USERPWD)
            curl_log.debug("%s %s", request.method, request.url)

        if request.client_cert is not None:
            curl.setopt(pycurl.SSLCERT, request.client_cert)

        if request.client_key is not None:
            curl.setopt(pycurl.SSLKEY, request.client_key)

        if request.ssl_options is not None:
            raise ValueError("ssl_options not supported in curl_httpclient")

        if threading.active_count() > 1:
            # libcurl/pycurl is not thread-safe by default.  When multiple threads
            # are used, signals should be disabled.  This has the side effect
            # of disabling DNS timeouts in some environments (when libcurl is
            # not linked against ares), so we don't do it when there is only one
            # thread.  Applications that use many short-lived threads may need
            # to set NOSIGNAL manually in a prepare_curl_callback since
            # there may not be any other threads running at the time we call
            # threading.activeCount.
            curl.setopt(pycurl.NOSIGNAL, 1)
        if request.prepare_curl_callback is not None:
            request.prepare_curl_callback(curl)
Beispiel #11
0
 def write_headers(
     self,
     start_line: Union[httputil.RequestStartLine,
                       httputil.ResponseStartLine],
     headers: httputil.HTTPHeaders,
     chunk: Optional[bytes] = None,
 ) -> "Future[None]":
     """Implements `.HTTPConnection.write_headers`."""
     lines = []
     if self.is_client:
         assert isinstance(start_line, httputil.RequestStartLine)
         self._request_start_line = start_line
         lines.append(
             utf8("%s %s HTTP/1.1" % (start_line[0], start_line[1])))
         # Client requests with a non-empty body must have either a
         # Content-Length or a Transfer-Encoding.
         self._chunking_output = (
             start_line.method in ("POST", "PUT", "PATCH")
             and "Content-Length" not in headers
             and ("Transfer-Encoding" not in headers
                  or headers["Transfer-Encoding"] == "chunked"))
     else:
         assert isinstance(start_line, httputil.ResponseStartLine)
         assert self._request_start_line is not None
         assert self._request_headers is not None
         self._response_start_line = start_line
         lines.append(
             utf8("HTTP/1.1 %d %s" % (start_line[1], start_line[2])))
         self._chunking_output = (
             # TODO: should this use
             # self._request_start_line.version or
             # start_line.version?
             self._request_start_line.version == "HTTP/1.1"
             # 1xx, 204 and 304 responses have no body (not even a zero-length
             # body), and so should not have either Content-Length or
             # Transfer-Encoding headers.
             and start_line.code not in (204, 304) and
             (start_line.code < 100 or start_line.code >= 200)
             # No need to chunk the output if a Content-Length is specified.
             and "Content-Length" not in headers
             # Applications are discouraged from touching Transfer-Encoding,
             # but if they do, leave it alone.
             and "Transfer-Encoding" not in headers)
         # If connection to a 1.1 client will be closed, inform client
         if (self._request_start_line.version == "HTTP/1.1"
                 and self._disconnect_on_finish):
             headers["Connection"] = "close"
         # If a 1.0 client asked for keep-alive, add the header.
         if (self._request_start_line.version == "HTTP/1.0"
                 and self._request_headers.get("Connection",
                                               "").lower() == "keep-alive"):
             headers["Connection"] = "Keep-Alive"
     if self._chunking_output:
         headers["Transfer-Encoding"] = "chunked"
     if not self.is_client and (self._request_start_line.method == "HEAD"
                                or cast(httputil.ResponseStartLine,
                                        start_line).code == 304):
         self._expected_content_remaining = 0
     elif "Content-Length" in headers:
         self._expected_content_remaining = int(headers["Content-Length"])
     else:
         self._expected_content_remaining = None
     # TODO: headers are supposed to be of type str, but we still have some
     # cases that let bytes slip through. Remove these native_str calls when those
     # are fixed.
     header_lines = (native_str(n) + ": " + native_str(v)
                     for n, v in headers.get_all())
     lines.extend(l.encode("latin1") for l in header_lines)
     for line in lines:
         if b"\n" in line:
             raise ValueError("Newline in header: " + repr(line))
     future = None
     if self.stream.closed():
         future = self._write_future = Future()
         future.set_exception(iostream.StreamClosedError())
         future.exception()
     else:
         future = self._write_future = Future()
         data = b"\r\n".join(lines) + b"\r\n\r\n"
         if chunk:
             data += self._format_chunk(chunk)
         self._pending_write = self.stream.write(data)
         future_add_done_callback(self._pending_write,
                                  self._on_write_complete)
     return future
Beispiel #12
0
 def body(self, value: Union[bytes, str]) -> None:
     self._body = utf8(value)