Esempio n. 1
0
def _switch_hosts(request, new_endpoint, use_new_scheme=True):
    new_endpoint_components = urlsplit(new_endpoint)
    original_endpoint = request.url
    original_endpoint_components = urlsplit(original_endpoint)
    scheme = original_endpoint_components.scheme
    if use_new_scheme:
        scheme = new_endpoint_components.scheme
    final_endpoint_components = (scheme, new_endpoint_components.netloc,
                                 original_endpoint_components.path,
                                 original_endpoint_components.query, '')
    final_endpoint = urlunsplit(final_endpoint_components)
    logger.debug('Updating URI from %s to %s' % (request.url, final_endpoint))
    request.url = final_endpoint
Esempio n. 2
0
 def calc_signature(self, request, params):
     logger.debug("Calculating signature using v2 auth.")
     split = urlsplit(request.url)
     path = split.path
     if len(path) == 0:
         path = '/'
     string_to_sign = '%s\n%s\n%s\n' % (request.method,
                                        split.netloc,
                                        path)
     lhmac = hmac.new(self.credentials.secret_key.encode('utf-8'),
                      digestmod=sha256)
     pairs = []
     for key in sorted(params):
         # Any previous signature should not be a part of this
         # one, so we skip that particular key. This prevents
         # issues during retries.
         if key == 'Signature':
             continue
         value = six.text_type(params[key])
         pairs.append(quote(key.encode('utf-8'), safe='') + '=' +
                      quote(value.encode('utf-8'), safe='-_~'))
     qs = '&'.join(pairs)
     string_to_sign += qs
     logger.debug('String to sign: %s', string_to_sign)
     lhmac.update(string_to_sign.encode('utf-8'))
     b64 = base64.b64encode(lhmac.digest()).strip().decode('utf-8')
     return (qs, b64)
Esempio n. 3
0
    def _inject_signature(self, request, signature):
        query_dict = {}
        query_dict['AWSAccessKeyId'] = self.credentials.access_key
        query_dict['Signature'] = signature

        for header_key in request.headers:
            lk = header_key.lower()
            # For query string requests, Expires is used instead of the
            # Date header.
            if header_key == 'Date':
                query_dict['Expires'] = request.headers['Date']
            # We only want to include relevant headers in the query string.
            # These can be anything that starts with x-amz, is Content-MD5,
            # or is Content-Type.
            elif lk.startswith('x-amz-') or lk in ['content-md5',
                                                   'content-type']:
                query_dict[lk] = request.headers[lk]
        # Combine all of the identified headers into an encoded
        # query string
        new_query_string = percent_encode_sequence(query_dict)

        # Create a new url with the presigned url.
        p = urlsplit(request.url)
        if p[3]:
            # If there was a pre-existing query string, we should
            # add that back before injecting the new query string.
            new_query_string ='%s&%s' % (p[3], new_query_string)
        new_url_parts = (p[0], p[1], p[2], new_query_string, p[4])
        request.url = urlunsplit(new_url_parts)
Esempio n. 4
0
 def canonical_query_string(self, request):
     # The query string can come from two parts.  One is the
     # params attribute of the request.  The other is from the request
     # url (in which case we have to re-split the url into its components
     # and parse out the query string component).
     if request.params:
         return self._canonical_query_string_params(request.params)
     else:
         return self._canonical_query_string_url(urlsplit(request.url))
Esempio n. 5
0
 def add_auth(self, request):
     if self.credentials is None:
         raise NoCredentialsError
     logger.debug("Calculating signature using hmacv1 auth.")
     split = urlsplit(request.url)
     logger.debug('HTTP request method: %s', request.method)
     signature = self.get_signature(request.method, split,
                                    request.headers,
                                    auth_path=request.auth_path)
     self._inject_signature(request, signature)
