Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python3 support : another take #16

Merged
merged 4 commits into from
Jun 19, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
language: python
python:
- "2.7"
- "3.4"

# command to install dependencies
install: pip install -r requirements.txt
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

This package allows you to authenticate to AWS with Amazon's [signature version 4 signing process](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html) with the python [requests](http://docs.python-requests.org/en/latest/) library.

Developed and tested with python `2.7.10`.
Tested with both python `2.7` and `3.4`.

# Installation

Expand Down
15 changes: 10 additions & 5 deletions aws_requests_auth/aws_auth.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import hmac
import urllib
import hashlib
import datetime
from urlparse import urlparse
try:
# python 2
from urllib import quote
from urlparse import urlparse
except ImportError:
# python 3
from urllib.parse import quote, urlparse

import requests

Expand Down Expand Up @@ -95,7 +100,7 @@ def __call__(self, r):

# Create payload hash (hash of the request body content). For GET
# requests, the payload is an empty string ('').
body = r.body if r.body else ''
body = r.body if r.body else bytes()
payload_hash = hashlib.sha256(body).hexdigest()
Copy link
Owner

@DavidMuller DavidMuller Jun 19, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

python2 vs pytnon3 string compability is still a bit of an unknown for me. Could you explain why we run .encode('utf-8') on hashlib.sha256(canonical_request.encode('utf-8')).hexdigest() but not on payload_hash = hashlib.sha256(body).hexdigest()?

(A separte python3 PR, #14, for example runs a .encode(utf-8') option in this general area)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The key is that PreparedRequest.body is never a string, it is either None or binary data (bytes), encoding has already been applied by the time it becomes a request body. In PR #14 encoding is applied conditionally, but the only case it makes sense is when we replace None by an empty string. Instead of this hack we do the right thing and set body to an empty "bytes".

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, thanks for explaining


# Combine elements to create create canonical request
Expand All @@ -109,7 +114,7 @@ def __call__(self, r):
credential_scope = (datestamp + '/' + self.aws_region + '/' +
self.service +'/' + 'aws4_request')
string_to_sign = (algorithm + '\n' + amzdate + '\n' + credential_scope +
'\n' + hashlib.sha256(canonical_request).hexdigest())
'\n' + hashlib.sha256(canonical_request.encode('utf-8')).hexdigest())

# Create the signing key using the function defined above.
signing_key = getSignatureKey(self.aws_secret_access_key,
Expand Down Expand Up @@ -146,7 +151,7 @@ def get_caononical_path(cls, r):

# safe chars adapted from boto's use of urllib.parse.quote
# https://github.com/boto/boto/blob/d9e5cfe900e1a58717e393c76a6e3580305f217a/boto/auth.py#L393
return urllib.quote(parsedurl.path if parsedurl.path else '/', safe='/-_.~')
return quote(parsedurl.path if parsedurl.path else '/', safe='/-_.~')

@classmethod
def get_canonical_querystring(cls, r):
Expand Down
54 changes: 54 additions & 0 deletions aws_requests_auth/tests/test_aws_auth.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import datetime
import mock
import unittest

Expand Down Expand Up @@ -65,3 +66,56 @@ def test_post_request_with_get_param(self):
mock_request.url = url
mock_request.method = "POST"
self.assertEqual('version=1', AWSRequestsAuth.get_canonical_querystring(mock_request))

def test_auth_for_get(self):
auth = AWSRequestsAuth(aws_access_key='YOURKEY',
aws_secret_access_key='YOURSECRET',
aws_host='search-foo.us-east-1.es.amazonaws.com',
aws_region='us-east-1',
aws_service='es')
url = 'http://search-foo.us-east-1.es.amazonaws.com:80/'
mock_request = mock.Mock()
mock_request.url = url
mock_request.method = "GET"
mock_request.body = None
mock_request.headers = {}

frozen_datetime = datetime.datetime(2016, 6, 18, 22, 4, 5)
with mock.patch('datetime.datetime') as mock_datetime:
mock_datetime.utcnow.return_value = frozen_datetime
auth(mock_request)
self.assertEqual({
'Authorization': 'AWS4-HMAC-SHA256 Credential=YOURKEY/20160618/us-east-1/es/aws4_request, '
'SignedHeaders=host;x-amz-date, '
'Signature=ca0a856286efce2a4bd96a978ca6c8966057e53184776c0685169d08abd74739',
'x-amz-date': '20160618T220405Z',

}, mock_request.headers)

def test_auth_for_post(self):
auth = AWSRequestsAuth(aws_access_key='YOURKEY',
aws_secret_access_key='YOURSECRET',
aws_host='search-foo.us-east-1.es.amazonaws.com',
aws_region='us-east-1',
aws_service='es')
url = 'http://search-foo.us-east-1.es.amazonaws.com:80/'
mock_request = mock.Mock()
mock_request.url = url
mock_request.method = "POST"
mock_request.body = b'foo=bar'
mock_request.headers = {
'Content-Type': 'application/x-www-form-urlencoded',
}

frozen_datetime = datetime.datetime(2016, 6, 18, 22, 4, 5)
with mock.patch('datetime.datetime') as mock_datetime:
mock_datetime.utcnow.return_value = frozen_datetime
auth(mock_request)
self.assertEqual({
'Authorization': 'AWS4-HMAC-SHA256 Credential=YOURKEY/20160618/us-east-1/es/aws4_request, '
'SignedHeaders=host;x-amz-date, '
'Signature=a6fd88e5f5c43e005482894001d9b05b43f6710e96be6098bcfcfccdeb8ed812',
'Content-Type': 'application/x-www-form-urlencoded',
'x-amz-date': '20160618T220405Z',

}, mock_request.headers)