0
votes

I am trying to execute a watch to enable GMail push API to send notifications to PubSub. I am using Elixir, and I am already using others Google API, in the same language; for some reason, I fail with GMail API.

I am receiving a 400 BAD REQUEST, with "preconditionFailed" in the body.

Since the 400, I guessed that I was passing the value in the wrong way. So I tried to get the list of the messages, which is a call that takes no parameters:

{:ok, token} = Goth.Token.for_scope("https://mail.google.com/")
conn = GoogleApi.Gmail.V1.Connection.new(token.token)
GoogleApi.Gmail.V1.Api.Users.gmail_users_messages_list(conn, "me")

And still I receive a 400:

{:error,
 %Tesla.Env{
   __client__: %Tesla.Client{
     adapter: nil,
     fun: nil,
     post: [],
     pre: [
       {Tesla.Middleware.Headers, :call,
        [
          [
            {"authorization",
             "Bearer TOKEN HERE"}
          ]
        ]}
     ]
   },
   __module__: GoogleApi.Gmail.V1.Connection,
   body: "{\n \"error\": {\n  \"errors\": [\n   {\n    \"domain\": \"global\",\n    \"reason\": \"failedPrecondition\",\n    \"message\": \"Bad Request\"\n   }\n  ],\n  \"code\": 400,\n  \"message\": \"Bad Request\"\n }\n}\n",
   headers: [
     {"cache-control", "private, max-age=0"},
     {"date", "Fri, 01 May 2020 19:40:10 GMT"},
     {"accept-ranges", "none"},
     {"server", "GSE"},
     {"vary", "X-Origin"},
     {"content-length", "179"},
     {"content-type", "application/json; charset=UTF-8"},
     {"expires", "Fri, 01 May 2020 19:40:10 GMT"},
     {"x-content-type-options", "nosniff"},
     {"x-frame-options", "SAMEORIGIN"},
     {"content-security-policy", "frame-ancestors 'self'"},
     {"x-xss-protection", "1; mode=block"},
     {"alt-svc",
      "h3-Q050=\":443\"; ma=2592000,h3-Q049=\":443\"; ma=2592000,h3-Q048=\":443\"; ma=2592000,h3-Q046=\":443\"; ma=2592000,h3-Q043=\":443\"; ma=2592000,quic=\":443\"; ma=2592000; v=\"46,43\""}
   ],
   method: :get,
   opts: [],
   query: [],
   status: 400,
   url: "https://www.googleapis.com/gmail/v1/users/me/messages"
 }}

I don't understand why this is failing. Also, I have checked with REST specs, and it looks correct:

https://developers.google.com/gmail/api/v1/reference/users/messages/list

P.s.: as a reference, the following code works:

  {:ok, token} = Goth.Token.for_scope("https://www.googleapis.com/auth/cloud-platform")
  conn = GoogleApi.PubSub.V1.Connection.new(token.token)

  pubsub_message = %GoogleApi.PubSub.V1.Model.PubsubMessage{
    data: message |> Jason.encode!() |> Base.encode64(padding: true)
  }

  request = %GoogleApi.PubSub.V1.Model.PublishRequest{
    messages: [pubsub_message]
  }

  Projects.pubsub_projects_topics_publish(
    conn,
    <PROJECT>,
    "consolidation-messages",
    body: request
  )

So the authentication procedure looks correct.

1
You are trying to use a service account instead of OAuth user credentials. Unless you have enabled G Suite Domain Wide Delegation, service account credentials will not work with Gmail.John Hanley
Hi @JohnHanley, thanks for the reply. I think that SDWD is a viable option, I can ask. Once I have enabled it, do you think should be everything in place ? Or should I deal with roles/permissions ? Why wasn't this mentioned in the official doc (or maybe it was and I did not spot it) ? Thanks !Bruno Ripa
You will need to read the documentation for Domain Wide Delegation and understand this feature. This feature provides admin rights to the G Suite member (user) accounts including impersonation. This feature is not part of Gmail, it is a G Suite / GCP feature and is part of the documentation. Note: this feature supports G Suite accounts and not Gmail (only) accounts.John Hanley
Yeah I know it exists but I have never used it. Thanks for the tip!Bruno Ripa
Also, I was reading everything again, and I have one more question. Why am I receiving a 400 BAD REQUEST, and not a 401 or 403, maybe ? This is the only doubt I have, at this point.Bruno Ripa

1 Answers

0
votes

The Gmail API, does require impersonation to get a user's messages and other API resources. Your G Suite Admin should delegate domain wide authority to yous service account for it to be able to impersonate the user whose messages your app wants to access.

Here is some information about Service Account Domain Wide Delegation.


Note:

There is a little part in the documentation that most people miss, and it is the key. And I'm sure it is where you are having trouble authorizing the client.

The Addendum here: [1]. You may have to create your own JWT token to make authorized API calls.

[1]https://developers.google.com/identity/protocols/oauth2/service-account#jwt-auth