Esempio n. 6
0
 def _modify_request_before_signing(self, request):
     # Note that we're not including X-Amz-Signature.
     # From the docs: "The Canonical Query String must include all the query
     # parameters from the preceding table except for X-Amz-Signature.
     signed_headers = self.signed_headers(self.headers_to_sign(request))
     auth_params = {
         'X-Amz-Algorithm': 'AWS4-HMAC-SHA256',
         'X-Amz-Credential': self.scope(request),
         'X-Amz-Date': request.context['timestamp'],
         'X-Amz-Expires': self._expires,
         'X-Amz-SignedHeaders': signed_headers,
     }
     if self.credentials.token is not None:
         auth_params['X-Amz-Security-Token'] = self.credentials.token
     # Now parse the original query string to a dict, inject our new query
     # params, and serialize back to a query string.
     url_parts = urlsplit(request.url)
     # parse_qs makes each value a list, but in our case we know we won't
     # have repeated keys so we know we have single element lists which we
     # can convert back to scalar values.
     query_dict = dict(
         [(k, v[0]) for k, v in parse_qs(url_parts.query).items()])
     # The spec is particular about this.  It *has* to be:
     # https://<endpoint>?<operation params>&<auth params>
     # You can't mix the two types of params together, i.e just keep doing
     # new_query_params.update(op_params)
     # new_query_params.update(auth_params)
     # percent_encode_sequence(new_query_params)
     operation_params = ''
     if request.data:
         # We also need to move the body params into the query string.
         # request.data will be populated, for example, with query services
         # which normally form encode the params into the body.
         # This means that request.data is a dict() of the operation params.
         query_dict.update(request.data)
         request.data = ''
     if query_dict:
         operation_params = percent_encode_sequence(query_dict) + '&'
     new_query_string = (operation_params +
                         percent_encode_sequence(auth_params))
     # url_parts is a tuple (and therefore immutable) so we need to create
     # a new url_parts with the new query string.
     # <part>   - <index>
     # scheme   - 0
     # netloc   - 1
     # path     - 2
     # query    - 3  <-- we're replacing this.
     # fragment - 4
     p = url_parts
     new_url_parts = (p[0], p[1], p[2], new_query_string, p[4])
     request.url = urlunsplit(new_url_parts)
Esempio n. 7
0
 def canonical_request(self, request):
     cr = [request.method.upper()]
     path = self._normalize_url_path(urlsplit(request.url).path)
     cr.append(path)
     cr.append(self.canonical_query_string(request))
     headers_to_sign = self.headers_to_sign(request)
     cr.append(self.canonical_headers(headers_to_sign) + '\n')
     cr.append(self.signed_headers(headers_to_sign))
     if 'X-Amz-Content-SHA256' in request.headers:
         body_checksum = request.headers['X-Amz-Content-SHA256']
     else:
         body_checksum = self.payload(request)
     cr.append(body_checksum)
     return '\n'.join(cr)
Esempio n. 8
0
 def headers_to_sign(self, request):
     """
     Select the headers from the request that need to be included
     in the StringToSign.
     """
     header_map = HTTPHeaders()
     split = urlsplit(request.url)
     for name, value in request.headers.items():
         lname = name.lower()
         if lname not in SIGNED_HEADERS_BLACKLIST:
             header_map[lname] = value
     if 'host' not in header_map:
         header_map['host'] = split.netloc
     return header_map
Esempio n. 9
0
def _urljoin(endpoint_url, url_path):
    p = urlsplit(endpoint_url)
    # <part>   - <index>
    # scheme   - p[0]
    # netloc   - p[1]
    # path     - p[2]
    # query    - p[3]
    # fragment - p[4]
    if not url_path or url_path == '/':
        # If there's no path component, ensure the URL ends with
        # a '/' for backwards compatibility.
        if not p[2]:
            return endpoint_url + '/'
        return endpoint_url
    if p[2].endswith('/') and url_path.startswith('/'):
        new_path = p[2][:-1] + url_path
    else:
        new_path = p[2] + url_path
    reconstructed = urlunsplit((p[0], p[1], new_path, p[3], p[4]))
    return reconstructed
