0

I have a chat bot that connects to the Google People API with a google service account. The bot receives an event that contains the message sender's Google ID (ie 1234567890987654321). I would like to look up the message sender's name and email address using the service account.

I believe that the https://www.googleapis.com/auth/directory.readonly scope should allow this, with domain-wide delegation set for the service account. But the response does not include the requested fields, only Etag and ResourceName are populated.

What can I change or configure to include the name and email of an arbitrary directory user in a People.Get call with a service account?

package main

import (
        "context"
        "log"

        "google.golang.org/api/option"
        "google.golang.org/api/people/v1"
)

func main() {
        // Service account's credentials
        apiKeyFile := "credentials.json"
        // Google ID of a person within your directory
        resourceName := "people/1234567890987654321"
        fields := "names,emailAddresses"

        ctx := context.Background()
        // directory.readonly scope is included by default
        s, _ := people.NewService(ctx, option.WithCredentialsFile(apiKeyFile))
        pCall := s.People.Get(resourceName)
        pCall.PersonFields(fields)
        person, _ := pCall.Do()
        log.Print(person.Etag)
        for _, address := range person.EmailAddresses {
                log.Print(address.Value)
        }
        for _, name := range person.Names {
                log.Print(name.DisplayName)
        }
}

Go Playground

Dave Neeley
  • 3,526
  • 1
  • 24
  • 42
  • I'm unable to try this (without a domain) but, see [here](https://developers.google.com/admin-sdk/directory/v1/guides/delegation#go) the `JWT.Subject` is set to the desired user's email account and a token obtained and that's what's used to authenticate when instantiating the service. Yes, Admin SDK not People but... – DazWilkin Jan 21 '21 at 22:05
  • This kinda-sorta agrees with the People API [auth](https://developers.google.com/people/v1/how-tos/authorizing) doc. I assume `names` and `emailAddresses` are private not public data and so you must authenticate with an OAuth2 token. – DazWilkin Jan 21 '21 at 22:07
  • Whenever I read OAuth2, I think "requires a user login prompt in a browser". – Dave Neeley Jan 21 '21 at 22:33
  • Oh, I see. the admin SDK example jives with "Then your client application requests an access token from the Google Authorization Server, extracts a token from the response, and sends the token to the Google API that you want to access." from [here](https://developers.google.com/identity/protocols/oauth2) – Dave Neeley Jan 21 '21 at 22:38
  • Yes, this is all hypothetical because I can't try it for myself but, because you've got a service account that supports delegated auth and because you give it a known (email), it's permitted to get an OAuth token for you on the account's behalf.... In theory – DazWilkin Jan 22 '21 at 01:20
  • Does the user you are impersonating have access to these resources? @DaveNeeley – ale13 Jan 22 '21 at 11:39
  • From https://developers.google.com/people/v1/directory, "Reading domain data requires that the domain admin must have enabled external contact and profile sharing of domain-scoped data for their domain.". Have you verified the domain has external sharing enabled? – Amos Yuen Jan 25 '21 at 23:58
  • @AmosYuen, yes. – Dave Neeley Jan 26 '21 at 00:25
  • @ale13 If this were 3-legged-oauth the bot could send an authorization challenge and then get the sender's own profile data. I may end up doing that. However I found [this](https://tanaikech.github.io/2018/12/11/retrieving-access-token-using-service-account-by-googles-oauth2-package-for-golang/) yesterday which is along the lines of the the Admin SDK example. – Dave Neeley Jan 26 '21 at 15:02
  • 1
    If the your issue was solved, can you post the answer here for documentation purposes? – ale13 Jan 28 '21 at 11:37
  • I found another way to get the email address I needed without using the people API. But I asked my admin to double-check that the scopes this needs have actually been granted to the account. Domain-wide delegation is definitely configured. – Dave Neeley Jan 29 '21 at 17:38
  • What was the other way of doing this? – Chris Aug 17 '21 at 12:57
  • 1
    @Chris I added an answer to the question. Hope it helps! – Dave Neeley Aug 17 '21 at 22:17
  • Amazing. Thanks so much!! – Chris Sep 01 '21 at 14:08

1 Answers1

1

It turns out that Google Chat is returning the email address in the json response, it is just missing from chat.DeprecatedEvent. I created new structs to capture the other data, avoiding the person API altogether.

package lib

// This package creates a customized chat.DeprecatedEvent from v0.37.0 of the google chat api
import chat "google.golang.org/api/chat/v1"

// ChatEvent Google Chat event with customized User object
// Both ChatEvent.User and ChatEvent.Message.Sender should have the same fields,
// but only ChatEvent.User is modified to use ChatUser for simplicity
type ChatEvent struct {
    // Action: The form action data associated with an interactive card that
    // was clicked. Only populated for CARD_CLICKED events. See the
    // Interactive Cards guide (/hangouts/chat/how-tos/cards-onclick) for
    // more information.
    Action *chat.FormAction `json:"action,omitempty"`

    // ConfigCompleteRedirectUrl: The URL the bot should redirect the user
    // to after they have completed an authorization or configuration flow
    // outside of Google Chat. See the Authorizing access to 3p services
    // guide (/hangouts/chat/how-tos/auth-3p) for more information.
    ConfigCompleteRedirectURL string `json:"configCompleteRedirectUrl,omitempty"`

    // EventTime: The timestamp indicating when the event was dispatched.
    EventTime string `json:"eventTime,omitempty"`

    // Message: The message that triggered the event, if applicable.
    Message *chat.Message `json:"message,omitempty"`

    // Space: The room or DM in which the event occurred.
    Space *chat.Space `json:"space,omitempty"`

    // ThreadKey: The bot-defined key for the thread related to the event.
    // See the thread_key field of the `spaces.message.create` request for
    // more information.
    ThreadKey string `json:"threadKey,omitempty"`

    // Token: A secret value that bots can use to verify if a request is
    // from Google. The token is randomly generated by Google, remains
    // static, and can be obtained from the Google Chat API configuration
    // page in the Cloud Console. Developers can revoke/regenerate it if
    // needed from the same page.
    Token string `json:"token,omitempty"`

    // Type: The type of the event.
    //
    // Possible values:
    //   "UNSPECIFIED" - Default value for the enum. DO NOT USE.
    //   "MESSAGE" - A message was sent in a room or direct message.
    //   "ADDED_TO_SPACE" - The bot was added to a room or DM.
    //   "REMOVED_FROM_SPACE" - The bot was removed from a room or DM.
    //   "CARD_CLICKED" - The bot's interactive card was clicked.
    Type string `json:"type,omitempty"`

    // User: The customized hangouts chat user that triggered the event.
    User *ChatUser `json:"user,omitempty"`
}

// ChatUser A custom hangouts chat user that contains the fields currently sent from google as of 26-Jan-2021
type ChatUser struct {
    // Name: Google's name for the user, such as users/1234567890987654321
    Name string `json:"name"`
    // DisplayName: The first and last name of the user, such as John Doe
    DisplayName string `json:"displayName"`
    // AvatarURL: full URL to an avatar image
    AvatarURL string `json:"avatarUrl"`
    // Email: standard email address
    Email string `json:"email"`
    // Type: see chat.User.Type, typically this will be HUMAN
    Type string `json:"type"`
    // DomainID: see chat.User.DomainId
    DomainID string `json:"domainId"`
}
Dave Neeley
  • 3,526
  • 1
  • 24
  • 42