Google Apps API 403 with Service Account - go

I've been trying make a query against Google's Admin API to list all users in my Google Apps Organization. I have permissions to make this query in the web UI example and get results, but it 403's when I try to make the query with a service account.
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() {
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,
)
client, err := directory.New(config.Client(context.Background()))
if err != nil {
log.Fatalf("Could not create directory service client => {%s}", err)
}
users, err := client.Users.List().ViewType(publicDataView).Domain(domain).Do()
if err != nil {
log.Fatalf("Failed to query all users => {%s}", err)
}
for _, u := range users.Users {
fmt.Println(u.Name.FullName)
}
}
Every time I execute I get a 403. The same query parameters works in the Try it! section here so I'm not sure why it fails.
result: Failed to query all users => {googleapi: Error 403: Not Authorized to access this resource/api, forbidden}

I know this question is a year old, but I couldnt find anything about this anywhere - but ive just managed to fix it after running into the same error as you.
basically you need to set a delegation user to your config, eg:
func main() {
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 me
config.Subject = "someone#example.com"
client, err := directory.New(config.Client(context.Background()))
if err != nil {
log.Fatalf("Could not create directory service client => {%s}", err)
}
users, err := client.Users.List().ViewType(publicDataView).Domain(domain).Do()
if err != nil {
log.Fatalf("Failed to query all users => {%s}", err)
}
for _, u := range users.Users {
fmt.Println(u.Name.FullName)
}
}
See https://github.com/golang/oauth2/blob/master/google/example_test.go#L118
hope this helps someone else!

The #Chronojam reply saved me hours, however on the newer versions of the Google SDK directory.New is deprecated, you should be achieve the same thing replacing the directory.New(...) line with this one instead:
client, err := directory.NewService(context.Background(), option.WithHTTPClient(config.Client(context.Background())))

Related

Share Google Doc via Golang SDK

I am using service account JSON to create google Doc via Golang SDK but as this doc is only accessible to Service account I am not able to access it with my Personal Google Account. In the Google Doc SDK documentation I couldn't find any function to share the Doc.
This is my sample Code:
package googledoc
import (
"context"
"fmt"
log "github.com/sirupsen/logrus"
"golang.org/x/oauth2/google"
"google.golang.org/api/docs/v1"
"google.golang.org/api/option"
func CreateDoc(title string) error {
ctx := context.Background()
cred, err := GetSecrets("ap-south-1", "GOOGLE_SVC_ACC_JSON")
if err != nil {
return fmt.Errorf("unable to get the SSM %v", err)
}
config, err := google.JWTConfigFromJSON(cred, docs.DocumentsScope)
if err != nil {
log.Fatalf("Unable to parse client secret file to config: %v", err)
}
client := config.Client(ctx)
srv, err := docs.NewService(ctx, option.WithHTTPClient(client))
if err != nil {
log.Fatalf("Unable to retrieve Docs client: %v", err)
}
docObj := docs.Document{
Title: title,
}
doc, err := srv.Documents.Create(&docObj).Do()
if err != nil {
log.Fatalf("Unable to retrieve data from document: %v", err)
return err
}
fmt.Printf("The title of the doc is: %s %s\n", doc.Title, doc.DocumentId)
return nil
}
Any help with this would be really appreciated Thanks.
I believe your goal is as follows.
You want to share the created Google Document by the service account with your Google account.
You want to achieve this using googleapis for golang.
Unfortunately, Google Docs API cannot be used for sharing the Document with users. In this case, Drive API is used. When your script is modified using Drive API, how about the following modification?
Sample script:
Please add this script just after the line of fmt.Printf("The title of the doc is: %s %s\n", doc.Title, doc.DocumentId). By this, the created Google Document is shared with the user.
driveSrv, err := drive.NewService(ctx, option.WithHTTPClient(client)) // Please use your client and service for using Drive API.
if err != nil {
log.Fatal(err)
}
permission := &drive.Permission{
EmailAddress: "###", // Please set the email address you want to share.
Role: "writer",
Type: "user",
}
res, err := driveSrv.Permissions.Create(doc.DocumentId, permission).Do()
if err != nil {
log.Fatal(err)
return err
}
fmt.Println(res)
When this script is added to your script, the created Document is shared with the user as the writer.
Reference:
Permissions: create

When I try to get the user data with Go and firebase adminSDK, I can only get the memory address

I'm currently using the Firebase adminSDK to get the user data from the firebase authentication.
The following code seems it can access firebase, but the data to be retrieved is a memory address.
I want to get the value.
I tried to find out about this issue, but I couldn't find any clues.
I am happy with any solution or hint.
package main
import (
firebase "firebase.google.com/go"
"fmt"
"golang.org/x/net/context"
"google.golang.org/api/iterator"
"google.golang.org/api/option"
"log"
)
func main() {
ctx := context.Background()
opt := option.WithCredentialsFile("/10_pro4_go/workspace/fb.json")
app, err := firebase.NewApp(ctx, nil, opt)
if err != nil {
log.Fatalln("error initializing app:", err)
}
client, err := app.Auth(ctx)
if err != nil{
log.Fatalln(err)
}
//getting all user data
iter := client.Users(ctx, "")
fmt.Println(*iter)
for {
user, err := iter.Next()
if err == iterator.Done {
break
}
if err != nil {
log.Fatalf("error listing users: %s\n", err)
}
userdata := *user
log.Printf("read user user: %v\n", userdata)
}
}
Results:
2021/01/15 23:08:33 read user user: {0xc00024a000 }
2021/01/15 23:08:33 read user user: {0xc00024a050 }
2021/01/15 23:08:33 read user user: {0xc00024a0f0 }
Accodring to the documentation of firebase auth package client.Users returns an Iterator that iterates through ExportedUserRecord objects
https://pkg.go.dev/firebase.google.com/go/v4#v4.6.0/auth#ExportedUserRecord
That ExportedUserRecord have an embedded pointer to UserRecord which is probably what ur searching for:
https://pkg.go.dev/firebase.google.com/go/v4#v4.6.0/auth#UserRecord
So i would suggest printing that UserRecord Data, or even UserInfo array inside of it if that is what you goal is:
user.UserRecrod.ProviderUserInfo

Getting 400 invalid_grant on google admin sdk api with golang. Any suggestions?

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)
}
}
}
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.
Used Subject instead of Email in the configuration.
Only used the AdminDirectoryUserScope scope, instead of the AdminDirectoryUserReadonlyScope scope (or the combination of both).
Included the Domain in the request. I get a 400 Bad Request without it.
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)
}
}
}
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