Esempio n. 10
0
def _urljoin(endpoint_url, url_path):
    p = urlsplit(endpoint_url)
    # <part>   - <index>
    # scheme   - p[0]
    # netloc   - p[1]
    # path     - p[2]
    # query    - p[3]
    # fragment - p[4]
    if not url_path or url_path == '/':
        # If there's no path component, ensure the URL ends with
        # a '/' for backwards compatibility.
        if not p[2]:
            return endpoint_url + '/'
        return endpoint_url
    if p[2].endswith('/') and url_path.startswith('/'):
        new_path = p[2][:-1] + url_path
    else:
        new_path = p[2] + url_path
    reconstructed = urlunsplit((p[0], p[1], new_path, p[3], p[4]))
    return reconstructed
Esempio n. 11
0
def is_valid_endpoint_url(endpoint_url):
    """Verify the endpoint_url is valid.

    :type endpoint_url: string
    :param endpoint_url: An endpoint_url.  Must have at least a scheme
        and a hostname.

    :return: True if the endpoint url is valid. False otherwise.

    """
    parts = urlsplit(endpoint_url)
    hostname = parts.hostname
    if hostname is None:
        return False
    if len(hostname) > 255:
        return False
    if hostname[-1] == ".":
        hostname = hostname[:-1]
    allowed = re.compile(
        "^((?!-)[A-Z\d-]{1,63}(?<!-)\.)*((?!-)[A-Z\d-]{1,63}(?<!-))$",
        re.IGNORECASE)
    return allowed.match(hostname)
Esempio n. 12
0
def switch_to_virtual_host_style(request,
                                 signature_version,
                                 default_endpoint_url=None,
                                 **kwargs):
    """
    This is a handler to force virtual host style s3 addressing no matter
    the signature version (which is taken in consideration for the default
    case). If the bucket is not DNS compatible an InvalidDNSName is thrown.

    :param request: A KSRequest object that is about to be sent.
    :param signature_version: The signature version to sign with
    :param default_endpoint_url: The endpoint to use when switching to a
        virtual style. If None is supplied, the virtual host will be
        constructed from the url of the request.
    """
    if request.auth_path is not None:
        # The auth_path has already been applied (this may be a
        # retried request).  We don't need to perform this
        # customization again.
        return
    elif _is_get_bucket_location_request(request):
        # For the GetBucketLocation response, we should not be using
        # the virtual host style addressing so we can avoid any sigv4
        # issues.
        logger.debug("Request is GetBucketLocation operation, not checking "
                     "for DNS compatibility.")
        return
    parts = urlsplit(request.url)
    request.auth_path = parts.path
    path_parts = parts.path.split('/')

    # Retrieve what the endpoint we will be prepending the bucket name to.
    if default_endpoint_url is None:
        default_endpoint_url = parts.netloc

    if len(path_parts) > 1:
        bucket_name = path_parts[1]
        if not bucket_name:
            # If the bucket name is empty we should not be checking for
            # dns compatibility.
            return
        logger.debug('Checking for DNS compatible bucket for: %s', request.url)
        if check_dns_name(bucket_name):
            # If the operation is on a bucket, the auth_path must be
            # terminated with a '/' character.
            if len(path_parts) == 2:
                if request.auth_path[-1] != '/':
                    request.auth_path += '/'
            path_parts.remove(bucket_name)
            # At the very least the path must be a '/', such as with the
            # CreateBucket operation when DNS style is being used. If this
            # is not used you will get an empty path which is incorrect.
            path = '/'.join(path_parts) or '/'
            global_endpoint = default_endpoint_url
            host = bucket_name + '.' + global_endpoint
            new_tuple = (parts.scheme, host, path, parts.query, '')
            new_uri = urlunsplit(new_tuple)
            request.url = new_uri
            logger.debug('URI updated to: %s', new_uri)
        else:
            raise InvalidDNSNameError(bucket_name=bucket_name)