Why can I not use a DefaultClient to access Calendar API? - go

I'd like to use Google's quickstart code to access my Google Calendar. It works when I follow the steps, but changing it to use a DefaultClient results in a 403 error:
$ go run quickstart.go
2021/02/09 14:03:19 Unable to retrieve next ten of the user's events: googleapi: Error 403: Request had insufficient authentication
scopes.
More details:
Reason: insufficientPermissions, Message: Insufficient Permission
exit status 1
This is my current code to retrieve local Default Credentials and use them for generating an httpClient:
client, err := google.DefaultClient(oauth2.NoContext, calendar.CalendarReadonlyScope)
if err != nil {
log.Fatalf("auth broke: %v", err)
}
srv, err := calendar.New(client)
if err != nil {
log.Fatalf("Unable to retrieve Calendar client: %v", err)
}
t := time.Now().Format(time.RFC3339)
events, err := srv.Events.List("primary").ShowDeleted(false).
SingleEvents(true).TimeMin(t).MaxResults(10).OrderBy("startTime").Do()
if err != nil {
log.Fatalf("Unable to retrieve next ten of the user's events: %v", err)
}
fmt.Println("Upcoming events:")
if len(events.Items) == 0 {
fmt.Println("No upcoming events found.")
} else {
for _, item := range events.Items {
date := item.Start.DateTime
if date == "" {
date = item.Start.Date
}
fmt.Printf("%v (%v)\n", item.Summary, date)
}
}
The code does work if I create an OAuth2 credential in my GCP Project and use that, but that's not the approach I'd like to use here.
I understand I may need to use a 3-legged approach to get an OAuth2 token, but I can't find any examples on how to do this with Default Credentials.
Can I not use an approach similar to gcloud CLI's built-in token generation?

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

Google Cloud Function Calling Gmail API Using Service Account Domain-Wide Access

// Package p contains an HTTP Cloud Function.
package p
import (
"log"
"fmt"
"net/http"
"context"
"google.golang.org/api/gmail/v1"
)
func HelloWorld(wx http.ResponseWriter, rx *http.Request) {
log.Println("start")
srv, err := gmail.NewService(context.Background())
if err != nil {
log.Fatalf("Unable to retrieve Gmail client: %v", err)
}
user := "me"
r, err := srv.Users.Labels.List(user).Do()
if err != nil {
log.Fatalf("Unable to retrieve labels: %v", err)
}
if len(r.Labels) == 0 {
fmt.Println("No labels found.")
return
}
fmt.Println("Labels:")
for _, l := range r.Labels {
fmt.Printf("- %s\n", l.Name)
}
log.Println("end")
}
I'm writing a Google Cloud function in Go that needs to access the Gmail of my admin userid that owns the project, the gmail account, and created the cloud function.
Using the information on the page Choose the best way to use and authenticate service accounts on Google Cloud, I determined I should authenticate using "Attach Service Account" option.
Following the instructions on page Using OAuth 2.0 for Server to Server Applications, I created a new service account and delegated to it domain-wide access to scope https://mail.google.com/
I assigned the new service account as the Runtime Service Account for the Cloud Function.
The gmail.NewService statement seems to execute successfully but the srv.Users.Labels.List(user) statement fails with "Error 400: Precondition check".
The log file is below.
2022-07-09T03:34:14.575564Z testgogmail hy9add8bqppl 2022/07/09 03:34:14 start
2022-07-09T03:34:14.785116Z testgogmail hy9add8bqppl 2022/07/09 03:34:14 Unable to retrieve labels: googleapi: Error 400: Precondition check failed., failedPrecondition
2022-07-09T03:34:14.799538102Z testgogmail hy9add8bqppl Function execution took 553 ms. Finished with status: connection error
So what am I missing? What have I done wrong?
From what I determined, currently it is not possible to impersonate another account using the Gmail API for Go when you are relying on the basic domain-wide access/authentication. The gmail.NewService(context.Background()) statement executes successfully but you are authenticated as the gmail address of the service account. Since that does not actually exist, the subsequent Users.Labels.List fails even if you pass a different/valid email account as the user parameter.
However, it does work if you create an authentication token based on the service account with domain-wide access by using google.JWTConfigFromJSON and then use the WithTokenSource option when creating the Gmail service. Note - the sample code below is based on creating and executing the cloud function from the online UI, not the cloud shell.
pathWD, err := os.Getwd()
if err != nil {
log.Println("error getting working directory:", err)
}
log.Println("pathWD: ", pathWD)
jsonPath := pathWD + "/serverless_function_source_code/"
serviceAccountFile := "service_account.json"
serviceAccountFullFile := jsonPath + serviceAccountFile
os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", serviceAccountFullFile)
serviceAccountJSON, err := ioutil.ReadFile(serviceAccountFullFile)
if err != nil {
log.Fatal(err)
}
config, err := google.JWTConfigFromJSON(serviceAccountJSON,
"https://mail.google.com/", "https://www.googleapis.com/auth/cloud-platform",
)
config.Subject = "admin#awarenet.us"
ctx := context.Background()
srv, err := gmail.NewService(ctx, option.WithTokenSource(config.TokenSource(ctx)))
if err != nil {
log.Fatalf("Unable to retrieve Gmail client: %v", err)
}

Google people API not able to fetch emailAddresses

