Setting up Gmail Push notifications through GCP pub/sub using Go - 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
}

Related

How to download calendar events from external user in a non-interactive manner?

I created this method, which essentially does what I need:
clientId := os.Getenv("CLIENT_ID")
tenantId := "common"
scopes := []string{"calendars.read", "calendars.readwrite"} // todo add offline_access
credential, err := azidentity.NewDeviceCodeCredential(&azidentity.DeviceCodeCredentialOptions{
ClientID: clientId,
TenantID: tenantId,
UserPrompt: func(ctx context.Context, message azidentity.DeviceCodeMessage) error {
fmt.Println(message.Message)
return nil
},
})
if err != nil {
log.Print(err)
return
}
authProvider, err := auth.NewAzureIdentityAuthenticationProviderWithScopes(credential, scopes)
if err != nil {
log.Print(err)
return
}
adapter, err := msgraphsdk.NewGraphRequestAdapter(authProvider)
if err != nil {
log.Print(err)
return
}
result, err := msgraphsdk.NewGraphServiceClient(adapter).Me().Calendar().Events().Get(context.Background(), nil)
if err != nil {
log.Print(err)
}
for _, event := range result.GetValue() {
log.Print(*event.GetICalUId())
log.Print(*event.GetSubject())
}
However, this one will print out https://microsoft.com/devicelogin and the code to authenticate interactively. I have the access token and refresh token from another resource. I would need to inject either access token or refresh token into something, to replace the logic that results in interactivity. I need that for a back-end service that handles calendar synchronization. What do I need to do? Do you have some pointers? I am using Golang and I am breaking my head for the last few days (nothing I tried worked). Please, help.
I went through all tutorials on Microsoft Graph for Go that I could find. I read tons of documentation. I suspect that publicClientApp, err := public.New("client_id", public.WithAuthority("https://login.microsoftonline.com/common")) and publicClientApp.AcquireTokenSilent(context.Background(), []string{"calendars.read", "calendars.readwrite"}, ...) could be the answer, but I cannot make it work. I have a working solution with curl, but I need to use Go SDK.

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 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)

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

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?

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.

Resources