Repository: googleapis/google-cloud-python

Issue:
Storage: Make HTTP-Method in signed url in upper case (and validate)
1. Specify the API at the beginning of the title (for example, "BigQuery: ...")
   General, Core, and Other are also allowed as types
✅
2. OS type and version
I use the Storage lib inside a `Cloud Function` (Python Beta runntime)
3. Python version and virtual environment information `python --version`
3.7
4. google-cloud-python version `pip show google-cloud`, `pip show google-<service>` or `pip freeze`
1.12.0
5. Stacktrace if available
❌
6. Steps to reproduce
❌
7. Code example
👇


Hey guys 👋 
currently I experiment a little bit with the Cloud Storage library inside a Cloud Function.
For testing I want to make an already uploaded file available via `Blob#generate_signed_url`.
The thing is - it works. I have no issues.
But Yesterday I spend at least 6 hours for testing it because the `method` in the `generated_signed_url` needs to be in uppercase.
Meaning the following does **not** work:
```python
blob.generate_signed_url(timedelta(minutes=5), "get")
```
but **this** works:
```python
blob.generate_signed_url(timedelta(minutes=5), "GET")
```

After a quick look into your code I found out that you "just put" the string into the generated URL. Without validating if that is a valid HTTP-Method!
https://github.com/GoogleCloudPlatform/google-cloud-python/blob/81bc5ef11d1918580f41e9f8ee2c9be2529b1169/storage/google/cloud/storage/_signing.py#L189

I would recommend (or request with this issue 🙃 ) to either:
* Check if the given string is a valid HTTP-Method (POST, GET, PUT etc.) **and** make the string to uppercase! 
* Or better (but leads to breaking changes (maybe 😃)) make an ENUM of it and only allow to put an enum-value into the method.



Discussion:
I can see making the method uppercase (because the backend requires it), but enforcing a valid verb is not reasonable:  we would have to track the set of verbs allowed over time by the back end.

Resolving patch:
diff --git a/storage/google/cloud/storage/blob.py b/storage/google/cloud/storage/blob.py
--- a/storage/google/cloud/storage/blob.py
+++ b/storage/google/cloud/storage/blob.py
@@ -345,9 +345,11 @@ def generate_signed_url(self, expiration, method='GET',
             credentials = client._credentials
 
         return generate_signed_url(
-            credentials, resource=resource,
+            credentials,
+            resource=resource,
             api_access_endpoint=_API_ACCESS_ENDPOINT,
-            expiration=expiration, method=method,
+            expiration=expiration,
+            method=method.upper(),
             content_type=content_type,
             response_type=response_type,
             response_disposition=response_disposition,


Test patch:
diff --git a/storage/tests/system.py b/storage/tests/system.py
--- a/storage/tests/system.py
+++ b/storage/tests/system.py
@@ -727,6 +727,16 @@ def test_create_signed_read_url(self):
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.content, self.LOCAL_FILE)
 
+    def test_create_signed_read_url_lowercase_method(self):
+        blob = self.bucket.blob('LogoToSign.jpg')
+        expiration = int(time.time() + 10)
+        signed_url = blob.generate_signed_url(expiration, method='get',
+                                              client=Config.CLIENT)
+
+        response = requests.get(signed_url)
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.content, self.LOCAL_FILE)
+
     def test_create_signed_read_url_w_non_ascii_name(self):
         blob = self.bucket.blob(u'Caf\xe9.txt')
         payload = b'Test signed URL for blob w/ non-ASCII name'
diff --git a/storage/tests/unit/test_blob.py b/storage/tests/unit/test_blob.py
--- a/storage/tests/unit/test_blob.py
+++ b/storage/tests/unit/test_blob.py
@@ -383,6 +383,36 @@ def test_generate_signed_url_w_credentials(self):
         credentials = object()
         self._basic_generate_signed_url_helper(credentials=credentials)
 
+    def test_generate_signed_url_lowercase_method(self):
+        BLOB_NAME = 'blob-name'
+        EXPIRATION = '2014-10-16T20:34:37.000Z'
+        connection = _Connection()
+        client = _Client(connection)
+        bucket = _Bucket(client)
+        blob = self._make_one(BLOB_NAME, bucket=bucket)
+        URI = (u'http://example.com/abucket/a-blob-name?Signature=DEADBEEF'
+               u'&Expiration=2014-10-16T20:34:37.000Z')
+
+        SIGNER = _Signer()
+        with mock.patch('google.cloud.storage.blob.generate_signed_url',
+                        new=SIGNER):
+            signed_url = blob.generate_signed_url(EXPIRATION, method='get')
+            self.assertEqual(signed_url, URI)
+
+        PATH = '/name/%s' % (BLOB_NAME,)
+        EXPECTED_ARGS = (_Connection.credentials,)
+        EXPECTED_KWARGS = {
+            'api_access_endpoint': 'https://storage.googleapis.com',
+            'expiration': EXPIRATION,
+            'method': 'GET',
+            'resource': PATH,
+            'content_type': None,
+            'response_type': None,
+            'response_disposition': None,
+            'generation': None,
+        }
+        self.assertEqual(SIGNER._signed, [(EXPECTED_ARGS, EXPECTED_KWARGS)])
+
     def test_generate_signed_url_non_ascii(self):
         BLOB_NAME = u'\u0410\u043a\u043a\u043e\u0440\u0434\u044b.txt'
         EXPIRATION = '2014-10-16T20:34:37.000Z'