I am writing a desktop app that needs to authenticate a google user and fetch his email id. So far with the quickstart example, I am able to authenticate but I am having a hard time fetching user email address.
config, err := google.ConfigFromJSON(b, people.UserEmailsReadScope)
if err != nil {
log.Fatalf("Unable to parse client secret file to config: %v", err)
}
client := getClient(config)
srv, err := people.NewService(ctx, option.WithHTTPClient(client))
if err != nil {
log.Fatalf("Unable to create people Client %v", err)
}
me, err := srv.People.Get("people/me").PersonFields("emailAddresses").Do()
if err != nil {
log.Fatalf("Unable to retrieve user. %v", err)
}
I also tried UserinfoEmailScope but no luck. Getting 403 each time.
googleapi: Error 403: The caller does not have permission to request "people/me". Request requires one of the following scopes: [profile]., forbidden
I have added all of these scopes to the project also. What am I doing wrong?
Finally figured it out. Two scopes are required for this.
config, err := google.ConfigFromJSON(b, people.UserinfoProfileScope, people.UserinfoEmailScope)

googleapi: Error 403: Request had insufficient authentication scopes. More details: Reason: insufficientPermissions, Message: Insufficient Permission

I am trying to send an email with Gmail API. But I get this error
googleapi: Error 403: Request had insufficient authentication scopes.
More details:
Reason: insufficientPermissions, Message: Insufficient Permission
I think it might bee related to config, I also followed google's quickstart for Go
here is the getClient func:
func getClient(config *oauth2.Config) *http.Client {
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
tokFile := "token.json"
tok, err := tokenFromFile(tokFile)
if err != nil {
tok = getTokenFromWeb(config)
saveToken(tokFile, tok)
}
return config.Client(context.Background(), tok)
}
here is the code send:
case "pass":
templateData := struct {
VerPass string
}{
VerPass: cont1,
}
emailBody, err := parseTemplate("ver.html", templateData)
if err != nil {
fmt.Println("Parse Err")
return false, err
}
var message gmail.Message
emailTo := "To: " + to + "\r\n"
subject := "Subject: " + sub + "\n"
mime := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n"
msg := []byte(emailTo + subject + mime + "\n" + emailBody)
message.Raw = base64.URLEncoding.EncodeToString(msg)
// Send the message
fmt.Println("OOOOOYYYYY")
//here is the problem
b, err := ioutil.ReadFile("credentials.json")
if err != nil {
log.Fatalf("Unable to read client secret file: %v", err)
}
// If modifying these scopes, delete your previously saved token.json.
config, err := google.ConfigFromJSON(b, gmail.MailGoogleComScope)
if err != nil {
log.Fatalf("Unable to parse client secret file to config: %v", err)
}
client := getClient(config)
srv, err := gmail.New(client)
if err != nil {
log.Fatalf("Unable to retrieve Gmail client: %v", err)
}
//GmailService
res, err := srv.Users.Messages.Send("me", &message).Do() // change me
if err != nil {
fmt.Println("Res Err")
fmt.Println(err)
return false, err
}
fmt.Println(res)
I tried for config, err := google.ConfigFromJSON(b, gmail.MailGoogleComScope), I tried using GmailReadonlyScope and gmail.GmailSendScope, but I got the same error.
Request had insufficient authentication scopes.
Means that the user who has authorized your application to access their data has not granted your application enough permissions in order to do what you are trying to do.
You apear to be using the user.message.send method. If you check the documentation you will find that that method requires that the user be authorized with one of the following scopes.
If you did follow Googles quick start for go then you used gmail.GmailReadonlyScope as a scope which will only give you read only access.
However your code now apears to contain the mail.MailGoogleComScope scope which should work however i am guessing you neglected to reauthorize the application. And didn't see the comment in the tutorial
// If modifying these scopes, delete your previously saved token.json.
I suggest you deleted token.json and then the application will require that you authorize it again and your code should work with the elevated permissions.

Setting up Gmail Push notifications through GCP pub/sub using Go

I'm looking to basically setup my application such that it receives notifications every time a mail hits a Gmail inbox.
I'm following the guide here.
I have a Topic and subscription created.
My credentials are working. I can retrieve my emails using the credentials and a Go script as shown here.
I have enabled permissions on my topic gmail with gmail-api-push#system.gserviceaccount.com as a Pub/Sub Publisher.
I have tested the topic in pub/sub by manually sending a message through the console. The message shows up in the simple subscription I made.
main.go
func main() {
ctx := context.Background()
// Sets your Google Cloud Platform project ID.
projectID := "xxx"
// Creates a client.
// client, err := pubsub.NewClient(ctx, projectID)
_, err := pubsub.NewClient(ctx, projectID)
if err != nil {
log.Fatalf("Failed to create client: %v", err)
}
b, err := ioutil.ReadFile("credentials.json")
if err != nil {
log.Fatalf("Unable to read client secret file: %v", err)
}
// If modifying these scopes, delete your previously saved token.json.
config, err := google.ConfigFromJSON(b, gmail.GmailReadonlyScope)
if err != nil {
log.Fatalf("Unable to parse client secret file to config: %v", err)
}
gmailClient := getClient(config)
svc, err := gmail.New(gmailClient)
if err != nil {
log.Fatalf("Unable to retrieve Gmail client: %v", err)
}
var watchRequest gmail.WatchRequest
watchRequest.TopicName = "projects/xxx/topics/gmail"
svc.Users.Watch("xxx#gmail.com", &watchRequest)
...
The script runs fine although there's no stdout with any confirmation the Watch service is running.
However, with the above setup, I sent a mail from xxx#gmail.com to itself but the mail does not show up in my topic/subscription.
What else must I do to enable Gmail push notification through pub/sub using Go?
Make sure you are calling the Do method on your UsersWatchCall. The svc.Users.Watch returns only the structure, doesn't do the call immediately.
It would look something like this:
res, err := svc.Users.Watch("xxx#gmail.com", &watchRequest).Do()
if err != nil {
// don't forget to check the error
}

Resources