Repository: boto/boto

Issue:
generate_url raises exception for anonymous connections
It would be nice to be able to call generate_url on keys from anonymous connections

Right now if you open a connection anonymously

  conn = boto.connect_s3(anon=True)

Then later try to call generate_url on a key form the bucket, it fails with the following exception.

AttributeError: 'AnonAuthHandler' object has no attribute '_hmac_256'



Discussion:
Bump, just ran into the same thing. I have to workaround this by manually generating the download URL.

**STR:**
```
import boto
s3 = boto.connect_s3(anon=True)
print(s3.generate_url(0, 'GET', bucket='examplebucket', key='test.txt', query_auth=False))
```

**Expected:**
```
https://examplebucket.s3.amazonaws.com/test.txt
```

**Actual:**
```
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "boto/s3/connection.py", line 411, in generate_url
    b64_hmac = self._auth_handler.sign_string(c_string)
  File "boto/auth.py", line 132, in sign_string
    new_hmac = self._get_hmac()
  File "boto/auth.py", line 124, in _get_hmac
    if self._hmac_256:
AttributeError: 'AnonAuthHandler' object has no attribute '_hmac_256'
```

The `self._auth_handler.sign_string()` call here should be inside the `query_auth` conditional:
https://github.com/boto/boto/blob/af045f93d70fbb4cdac2a8e57d040ce59935d45b/boto/s3/connection.py#L411-L416

In addition, the conditional should probably also take into account `self.anon` (ie: if `anon=True` don't require `query_auth` to have been explicitly set to `False`).

I'll open a PR.

Resolving patch:
diff --git a/boto/s3/connection.py b/boto/s3/connection.py
--- a/boto/s3/connection.py
+++ b/boto/s3/connection.py
@@ -406,12 +406,12 @@ def generate_url(self, expires_in, method, bucket='', key='', headers=None,
         if extra_qp:
             delimiter = '?' if '?' not in auth_path else '&'
             auth_path += delimiter + '&'.join(extra_qp)
-        c_string = boto.utils.canonical_string(method, auth_path, headers,
-                                               expires, self.provider)
-        b64_hmac = self._auth_handler.sign_string(c_string)
-        encoded_canonical = urllib.parse.quote(b64_hmac, safe='')
         self.calling_format.build_path_base(bucket, key)
-        if query_auth:
+        if query_auth and not self.anon:
+            c_string = boto.utils.canonical_string(method, auth_path, headers,
+                                                   expires, self.provider)
+            b64_hmac = self._auth_handler.sign_string(c_string)
+            encoded_canonical = urllib.parse.quote(b64_hmac, safe='')
             query_part = '?' + self.QueryString % (encoded_canonical, expires,
                                                    self.aws_access_key_id)
         else:


Test patch:
diff --git a/tests/unit/s3/test_connection.py b/tests/unit/s3/test_connection.py
--- a/tests/unit/s3/test_connection.py
+++ b/tests/unit/s3/test_connection.py
@@ -48,6 +48,18 @@ def test_switched(self):
         )
 
 
+class TestAnon(MockServiceWithConfigTestCase):
+    connection_class = S3Connection
+
+    def test_generate_url(self):
+        conn = self.connection_class(
+            anon=True,
+            host='s3.amazonaws.com'
+        )
+        url = conn.generate_url(0, 'GET', bucket='examplebucket', key='test.txt')
+        self.assertNotIn('Signature=', url)
+
+
 class TestPresigned(MockServiceWithConfigTestCase):
     connection_class = S3Connection