0

I am trying to work out a golang script that uses my service account to manage my google domain. I get an error when I try to do a simple user list: 400 invalid_grant. It appears that I am using my service account correctly(?), and my service account is a super admin. I am using credentials in java code; so I know that it is valid. Any thoughts?

package main

import (
    "fmt"
    "io/ioutil"
    "log"

    "golang.org/x/net/context"
    "golang.org/x/oauth2/google"
    directory "google.golang.org/api/admin/directory/v1"
)

func main() {
    serviceAccountFile := "/credentials.json"
    serviceAccountJSON, err := ioutil.ReadFile(serviceAccountFile)
    if err != nil {
        log.Fatalf("Could not read service account credentials file, %s => {%s}", serviceAccountFile, err)
    }

    config, err := google.JWTConfigFromJSON(serviceAccountJSON,
        directory.AdminDirectoryUserScope,
        directory.AdminDirectoryUserReadonlyScope,
    )

    // Add the service account.
    config.Email = "serviceaccount@domain.com"

    srv, err := directory.New(config.Client(context.Background()))
    if err != nil {
        log.Fatalf("Could not create directory service client => {%s}", err)
    }

    // The next step fails with:
    //2019/03/25 10:38:43 Unable to retrieve users in domain: Get https://www.googleapis.com/admin/directory/v1/users?alt=json&maxResults=10&orderBy=email&prettyPrint=false: oauth2: cannot fetch token: 400 Bad Request
    //Response: {
    //  "error": "invalid_grant",
    //  "error_description": "Robot is missing a project number."
    //}
    //exit status 1
    usersReport, err := srv.Users.List().MaxResults(10).OrderBy("email").Do()
    if err != nil {
        log.Fatalf("Unable to retrieve users in domain: %v", err)
    }

    if len(usersReport.Users) == 0 {
        fmt.Print("No users found.\n")
    } else {
        fmt.Print("Users:\n")
        for _, u := range usersReport.Users {
            fmt.Printf("%s (%s)\n", u.PrimaryEmail, u.Name.FullName)
        }
    }
}
ad34
  • 1
  • Have you effectively followed these instructions particularly (a) the delegation step; (b) the "note"? When you write that you've used the service account in Java, is that for a very similar purpose? i.e. does it work in Java but not Go? https://developers.google.com/admin-sdk/directory/v1/guides/delegation – DazWilkin Mar 26 '19 at 02:20
  • 1
    I tried it but it's challenging for me to gain access to a G Suite domain. I think the Google Golang sample is incorrect. Please try ```config.Email="[[Service Account Email]]``` and ```config.Subject=[[User-being-delegated Email Address]]``` – DazWilkin Mar 26 '19 at 04:36
  • I'm going to "Ask a friend" because I'm unable to get this to work although I get 401s (Unauthorized). Will report back here with my experts' guidance. – DazWilkin Mar 26 '19 at 18:37
  • I have been unable to get this to work in Golang. I wrote an equivalent version in Python and it works. Continuing to investigate. https://gist.github.com/DazWilkin/dca8c3db8879765632d4c4be8d662074 – DazWilkin Mar 27 '19 at 00:47

2 Answers2

0

I got this working. It seems like it may be a combination of things. DazWilkin, yes I get the 401 unauthorized error when I switch around how I pass in my service account. I made the following changes.

  1. Used Subject instead of Email in the configuration.
  2. Only used the AdminDirectoryUserScope scope, instead of the AdminDirectoryUserReadonlyScope scope (or the combination of both).
  3. Included the Domain in the request. I get a 400 Bad Request without it.
  4. I verified that Directory APIs were on from this link: https://developers.google.com/admin-sdk/directory/v1/quickstart/go . When I clicked on this link, it said that apis were already working. I am using the same json credentials here that I am using in some production scripts written in other languages. Meaning, I thought that this was already in place. So I don't think I needed to do this step, but I will include it in case it is useful for others.

Here is what my script looks like now:

package main

import (
    "fmt"
    "io/ioutil"
    "log"

    "golang.org/x/net/context"
    "golang.org/x/oauth2/google"
    directory "google.golang.org/api/admin/directory/v1"
)

func main() {
    serviceAccountFile := "/credentials.json"

    serviceAccountJSON, err := ioutil.ReadFile(serviceAccountFile)
    if err != nil {
        log.Fatalf("Could not read service account credentials file, %s => {%s}", serviceAccountFile, err)
    }

    // I want to use these options, but these cause a 401 unauthorized error
    //  config, err := google.JWTConfigFromJSON(serviceAccountJSON,
    //      directory.AdminDirectoryUserScope,
    //      directory.AdminDirectoryUserReadonlyScope,
    //  )
    config, err := google.JWTConfigFromJSON(serviceAccountJSON,
        directory.AdminDirectoryUserScope,
    )

    // Add the service account.
    //config.Email = "serviceaccount@domain.com" // Don't use Email, use Subject.
    config.Subject = "serviceaccount@domain.com"

    srv, err := directory.New(config.Client(context.Background()))

    if err != nil {
        log.Fatalf("Could not create directory service client => {%s}", err)
    }

    // Get the results.
    usersReport, err := srv.Users.List().Domain("domain.com").MaxResults(100).Do()
    if err != nil {
        log.Fatalf("Unable to retrieve users in domain: %v", err)
    }

    // Report results.
    if len(usersReport.Users) == 0 {
        fmt.Print("No users found.\n")
    } else {
        fmt.Print("Users:\n")
        for _, u := range usersReport.Users {
            fmt.Printf("%s (%s)\n", u.PrimaryEmail, u.Name.FullName)
        }
    }
}
ad34
  • 1
0

Fixed!

Thanks to Sal.

Here's a working Golang example:

https://gist.github.com/DazWilkin/afb0413a25272dc7d855ebec5fcadcb6

NB

  • Line 24 --config.Subject
  • Line 31 --You'll need to include the CustomerId (IDPID) using this link

Here's a working Python example:

https://gist.github.com/DazWilkin/dca8c3db8879765632d4c4be8d662074

DazWilkin
  • 32,823
  • 5
  • 47
  • 88