Golang google sheets API V4 - Write/Update example?

Trying to write a simple three column table ([][]string) with Go, but can't.
The quick start guide is very nice, I now can read sheets, but there no any example of how to write data to a sheet, maybe it is trivial, but not for me it seems.
The Golang library for my brains is just too complicated to figure out.
And there not a single example I could google...
This C# example very looks close, but I am not sure I clearly understand C#
Well after some tryouts, there is an answer. Everything is same as in https://developers.google.com/sheets/quickstart/go Just changes in the main function
func write() {
ctx := context.Background()
b, err := ioutil.ReadFile("./Google_Sheets_API_Quickstart/client_secret.json")
if err != nil {
log.Fatalf("Unable to read client secret file: %v", err)
}
// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/sheets.googleapis.com-go-quickstart.json
config, err := google.ConfigFromJSON(b, "https://www.googleapis.com/auth/spreadsheets")
if err != nil {
log.Fatalf("Unable to parse client secret file to config: %v", err)
}
client := getClient(ctx, config)
srv, err := sheets.New(client)
if err != nil {
log.Fatalf("Unable to retrieve Sheets Client %v", err)
}
spreadsheetId := "YOUR SPREADSHEET ID"
writeRange := "A1"
var vr sheets.ValueRange
myval := []interface{}{"One", "Two", "Three"}
vr.Values = append(vr.Values, myval)
_, err = srv.Spreadsheets.Values.Update(spreadsheetId, writeRange, &vr).ValueInputOption("RAW").Do()
if err != nil {
log.Fatalf("Unable to retrieve data from sheet. %v", err)
}
}
Well if you are looking for Service Account based authentication, then the following worked for me.
Download the client secret file for service account from https://console.developers.google.com
import (
"fmt"
"golang.org/x/net/context"
"google.golang.org/api/option"
"google.golang.org/api/sheets/v4"
"log"
)
const (
client_secret_path = "./credentials/client_secret.json"
)
func NewSpreadsheetService() (*SpreadsheetService, error) {
// Service account based oauth2 two legged integration
ctx := context.Background()
srv, err := sheets.NewService(ctx, option.WithCredentialsFile(client_secret_path), option.WithScopes(sheets.SpreadsheetsScope))
if err != nil {
log.Fatalf("Unable to retrieve Sheets Client %v", err)
}
c := &SpreadsheetService{
service: srv,
}
return c, nil
}
func (s *SpreadsheetService) WriteToSpreadsheet(object *SpreadsheetPushRequest) error {
var vr sheets.ValueRange
vr.Values = append(vr.Values, object.Values)
res, err := s.service.Spreadsheets.Values.Append(object.SpreadsheetId, object.Range, &vr).ValueInputOption("RAW").Do()
fmt.Println("spreadsheet push ", res)
if err != nil {
fmt.Println("Unable to update data to sheet ", err)
}
return err
}
type SpreadsheetPushRequest struct {
SpreadsheetId string `json:"spreadsheet_id"`
Range string `json:"range"`
Values []interface{} `json:"values"`
}
change the scope in the quickstart example from spreadsheets.readonly to spreadsheets for r/w access
here is the write snippet:
writeRange := "A1" // or "sheet1:A1" if you have a different sheet
values := []interface{}{"It worked!"}
var vr sheets.ValueRange
vr.Values = append(vr.Values,values
_, err = srv.Spreadsheets.Values.Update(spreadsheetId,writeRange,&vr).ValueInputOption("RAW").Do()
and that should work:

Firebase: Failed to validate MAC

I am using fireauth and firego from zabawaba99. I am getting an error (please see below) when pushing data to my firebase database. I have been following his examples but I cannot get it to work. Someone got a idea why this is happening?
Error:
2016/06/03 14:30:13 {
"error" : "Failed to validate MAC."
}
Code:
gen := fireauth.New("<API-KEY/SECRET>")
data := fireauth.Data{"uid": "1"}
token, err := gen.CreateToken(data, nil)
if err != nil {
log.Fatal(err)
}
fb := firego.New("https://myapp.firebaseio.com" , nil)
log.Println(token)
fb.Auth(token)
for i := 0; i<len(items); i++ {
item := items[i]
pushedItem, err := fb.Child("items").Push(items)
if err != nil {
log.Fatal(err) // error is happening here
}
var itemTest string
if err := pushedItem.Value(&itemTest); err != nil {
log.Fatal(err)
}
fmt.Printf("%s: %s\n", pusedItem, itemTest)
}
Unfortunately there isn't Go-specific documentation, but I believe, based on the new docs, that the old REST way to authenticate doesn't work any longer.
Having said that, I have been able to get your code to work reading a bunch of docs, lots of trial & error, and by using OAuth authentication by means of JWT.
Firstly, follow this guide: https://firebase.google.com/docs/server/setup but just the "Add Firebase to your App" section.
Issue a go get -u golang.org/x/oauth2 and go get -u golang.org/x/oauth2/google (or use your favorite vendoring way).
Change your code as such:
package main
import (
"fmt"
"io/ioutil"
"log"
"github.com/zabawaba99/firego"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
)
func main() {
jsonKey, err := ioutil.ReadFile("./key.json") // or path to whatever name you downloaded the JWT to
if err != nil {
log.Fatal(err)
}
conf, err := google.JWTConfigFromJSON(jsonKey, "https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/firebase.database")
if err != nil {
log.Fatal(err)
}
client := conf.Client(oauth2.NoContext)
fb := firego.New("https://myapp.firebaseio.com" , client)
for i := 0; i<len(items); i++ {
item := items[i]
pushedItem, err := fb.Child("items").Push(items)
if err != nil {
log.Fatal(err) // error is happening here
}
var itemTest string
if err := pushedItem.Value(&itemTest); err != nil {
log.Fatal(err)
}
fmt.Printf("%s: %s\n", pusedItem, itemTest)
}
}
The above worked for me!
Edit: Adding reference to StackOverflow answers that helped me:
Using Custom Tokens to make REST requests to FB DB as an admin
Is it still possible to do server side verification of tokens in Firebase 3?

Resources