Drive API App OAuth Permissions Not Updating - go

I am working on a Golang service that will connect to Google Drive and download a file. The issue is that no matter how I update the projects permissions, when I run application and go to the browser window to OAuth, it only shows the metadata permissions needing granted when have granted more so that I can download files.
Here are the steps I've taken for the service:
I have created a project in Google Developer Console.
In that project I have enabled and updated Google Drive permissions to the capture all auth/drive permission (I have also tried granting all and only some granular).
I create the Golang service from this example: https://developers.google.com/drive/api/v3/quickstart/go
I run the Golang app which prompts me to open a URL in browser to OAuth authenticate.
I open link in browser, auth with my app-owner gmail account and every single time no matter what I do this is the only OAuth scope that shows:
If I use the Drive API Explorer to perform the file download it shows that I need these permissions:
When i click "Execute" in the explorer the OAuth popup shows all the permissions I would expect for my app and works correctly:
What am I doing wrong or missing?
Could this be because the app is an "internal" app?

The issue was in the Golang Quick-Start boilerplate code that I used to scaffold the application.
This line was overriding the permission settings I was explicitly setting in console.
// If modifying these scopes, delete your previously saved token.json.
config, err := google.ConfigFromJSON(b, drive.DriveMetadataReadonlyScope)
if err != nil {
log.Fatalf("Unable to parse client secret file to config: %v", err)
}
fixed when changed to:
// If modifying these scopes, delete your previously saved token.json.
config, err := google.ConfigFromJSON(b, drive.DriveScope)
if err != nil {
log.Fatalf("Unable to parse client secret file to config: %v", err)
}

Related

msgraph-sdk-go example code for getting user's drive fails

The example code for the msgraph-sdk-go to get a user's drive fails with a 401 error below. It expects the request body to contain a client_secret, although there is no place in the example code to create a request body.
The example code does successfully authenticate to my registered application via a web browser.
What is required to use the msgraph-sdk-go?
Here's the code that fails:
result, err := client.Me().Drive().Get(context.Background(), nil)
if err != nil {
fmt.Printf("Error getting the drive: %v\n", err)
printOdataError(err)
}
fmt.Printf("Found Drive : %v\n", *result.GetId())
Here's the error:
Error getting the drive: DeviceCodeCredential authentication failed
POST https://login.microsoftonline.com/efa4b4f3-5e38-4866-9206-79c604d86e7c/oauth2/v2.0/token
--------------------------------------------------------------------------------
RESPONSE 401 Unauthorized
--------------------------------------------------------------------------------
{
"error": "invalid_client",
"error_description": "AADSTS7000218: The request body must contain the following parameter: 'client_assertion' or 'client_secret'.\r\nTrace ID: b6f28bb4-6bed-4dfe-a275-c0343fb91e01\r\nCorrelation ID: c06d2257-b3ab-4df3-ba58-ab271cf97508\r\nTimestamp: 2023-02-14 14:18:22Z",
"error_codes": [
7000218
],
"timestamp": "2023-02-14 14:18:22Z",
"trace_id": "b6f28bb4-6bed-4dfe-a275-c0343fb91e01",
"correlation_id": "c06d2257-b3ab-4df3-ba58-ab271cf97508",
"error_uri": "https://login.microsoftonline.com/error?code=7000218"
}
Edit: Adding more detail for the answer from baywet
I have the client and device code credentials created as baywet shows but I added the TennantID field.
In point 5 I selected mobile/desktop application but with redirect URI of http://localhost. I have different options for Redirect URIs than he has.
Point 6 was the key to getting it to work. Once I selected Yes for Enable the following mobile and desktop flows my application worked.
I also had these API permissions.
Assuming you setup your client using the device code credentials given the error message you're getting with code similar to this
cred, err := azidentity.NewDeviceCodeCredential(&azidentity.DeviceCodeCredentialOptions{
ClientID: "CLIENT_ID",
UserPrompt: func(ctx context.Context, message azidentity.DeviceCodeMessage) error {
fmt.Println(message.Message)
return nil
},
})
client := msgraphsdk.NewGraphServiceClientWithCredentials(cred, []string{"User.Read"})
The registered application needs to be configured properly to allow for the device close flow.
For that:
Go to the azure portal
Navigate to the application registrations (Azure Active Directory, then Application registrations).
Find your application registration in the list.
Click on the authentication tab
Make sure the mobile and desktop applications platform is select with the https://login.microsoftonline.com/common/oauth2/nativeclient URL checked.
Make sur "Enable the following mobile and desktop flows" is set to "yes".
Click "save".
A couple of screenshots to guide you through.

Why do AWS credentials seem to go missing when creating a kinesis client from a session with the Go SDK?

