Google Drive API - Transfer ownership from Service Account - c#

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.

Related

Create labels with GMail API in C#

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,
});

Microsoft Graph API - Sending email as another user

In our application, we need to send notifications to users by email for various event triggers.
I'm able to send email if I send as "Me" the current user, but trying to send as another user account returns an error message and I'd prefer it if notifications didn't come users' themselves and may contain info we don't want floating around in Sent folders.
What works:
await graphClient.Me.SendMail(email, SaveToSentItems: false).Request().PostAsync();
What doesn't work:
string FromUserEmail = "notifications#contoso.com";
await graphClient.Users[FromUserEmail].SendMail(email, SaveToSentItems: false).Request().PostAsync();
Also tried using the user object id directly:
await graphClient.Users["cd8cc59c-0815-46ed-aa45-4d46c8a89d72"].SendMail(email, SaveToSentItems: false).Request().PostAsync();
My application has permissions for the Graph API to "Send mail as any user" enabled and granted by the owner/administrator.
The error message returned by the API:
Code: ErrorFolderNotFound Message: The specified folder could not be
found in the store.
I thought this error might have been because the notifications account didn't have a sent folder, so I set the SaveToSentItems value to false, but I still get the same error.
Are there any settings I need to check on the account itself to allow the app to send mail on this account or should this work?
I have checked out the documentation here:
https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_sendmail
Which appears to support what I'm trying to do, but doesn't reference any folder except for the sent items folder which I'm telling the API not to save to anyway.
We aren't intending to impersonate any actual user here, just send notification emails from within the app from this specific account (which I know is technically impersonation, but not of a real entity).
So like Schwarzie2478 we used a noreply#ourcompany.com address. But our AD is federated which means you can't use Username\Password auth and we didn't want to use the Application Mail.Send permission since it literally can send as anyone and there is no way IT Security would let that fly. So we used Windows Authentication instead.
This requires that you grant consent to the app to use the mail.send and user.read delegate permissions by going to https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/authorize?client_id={clientId}&response_type=code&scope=user.read%20mail.send and logging in with the windows user that the app will run as.
More info on using windows auth here: https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/Integrated-Windows-Authentication
// method call
var t = SendEmailUsingGraphAPI();
t.Wait();
// method
static async Task<Boolean> SendEmailUsingGraphAPI() {
// AUTHENTICATION
var tenantID = "YOUR_TENANT_ID"; //azure ad tenant/directory id
var clientID = "YOUR_APPS_CLIENT_ID"; // registered app clientID
var scopes = "user.read mail.send"; // DELEGATE permissions that the request will need
string authority = $"https://login.microsoftonline.com/{tenantID}";
string[] scopesArr = new string[] { scopes };
try {
IPublicClientApplication app = PublicClientApplicationBuilder
.Create(clientID)
.WithAuthority(authority)
.Build();
var accounts = await app.GetAccountsAsync();
AuthenticationResult result = null;
if (accounts.Any()) {
result = await app.AcquireTokenSilent(scopesArr, accounts.FirstOrDefault())
.ExecuteAsync();
}
else {
// you could acquire a token by username/password authentication if you aren't federated.
result = await app.AcquireTokenByIntegratedWindowsAuth(scopesArr)
//.WithUsername(fromAddress)
.ExecuteAsync(CancellationToken.None);
}
Console.WriteLine(result.Account.Username);
// SEND EMAIL
var toAddress = "EMAIL_OF_RECIPIENT";
var message = "{'message': {'subject': 'Hello from Microsoft Graph API', 'body': {'contentType': 'Text', 'content': 'Hello, World!'}, 'toRecipients': [{'emailAddress': {'address': '" + result.Account.Username + "'} } ]}}";
var restClient = new RestClient("https://graph.microsoft.com/v1.0/users/" + result.Account.Username + "/sendMail");
var request = new RestRequest(Method.POST);
request.AddHeader("Content-Type", "application/json");
request.AddHeader("Authorization", "Bearer " + result.AccessToken);
request.AddParameter("", message, ParameterType.RequestBody);
IRestResponse response = restClient.Execute(request);
Console.WriteLine(response.Content);
}
catch (Exception e) {
Console.WriteLine(e.Message);
throw e;
}
return true;
}
Whenever you are using delegated permissions (i.e. when a user is logged in), even though your admin has consented to the Mail.Send.Shared, it does NOT grant access to all mailboxes in the tenant. These OAuth permissions do not override the permissions (and restrictions) in place for the user.
If the user is not already configured with permissions to be able to "Send As" the notifications#contoso.com user, then you'll see this error.
To make it work, you'd need to actually grant "Send As" rights to all users that will be using your application.
This is a subtle thing, and granted it's a bit confusing. In the Azure portal, the permissions have slightly different descriptions, depending on if you're looking at the Application Permissions or the Delegated Permissions.
Application: Send mail as any user
Delegated: Send mail on behalf of others
Since you're using delegated, the permission doesn't allow you to send as any user, only send on behalf of any folks that the logged on user has rights to send as.
Another approach you could use here to avoid having to grant these rights to all users (which would allow them to send via Outlook, etc.) would be to have your backend app use the client credentials flow to get an app-only token. In that case, the app itself would have the permission to send as any user.
I don't know what others will have done for this, but I contacted Microsoft about this exact scenario: I want to send a mail as a fixed user ( noreply#mycompany.com) which has a mailbox in Azure. I want to send this mail from different applications or services.
The person there told me that sending a mail with no user logging in, is only possible with an delegated user token.
So we configured our application as an Native application in Azure like for mobile apps. Logging in for this application with the technical user during a setup phase gives me a delegated user token for that specific user which can be stored in a mailing service or component. This token does not expire ( at least not until the security changes of the user like password or something) and can be used to call the graph api to send mails when you give permission for this account to be sending mails from.
Next to that we even associated other shared mailboxes to this accounts to be able to send mails for those mailboxes too.
Documentation:
First You need a native app registration in Azure ( not an Web API):
https://learn.microsoft.com/en-us/azure/active-directory/develop/native-app
This app only requires an one-time login and approval from an user to get a token which can represent that user indefinitly. We set up a mail user account to be used for this. That token is then used to get access token to Graph Api for sending mails and such
Token Handling example:
https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/token-cache-serialization
With an identitytoken stored ( usually a .cache file somewhere) you can request an accesstoken:
Identity Client:
https://learn.microsoft.com/en-us/dotnet/api/microsoft.identity.client.publicclientapplication?view=azure-dotnet
_clientApp = new PublicClientApplication(ClientId, "https://login.microsoftonline.com/{xxx-xxx-xx}, usertoken,...
authResult = await _clientApp.AcquireTokenSilentAsync(scopes,...
private static string graphAPIEndpoint = "https://graph.microsoft.com/v1.0/me";
//Set the scope for API call to user.read
private static string[] scopes = new string[] { "user.read", "mail.send" };
private const string GraphApi = "https://graph.microsoft.com/";
var graphclient = new GraphServiceClient($"{GraphApi}/beta",
new DelegateAuthenticationProvider(
(requestMessage) =>
{
// inject bearer token for auth
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", authResult.AccessToken);
return Task.FromResult(0);
}));
var sendmail = graphclient.Users[User].SendMail(mail), true);
try
{
await sendmail.Request().PostAsync();
}
catch (Exception e)
{

How to authenticate with OAuth to access EWS APIs

My web service is currently doing basic username/password authentication in order to subscribe the exchange user for receiving the events (like new mail event etc) like below:
var service = new ExchangeService(exchangeVersion)
{
KeepAlive = true,
Url = new Uri("some autodiscovery url"),
Credentials = new NetworkCredential(username, password)
};
var subscription = service.SubscribeToPushNotifications(
new[] { inboxFolderFoldeID },
new Uri("some post back url"),
15,
null,
EventType.NewMail,
EventType.Created,
EventType.Deleted,
EventType.Modified,
EventType.Moved,
EventType.Copied);
Now, I am supposed to replace the authentication mechanism to use OAuth protocol. I saw some examples but all of them seem to be talking about authenticating the client (https://msdn.microsoft.com/en-us/library/office/dn903761%28v=exchg.150%29.aspx?f=255&MSPPError=-2147217396) but nowhere I was able to find an example of how to authenticate an exchange user with OAuth protocol. Any code sample will help a lot. Thanks.
It's not clear what you mean with 'web service' and how you currently get the username and password. If that is some kind of website where the user needs to login or pass credentials, then you'll have to start an OAuth2 grant from the browser as in redirecting the clients browser to the authorize endpoint to start implicit grant or code grant. The user will be presented a login screen on the OAuth2 server (and not in your application), once the user logs in a code or access token (depending on the grant) will be returned to your application which you can use in the ExchangeService constructor.
If that 'web' service is some service that runs on the users computer you can use one of the methods described below.
Get AccessToken using AuthenticationContext
The example seems to be based on an older version of the AuthenticationContext class.
The other version seems to be newer, also the AcquireToken is now renamed to AcquireTokenAsync / AcquireTokenSilentAsync.
No matter which version you're using, you will not be able to pass username and password like you're doing in your current code. However, you can let the AcquireToken[Async] method prompt for credentials to the user. Which, let's be honest, is more secure then letting your application deal with those user secrets directly. Before you know, you'll be storing plain text passwords in a database (hope you aren't already).
In both versions, those methods have a lot of overloads all with different parameters and slightly different functionality. For your use-case I think these are interesting:
New: AcquireTokenAsync(string, string, Uri, IPlatformParameters) where IPlatformParameters could be new PlatformParameters(PromptBehavior.Auto)
Old: AcquireToken(string, string, Uri, PromptBehavior where prompt behavior could be PromptBehavior.Auto
Prompt behavior auto, in both vesions, means: the user will be asked for credentials when they're not already cached. Both AuthenticationContext constructors allow you to pass a token-cache which is something you can implement yourself f.e. to cache tokens in memory, file or database (see this article for an example file cache implementation).
Get AccessToken manually
If you really want to pass in the user credentials from code without prompting the user, there is always a way around. In this case you'll have to implement the Resource Owner Password Credentials grant as outlined in OAuth2 specificatioin / RFC6749.
Coincidence or not, I have an open-source library called oauth2-client-handler that implements this for use with HttpClient, but anyway, if you want to go this route you can dig into that code, especially starting from this method.
Use Access Token
Once you have an access token, you can proceed with the samples on this MSDN page, f.e.:
var service = new ExchangeService(exchangeVersion)
{
KeepAlive = true,
Url = new Uri("some autodiscovery url"),
Credentials = new OAuthCredentials(authenticationResult.AccessToken))
};
In case someone is still struggling to get it to work. We need to upload a certificate manifest on azure portal for the application and then use the same certificate to authenticate the client for getting the access token. For more details please see: https://blogs.msdn.microsoft.com/exchangedev/2015/01/21/building-daemon-or-service-apps-with-office-365-mail-calendar-and-contacts-apis-oauth2-client-credential-flow/
Using the example code in this Microsoft Document as the starting point and these libraries:
Microsoft Identity Client 4.27
EWS Managed API v2.2
I am able to successfully authenticate and connect with Exchange on Office 365.
public void Connect_OAuth()
{
var cca = ConfidentialClientApplicationBuilder
.Create ( ConfigurationManager.AppSettings[ "appId" ] )
.WithClientSecret( ConfigurationManager.AppSettings[ "clientSecret" ] )
.WithTenantId ( ConfigurationManager.AppSettings[ "tenantId" ] )
.Build();
var ewsScopes = new string[] { "https://outlook.office365.com/.default" };
AuthenticationResult authResult = null;
try
{
authResult = cca.AcquireTokenForClient( ewsScopes ).ExecuteAsync().Result;
}
catch( Exception ex )
{
Console.WriteLine( "Error: " + ex );
}
try
{
var ewsClient = new ExchangeService();
ewsClient.Url = new Uri( "https://outlook.office365.com/EWS/Exchange.asmx" );
ewsClient.Credentials = new OAuthCredentials( authResult.AccessToken );
ewsClient.ImpersonatedUserId = new ImpersonatedUserId( ConnectingIdType.SmtpAddress, "ccc#pppsystems.co.uk" );
ewsClient.HttpHeaders.Add( "X-AnchorMailbox", "ccc#pppsystems.co.uk" );
var folders = ewsClient.FindFolders( WellKnownFolderName.MsgFolderRoot, new FolderView( 10 ) );
foreach( var folder in folders )
{
Console.WriteLine( "" + folder.DisplayName );
}
}
catch( Exception ex )
{
Console.WriteLine( "Error: " + ex );
}
}
The Microsoft example code did not work - the async call to AcquireTokenForClient never returned.
By calling AcquireTokenForClient in a separate try catch block catching a general Exception, removing the await and using .Result, this now works - nothing else was changed.
I realise that this is not best practice but, both with and without the debugger, the async call in the original code never returned.
In the Azure set-up:
A client secret text string was used - a x509 certificate was not necessary
The configuration was 'app-only authentication'
Hope this helps someone avoid hours of frustration.

Google API: ServiceAccount errors with DirectoryService and DriveService using C#

What I'm trying to accomplish:
I have been working the past couple days to implement automation into my company's GoogleApps setup. I need to suspend users that have been inactive for 90+ days, deleting them a week after that. Before deletion, I need to backup any files found in their Google Drive.
Why I haven't accomplished it:
I have attempted this with a few different methods. First I was using the Admin SDK to interface with DirectoryServices, listing users, and suspending as necessary. This interfacing would also allow me to delete users when needed. Now, I need only backup their Google Drive files. For this, it's to my understanding that I need a Service Account to impersonate users, download their files, and relocate them (off to a server in bulk storage probably). I don't think this is possible as an Admin programatically because I believe UserCredentials requires user authorization for each user I intend to interface with via the Drive API, manually. On the other hand, a service account should allow me to impersonate users without authorization, provided the proper delegation.
I have created a ServiceAccount, provided it with Domain-Wide-Delegation, enabled the appropriate APIs (Admin SDK, Drive API) through the developer console, and provided the service account with appropriate scope through Google Admin Console security settings. I think that's all the setup I needed to do.
private static async Task<bool> AuthenticateServiceAccount(string serviceAccountEmail, string keyFilePath)
{
if (!File.Exists(keyFilePath)) // make sure the file we're looking for actually exists
{
Console.WriteLine("Could not find ServAcct key file. Authentication failed!");
return false;
}
string[] scopes = new string[] { DirectoryService.Scope.AdminDirectoryUser, DriveService.Scope.Drive };
// Generate certificate using our key file
var certificate = new X509Certificate2(keyFilePath, "notasecret", X509KeyStorageFlags.Exportable);
try
{
// Create our credential from our certificate, using scopes
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = scopes
}.FromCertificate(certificate));
// Create our services
dirSvc = new DirectoryService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "GoogleAppAutomation"
});
drvSvc = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "GoogleAppAutomation"
});
} catch(Exception e)
{
Console.WriteLine(e.InnerException);
return false;
}
return true;
}
I am able to authenticate the service account in my code using the SA's email, and the P12 cert. Creation of the DirectoryService and DriveService objects using the ServiceAccountCredential works fine. However, when i go to access the services, I am presented with errors that I have failed to find any help for online.
DirectoryService
// Acquire list of active users
UsersResource.ListRequest req = dirSvc.Users.List();
req.Customer = "my_customer";
req.MaxResults = 500; // 500 is max allowed. Must use paging (.pageToken) for more
req.OrderBy = UsersResource.ListRequest.OrderByEnum.Email;
IList<User> users;
try
{
users = req.Execute().UsersValue; // Execute request for user list
}
catch (Exception e)
{
throw new Exception("User List Exception", e);
}
Sure enough, the catch block will throw an exception containing:
Google.Apis.Requests.RequestError
Domain not found. [404]
Errors [
Message[Domain not found.] Location[ - ] Reason[notFound] Domain[global]
]
DriveService
FilesResource.ListRequest lr = drvSvc.Files.List();
IList<Google.Apis.Drive.v2.Data.File> fl = lr.Execute().Items;
foreach (Google.Apis.Drive.v2.Data.File file in fl)
{
Console.WriteLine("{0}", file.Title);
}
Just as above, the Execute() method throws an exception...
Google.Apis.Requests.RequestError
Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup. [403]
Errors [
Message[Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup.] Location[ - ] Reason[dailyLimitExceededUnreg] Domain[usageLimits]
]
Looking in the Developer Console, this API has had no use at all in the Quota log.
So, I suppose my question is a very broad: what am I doing wrong? :(
Edit:
I believe the issue pertaining to Google Drive access relates to a lack of Billing setup. I cannot be sure of this as of yet because my company is being very slow to get such a thing setup. If anyone can confirm or deny this for me, that'd help!
As for the Directory Service issue, I'll just use the Admin SDK to access the users for suspension/deletion, and the service account for Drive backup. I already have the Admin SDK working for this purpose, so no further assistance is needed in that regard (though an explanation of the error would be awesome since it doesn't appear to be documented whatsoever online!)

Email Audit API - 403 Forbidden

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.

Categories

Resources