Having spent hours looking for an answer on how to access the Gmail API with the use of a service account and saw that I can't, unless I'm using a GSuite account that it's provided with domain-wide authorization, I came here to ask you if there's a way to actually create labels using the said API and a private account. I'm using Visual Studio 2019 and C#. In the "developers.google.com" there's a tool called "Try this API" and I can create a label using my OAuth 2.0 just fine, and the .NET Quickstart found here also works in listing my labels. But why can't it let me create labels? I have enabled all of the scopes possible for this to work.
This is the error I am getting:
"Google.GoogleApiException: 'Google.Apis.Requests.RequestError
Request had insufficient authentication scopes. [403]
Errors [
Message[Insufficient Permission] Location[ - ] Reason[insufficientPermissions] Domain[global]" enter image description here
The method Lables.create requires permissions in order to create labels on the users account. The user must have consented to that permission.
the error message
Google.Apis.Requests.RequestError Request had insufficient authentication scopes.
Is telling you that the user has not consented to the proper scope. The user must have consented to one of the following scopes
If you followed the quick start then you probably only included GmailService.Scope.GmailReadonly. You will need to change the scope and request authorization of the user again. Note that the tutorial you are following is not for service account authencation but rather for Oauth2 authentication.
service account
string ApplicationName = "Gmail API .NET Quickstart";
const string serviceAccount = "xxxxx-smtp#xxxxx-api.iam.gserviceaccount.com";
var certificate = new X509Certificate2(#"c:\xxxxx-api-ed4859a67674.p12", "notasecret", X509KeyStorageFlags.Exportable);
var gsuiteUser = "xxxxx#xxxx.com";
var serviceAccountCredentialInitializer = new ServiceAccountCredential.Initializer(serviceAccount)
{
User = gsuiteUser,
Scopes = new[] { GmailService.Scope.GmailSend, GmailService.Scope.GmailLabels }
}.FromCertificate(certificate);
var credential = new ServiceAccountCredential(serviceAccountCredentialInitializer);
if (!credential.RequestAccessTokenAsync(CancellationToken.None).Result)
throw new InvalidOperationException("Access token failed.");
var service = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
Related
We passed the verification to start using calendar.events.readonly scope on breess.com service.
We created the service account with wide delegation
In G Suite domain’s admin console added scope https://www.googleapis.com/auth/calendar.events.readonly
The user account xxxxx#xxx.com granted access to his calendar
Everything looks configured. The code below is executed on the host and returns the error:
Error:"unauthorized_client", Description:"Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested.", Uri:""
Have we missed something? Could you please guide us to resolve the issue?
Extra note:
var credential = GoogleCredential
.FromFile("breess-7798c393d5b0.json")
.CreateScoped(new[] { CalendarService.Scope.CalendarEventsReadonly })
.createwithuser("xxx#xxx.xxx");
var calendarService = new CalendarService(new BaseClientService.Initializer { HttpClientInitializer = credential, ApplicationName = "BREESS" });
var request = calendarService.Events.List(calendarId);
var events = await request.ExecuteAsync();
Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested.
There are three ways of authorizing Oauth with google. They each have their own type of credential file and code needed to use them.
Installed application
Web application
Service account application
API key for public access only
When you create the project on Google developer console you need to be sure that you have created service account credentials. Then you need to be sure that you are using the code that was intended for use with service account credentials
var scopes = new[] { CalendarService.Scope.CalendarEventsReadonly }
GoogleCredential credential;
using (var stream = new FileStream(serviceAccountCredentialFilePath, FileMode.Open, FileAccess.Read))
{
credential = GoogleCredential.FromStream(stream)
.CreateScoped(scopes);
}
credential.User = "domainuser#yourdomain.com";
// Create the Calendar service.
return new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Calendar Service account Authentication Sample",
}
);
I dont see anything major wrong with your code so i suspect the issue is that you do not have the correct type of credential file.
I want to update signature of a domain user via Gmail .Net Client Library as G Suite administrator.
I followed this example to setup and give permissions in google developer console.
Here is the summary what I did:
Enabled Gmail API in Google API Console
Created a Service Account for my project
Delegated domain-wide authority to the Service Account
Added https://www.googleapis.com/auth/gmail.settings.sharing, https://www.googleapis.com/auth/gmail.settings.basic scopes
I'm able to update my own email signature but when I try to update signature of any other user on my domain I get following error:
Google.Apis.Requests.RequestError
Not Found [404]
Errors [
Message[Not Found] Location[ - ] Reason[notFound] Domain[global]
]
Here's my code:
GoogleCredential credential;
using (var stream = new FileStream("signature-gmailapi.json", FileMode.Open, FileAccess.Read))
{
credential = GoogleCredential.FromStream(stream).CreateScoped(new[]
{GmailService.Scope.GmailSettingsBasic, GmailService.Scope.GmailSettingsSharing})
.CreateWithUser("admin#domain.com");
}
var service = new GmailService(new BaseClientService.Initializer()
{
ApplicationName = "Project",
HttpClientInitializer = credential
}
);
service.Users.Settings.SendAs.Patch(new SendAs { Signature = "Test signature..."}, "me",
"user#domain.com").Execute();
Can you anyone please guide me what I' doing wrong?
To change a domain user's signature with the service account, you need to use impersonation
This is achieved with the method CreateWithUser();
The parameter you need to assign to it, is the USER's email, not the admin's one. So:
credential = GoogleCredential.FromStream(stream).CreateScoped(new[]{GmailService.Scope.GmailSettingsBasic, GmailService.Scope.GmailSettingsSharing})
.CreateWithUser( "user#domain.com");
I am attempting to transfer ownership from a Service Account created document to another user who resides within the same Google Apps account using the code below but am getting the following error
The resource body includes fields which are not directly writable. [403]
Errors [Message[The resource body includes fields which are not directly writable.] Location[ - ] Reason[fieldNotWritable] Domain[global]]
var service = GetService();
try
{
var permission = GetPermission(fileId, email);
permission.Role = "owner";
var updatePermission = service.Permissions.Update(permission, fileId, permission.Id);
updatePermission.TransferOwnership = true;
return updatePermission.Execute();
}
catch (Exception e)
{
Console.WriteLine("An error occurred: " + e.Message);
}
return null;
Commenting out // permission.Role = "owner"; returns the error below
The transferOwnership parameter must be enabled when the permission role is 'owner'. [403] Errors [Message[The transferOwnership parameter must be enabled when the permission role is 'owner'.] Location[transferOwnership - parameter] Reason[forbidden] Domain[global]]
Assigning any other permissions works fine. Therefore, is this a limitation of the Service Account not being able to transfer ownership to any other account that doesn't use the #gserviceaccount.com email address (i.e. our-project#appspot.gserviceaccount.com > email#domain.com)?
The email#domain.com email address has been created and is managed within Google Apps.
In the case, it is not achievable, any pointers on where to look next? We need multiple users to have the ability to create documents ad hoc and assign permissions and transfer ownership on the fly via the API.
Thanks
I have found the answer and am posting for anyone else who comes across this question.
You can not use the 'Service Account Key JSON file' as recommended by Google.
You need to use the p.12 certificate file for authentication.
The code to create a drive service for mimicking accounts is as follows.
public DriveService GetService(string certificatePath, string certificatePassword, string googleAppsEmailAccount, string emailAccountToMimic, bool allowWrite = true)
{
var certificate = new X509Certificate2(certificatePath, certificatePassword, X509KeyStorageFlags.Exportable);
var credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(googleAppsEmailAccount)
{
Scopes = new[] { allowWrite ? DriveService.Scope.Drive : DriveService.Scope.DriveReadonly },
User = emailAccountToMimic
}.FromCertificate(certificate));
// Create the service.
return new DriveService(new BaseClientService.Initializer
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName
});
}
You need to follow the steps listed here to delegate domain-wide authority to the service account.
Allow 5 to 10 minutes after completing step 4.
You can now create documents under the 'emailAccountToMimic' user which sets them to be the owner during creation.
I don't think it is possible to transfer the ownership from a non-ServiceAccount to a ServiceAccount, vice versa.
If you do that interactively, you will get the below error:
Typically, the document can be created and owned by the users and ownership transfer can be done using their own credentials. You will also have the option to impersonate as the owner if your Service Account is granted with the domain-wide delegation correctly.
I am trying to download a user's mailbox using the Email Audit API. I am getting a 403 Forbidden response to this code (the error occurs on the last line, the call to the UploadPublicKey method):
var certificate = new X509Certificate2(System.Web.HttpRuntime.AppDomainAppPath + "key.p12", "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new[] { "https://apps-apis.google.com/a/feeds/compliance/audit/" }
}.FromCertificate(certificate));
credential.RequestAccessTokenAsync(System.Threading.CancellationToken.None).Wait();
DebugLabel.Text = credential.Token.AccessToken;
var requestFactory = new GDataRequestFactory("My App User Agent");
requestFactory.CustomHeaders.Add(string.Format("Authorization: Bearer {0}", credential.Token.AccessToken));
AuditService aserv = new AuditService(strOurDomain, "GoogleMailAudit");
aserv.RequestFactory = requestFactory;
aserv.UploadPublicKey(strPublicKey);
I have created the service account in the Developers Console and granted the Client ID access to https://apps-apis.google.com/a/feeds/compliance/audit/ in the Admin console.
Seems to me like the account should have all the permissions it needs, yet it doesn't. Any idea what I am missing?
OK, so I gave up on trying to make it work with a service account even though that is what Google's documentation would lead you to believe is the correct way to do it. After emailing Google support, I learned I could just use OAuth2 for the super user account that created the application on the developer's console.
So then I worked on getting an access token for offline access (a refresh token) by following the process outlined here:
Youtube API single-user scenario with OAuth (uploading videos)
and then taking that refresh token and using it with this code:
public static GOAuth2RequestFactory RefreshAuthenticate(){
OAuth2Parameters parameters = new OAuth2Parameters(){
RefreshToken = "<YourRefreshToken>",
AccessToken = "<AnyOfYourPreviousAccessTokens>",
ClientId = "<YourClientID>",
ClientSecret = "<YourClientSecret>",
Scope = "https://apps-apis.google.com/a/feeds/compliance/audit/",
AccessType = "offline",
TokenType = "refresh"
};
OAuthUtil.RefreshAccessToken(parameters);
return new GOAuth2RequestFactory(null, "<YourApplicationName>", parameters);
}
which is code from here https://stackoverflow.com/a/23528629/5215904 (Except I changed the second to last line... for whatever reason the code shared did not work until I made that change).
So there I was finally able to get myself an access token that would allow me access to the Email Audit API. From there everything was a breeze once I stopped trying to mess around with a service account.
Have a really strange issue going on. Trying to implement Google's OAuth 2.0 in a ASP.NET (non-MVC) scenario to Google Calendar API. I see the token response in the storage area, no errors encountered.
Here's the code:
public CalendarService Credential(string sUserID)
{
CalendarService service = new CalendarService();
var folder = #"C:\TEMP\GoogleStorage";
UserCredential credential = null;
string clientId = "{client id is redacted}";
string clientSecret = "{client secret is redacted}";
string[] scopes = new string[] {CalendarService.Scope.Calendar};
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets
{
ClientId = clientId,
ClientSecret = clientSecret
},
scopes,
sUserID,
CancellationToken.None,
new FileDataStore(folder)
).Result;
}
service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Interpose Concept"
});
return service;
}
Here's the series of events:
I hit the above code and get a successful credential and a successful service returned. Google Account A using Chrome gets prompted to allow application to access the calendar. A token in stored at c:\temp\GoogleStorage
I am able to use the returned service to query User A's calendar.
Google Account B (simulating a different user hitting the same web server) using Firefox goes through the same code. A different UserID is passed into Credential based on this new session. Again, there's a successful credential to Google, this time userID is different, but this time no requiring to allow access to their Calendar. I am able to successfully return the service, but the service is for Google Account A's calendar.
I am certain I am missing something along the lines of being able to segregate the different Google Accounts, but I don't see how to separate the different users hitting this web server other than the user parameter in the AuthorAsync call I make. I further don't understand why it would retain the first account's credential.
What's blowing my mind is this seems to be working (first request is good, no errors, can query the calendar), but all subsequent requests seem to be tied to the first credential request that works.
What am I not understanding?