I'm trying to list Kinesis streams
session, err := session.NewSessionWithOptions(session.Options{
Config: aws.Config{Region: aws.String("sa-east-1"),
CredentialsChainVerboseErrors: aws.Bool(true)},
Profile: profile,
})
client := kinesis.New(session)
streams, err := client.ListStreams(client.ListStreamsInput{})
The profile is correct and the region is correct but streams is always an empty list, despite there being one stream existing with those parameters. Can you advise why I don't see the stream?
I'm using the Delve debugger with vscode and can see this, where creds has nothing in AccessKeyId etc.
I've made sure that the credentials are being pointed to with the full path and they contain the required access key and secret.
From the docs
By default NewSession will only load credentials from the shared credentials file (~/.aws/credentials).
I also tried
session, err := session.NewSessionWithOptions(session.Options{
// Specify profile to load for the session's config
Profile: profile,
// Provide SDK Config options, such as Region.
Config: aws.Config{
Region: aws.String("sa-east-1"),
},
// Force enable Shared Config support
SharedConfigState: session.SharedConfigEnable,
}
but that returns
Could not initiate Kinesis client. Error: AssumeRoleTokenProviderNotSetError: assume role with MFA enabled, but AssumeRoleTokenProvider session option not set

Golang - spotify : This request requires user authentication

I'm writing a bot that takes the name of the music and the artist and adds it to the selected playlist with the zmb3 Spotify library. Everything works except the section add music to the playlist ;
the bot gives me this error: this request requires user authentication.
but Spotify token and Spotify secret are valid.
I searched, but I didn't get anything.
Is there anything more needed than Spotify Token and Spotify Secret?
What should I do?
Code :
// create spotify client connection and context to connect spotify
client, ctx := api.SpotifyConnection(SPOTIFYTOKEN, SPOTIFYSECRET)
// add playlist
playlist, err := client.GetPlaylist(ctx, PLAYLISTID)
if err != nil {
log.Fatal(err.Error())
}
/* SOME CODES */
if results.Tracks != nil {
items := results.Tracks.Tracks
musicID := items[0].ID.String()
cmd.AddMusic(client, ctx, playlist.ID.String(), musicID)
ERROR:
2021/12/26 11:06:25 This request requires user authentication.
Look at it this way - can you add a track to your playlist while not logged into your account? The same goes for your code. It's not an issue of authenticating to the API with TOKEN and SECRET_KEY, but you need to introduce user authentication, so that when a user opens your app, they log in and can add tracks to their own playlists.
There is an Authentication section in the zmb3 README that should help:
You can authenticate using a client credentials flow, but this does not provide any authorization to access a user's private data. For most use cases, you'll want to use the authorization code flow. This package includes an Authenticator type to handle the details for you.

Unable to access Google Cloud Storage from Google Kubernetes Engine using default service account and google cloud libraries

I wrote an application that has a function to upload image through Google Kubernetes Engine using go lang. Everything else works fine, but I kept running into problems when I try to write image to Google Cloud Storage.
Here is my code in golang that actually uses the google storage api:
func putImage(imageURL string, image multipart.File) bool {
fmt.Println("Putting into image location : " + imageURL)
contextBackground := context.Background()
storageClient, err := storage.NewClient(contextBackground)
if err != nil {
fmt.Println("No client.")
return false
}
bucket := storageClient.Bucket("mytestbucketname")
bucketWriter := bucket.Object(imageURL).NewWriter(contextBackground)
bucketWriter.ContentType = "image/jpg"
bucketWriter.CacheControl = "public, max-age=0"
size, err := io.Copy(bucketWriter, image)
if err != nil {
fmt.Println("failed to put image")
return false
}
fmt.Println(size)
fmt.Println("Successfully put image")
bucketWriter.Close()
return true
}
The above function always returns true and size is always greater than 0. However, when I check the bucket, it does not actually have anything inside. So I researched around and realized that the default service account only has a read permission to Cloud Storage. Which is very weird because it should be returning false or size should be 0 or even outputting an error of permission denied.
The official cloud.google.com/go/storage API says Bucket() function returns a BucketHandle that does not actually perform any network operations, which makes sense as to why I was not getting permission denied error (?). And so I decided to check if I am actually getting anything from the Client or Bucket, just to see if the read permission is working. I added the following code :
attr, err := bucket.Attrs(contextBackground)
if err != nil {
fmt.Println(err.Error())
}
fmt.Println("bucket name : " + attr.Name)
And at this point I started to get fatal errors that shuts down my application. The error I got when trying to retrieve bucket's attributes is this :
Get https://www.googleapis.com/storage/v1/b/mytestbucketname?alt=json&prettyPrint=false&projection=full: oauth2: cannot fetch token: Post https://oauth2.googleapis.com/token: x509: certificate signed by unknown authority
So I thought I need to add ca certificate to my image. But it just does not sound logical to me because if I was running my image in Google Kubernetes Engine and it was accessing my Google Cloud Storage, given that it already has a default service account with read permission, why would I need a certificate? I made a new cluster with version 1.12.8-gke.10 and made sure to disable issue client certificate and made sure I had read permission to Storage and still got the same error. And I added this line to my docker file and still got the same error:
RUN apk --no-cache add ca-certificates && update-ca-certificates
I've been at it for two days and now I'm running out of ideas. My question is what am I doing wrong that keeps giving me "x.509 certificate signed by unknown authority" error when trying to access the Storage bucket attributes from Kubernetes Engine when I am using default permission? Technically getting bucket attributes is just a read function, which I am supposed to be able to do with default permission right? If anyone has any ideas or has ever ran into the same problem, please give me some help! Thanks!
If you are using the default cluster service account, you need to make sure the cluster has sufficient scopes to write to storage. By default, GKE clusters only have the read scope which blocks write attempts to GCS buckets.
gke-default:
https://www.googleapis.com/auth/devstorage.read_only
https://www.googleapis.com/auth/logging.write
https://www.googleapis.com/auth/monitoring
https://www.googleapis.com/auth/service.management.readonly
https://www.googleapis.com/auth/servicecontrol
https://www.googleapis.com/auth/trace.append
Without the write scope enabled, regardless of the credentials, you won't be able to write to buckets.
Note: You can use the access token only for scopes that you specified when you created the instance. For example, if the instance has been granted only the https://www.googleapis.com/auth/storage-full scope for Google Cloud Storage, then it can't use the access token to make a request to BigQuery.
If you want to bypass scopes, create a custom service account for the application, download the JWT token and mount these credentials directly into your code. This is the recommended method to authenticate your application against Google APIs
So I finally figured it out, just leaving this here if anyone ever runs into this problem too. It was my mistake of being very careless, but after reading this question :
Cannot exchange AccessToken from Google API inside Docker container
I was able to narrow it down to a certificate problem. It was actually because I did not install the ca-certificate properly. Because I was using multi stage build in my docker file, I misplaced this line of code :
RUN apk --no-cache add ca-certificates && update-ca-certificates
After placing it correctly it worked!

Accessing Google Reseller API using Service Accounts

We are having issues accessing the reseller api using service accounts.
The example with client secrets work well, but we would need to deploy this in k8s (Kubernetes Engine) without the need to refresh the oauth session on a recurring basis (especially doing this once, as it is kinda hard in a docker container).
While there is a lot of documentation on how to do this with python we could not find any way of getting access using a service account.
We tried two accounts, the default compute engine one and one created directly for our use case.
Both got the reseller scope in G Suite.
https://www.googleapis.com/auth/apps.order,
https://www.googleapis.com/auth/admin.directory.user,
https://www.googleapis.com/auth/siteverification,
We keep getting "googleapi: Error 403: Authenticated user is not authorized to perform this action., insufficientPermissions" errors though when using
client, err = google.DefaultClient(ctx, reseller.AppsOrderScope)
if err != nil {
log.Fatal("creating oauth client failed", zap.Error(err))
}
subs, err := client.Subscriptions.List().Do()
if err != nil {
log.Fatal("listing subscriptions failed", zap.Error(err))
}
I read on a post on StackOverflow, that the Reseller API requires user impersonation but searching on this throughout Google and the oauth2 and client lib repos did not result in a way to do this.
Python does this like described in the End-to-End Tutorial
credentials = ServiceAccountCredentials.from_json_keyfile_name(
JSON_PRIVATE_KEY_FILE,
OAUTH2_SCOPES).create_delegated(RESELLER_ADMIN_USER)
but for Go i could not find any documented way of doing this.
So few points here:
Reseller API only requires impersonation / domain-wide delegation when using a service account. In other words, the service account itself has no rights to call the API directly but it does have the ability to impersonate a reseller user (e.g. admin#reseller.example.com or such) who has rights to call the Reseller API.
You may be able to use regular 3-legged OAuth instead of a service account. You just need to make sure you request offline access so that you get a refresh token that is long lived.
Impersonation / domain-wide delegation is not compatible with the default service accounts built-in to AppEngine and ComputeEngine. You must use a service account you created in your API project.
See if the samples Google provides get you where you need to be.
The problem was resolved by using a different config and then setting the jwt.Subject which apparently does impersonation:
const envVar = "GOOGLE_APPLICATION_CREDENTIALS"
if filename := os.Getenv(envVar); filename != "" {
serviceAccount, err := ioutil.ReadFile(filename)
if err != nil {
log.Fatal("creating oauth client failed", zap.Error(err))
}
config, err := google.JWTConfigFromJSON(serviceAccount,
reseller.AppsOrderScope,
)
config.Subject = *impersonationUser // like user#google.com
client = config.Client(ctx)
}

Resources