Repository: googleapis/google-api-python-client

Issue:
Change from 1.5.5 to 1.6.2 results in authentication failure.
We have some code that successfully talks to a Google API using v1.5.5 but breaks under v1.6.2 with the following error:

   "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential"

Our build statement looks like:

     return build(
         'monitoring',
         'v3',
         discoveryServiceUrl=discovery_url,
         developerKey=self.api_key,
         credentials=credentials)

I'm wondering if the following commit may be culprit here:

    https://github.com/google/google-api-python-client/commit/068eb35c9a70e31a42f04d5cb69c36d59e4a2a69

Because we supply both "credentials" and "developerKey" and expect the bearer token / other context from the credentials to be propagated regardless of the presence of "developerKey" (it was my understanding that "developerKey" simply adds a "key=<api-key>" to the discovery URL and to the API requests, but the headers implied by the credentials should still be provided in this case). 

Can you fix this? Thanks!


Discussion:
@michaelsafyan which API are you using that requires both a developer key and credentials?
Oh, I see, monitoring. You can just leave out the developer key, the monitoring API does not use it.
The developerKey is needed for visibility labels.
Looking in more detail, it looks like the API key is needed on the discovery URL but not on subsequent API calls which only need the HTTP bearer token.
We can workaround by dropping developerKey and manually appending the API key to the discovery URL (building the discovery URL ourselves rather than relying on the client library to construct the discovery URL), but it would be ideal if this worked correctly without that workaround.
I should also add, on top of the above, that if both a developerKey and credentials object are supplied but only one of the two is applied, wouldn't credential make more sense? Typically credentials include more authority than a simple API key. It may also make sense to log a warning/error to make this condition easier to debug (it took about two days to figure out the issue was in the client library version, not a result of IAM permissions or other causes).
> Looking in more detail, it looks like the API key is needed on the discovery URL but not on subsequent API calls which only need the HTTP bearer token.

Weird, developerKey should only be needed there for non-public (alpha/beta/EAP) APIs.

> I should also add, on top of the above, that if both a developerKey and credentials object are supplied but only one of the two is applied, wouldn't credential make more sense? Typically credentials include more authority than a simple API key. It may also make sense to log a warning/error to make this condition easier to debug (it took about two days to figure out the issue was in the client library version, not a result of IAM permissions or other causes).

Yeah, absolutely. This is a bit tricky. :) I agree it should either apply both or error.
Just to clarify, it is a non-public API (the Monitoring API includes both publicly released portions as well as portions that are restricted behind visibility labels). I'm currently working on a team that is iterating towards an EAP on one of these subsets of the API, and the code that broke is demo client code that uses only the external client library code to demo / test / verify the functioning of this part of the API.

Resolving patch:
diff --git a/googleapiclient/discovery.py b/googleapiclient/discovery.py
--- a/googleapiclient/discovery.py
+++ b/googleapiclient/discovery.py
@@ -334,6 +334,10 @@ def build_from_document(
   if http is not None and credentials is not None:
     raise ValueError('Arguments http and credentials are mutually exclusive.')
 
+  if developerKey is not None and credentials is not None:
+    raise ValueError(
+      'Arguments developerKey and credentials are mutually exclusive.')
+
   if isinstance(service, six.string_types):
     service = json.loads(service)
 


Test patch:
diff --git a/tests/test_discovery.py b/tests/test_discovery.py
--- a/tests/test_discovery.py
+++ b/tests/test_discovery.py
@@ -375,6 +375,13 @@ def test_credentials_and_http_mutually_exclusive(self):
       build(
         'plus', 'v1', http=http, credentials=mock.sentinel.credentials)
 
+  def test_credentials_and_developer_key_mutually_exclusive(self):
+    http = HttpMock(datafile('plus.json'), {'status': '200'})
+    with self.assertRaises(ValueError):
+      build(
+        'plus', 'v1', credentials=mock.sentinel.credentials,
+        developerKey=mock.sentinel.credentials)
+
 
 class DiscoveryFromDocument(unittest.TestCase):
   MOCK_CREDENTIALS = mock.Mock(spec=google.auth.credentials.Credentials)