googleads / google-ads-dotnet

This project hosts the .NET client library for the Google Ads API.
https://developers.google.com/google-ads/api
Apache License 2.0
72 stars 95 forks source link

Google.Ads API Request contains an invalid argument #261

Closed ToddEvansHome closed 3 years ago

ToddEvansHome commented 3 years ago

I am receiving the following error message during a SearchStream requests _Status(StatusCode="InvalidArgument", Detail="Request contains an invalid argument.", DebugException="Grpc.Core.Internal.CoreErrorDetailException: {"created":"@1611847453.657000000","description":"Error received from peer ipv6:[2607:f8b0:4009:802::200a]:443","file":"......\src\core\lib\surface\call.cc","file_line":1063,"grpc_message":"Request contains an invalid argument.","grpcstatus":3}")

Which version of the client library are you using? Google.Ads.GoogleAds v6.1.0 Which version of .NET are you using? .NET 5 Which operating system (Linux, Windows, ...) and version? Windows

Actions taken `GoogleAdsServiceClient _googleAdsService;

public static ICredential GetGoogleServiceCredential() { var googleCredential = GetGoogleCredentialsFromJSONKey( "Keys/xxxxxxx.json",
new string[] { "https://www.googleapis.com/auth/sqlservice.admin", "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/adwords"} );
return googleCredential;
}`

`///


/// Authenticate using a Service Account
///

/// Path to your JSON Key file
/// Scopes required in access token
/// Google Credential
public static ICredential GetGoogleCredentialsFromJSONKey(string jsonKeyFilePath, params string[] scopes) { using (var stream = new FileStream(jsonKeyFilePath, FileMode.Open, FileAccess.Read)) { return GoogleCredential .FromStream(stream) // Loads key file
.CreateScoped(scopes) // Gathers scopes requested
.UnderlyingCredential; // Gets the credentials

}

}`

` //This is a windows service starting point protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); await Task.Delay(1000, stoppingToken);

try
{
    ICredential credential = GetGoogleServiceCredential();

    // this returns a token successfully
    //var accessToken = await credential.GetAccessTokenForRequestAsync(cancellationToken: stoppingToken);

    var googleAdsServiceClientBuilder = new GoogleAdsServiceClientBuilder
    {
        TokenAccessMethod = credential.GetAccessTokenForRequestAsync
    };
    _googleAdsService = googleAdsServiceClientBuilder.Build();                              

    Campaings_Get();

}            
catch (Exception ex)
{
    throw;
}

}`

// Make a simple query call private void Campaings_Get() { string query = "SELECT campaign.id, campaign.name FROM campaign"; GoogleAdsService_SearchStream(customerID, query, Campaings_Show); } // Write results to console private void Campaings_Show(GoogleAdsRow googleAdsRow) { Console.WriteLine("Campaign with ID {0} and name '{1}' was found.", googleAdsRow.Campaign.Id, googleAdsRow.Campaign.Name); } ` // Query Google Ads protected void GoogleAdsService_SearchStream(long customerId, string query, Action writeLine) {

  try
  {
      Console.WriteLine($"{writeLine.Method.Name }");

      // Issue a search request.
      _googleAdsService.SearchStream(customerId.ToString(), query,
          delegate (SearchGoogleAdsStreamResponse resp)
          {
              foreach (GoogleAdsRow googleAdsRow in resp.Results)
              {  
                  writeLine(googleAdsRow);
              }
          }
      );
      Console.WriteLine($"{writeLine.Method.Name} Done"); 
      Console.WriteLine();
  }
  catch (GoogleAdsException e)
  {
      Console.WriteLine("Failure:");
      Console.WriteLine($"Message: {e.Message}");
      Console.WriteLine($"Failure: {e.Failure}");
      Console.WriteLine($"Request ID: {e.RequestId}");
      throw;
  }
  catch (Exception ex)
  {
      throw;
  }

}` Expected result "SearchGoogleAdsStreamResponse resp": is populated with records returned

Actual result One or more errors occurred. (Status(StatusCode="InvalidArgument", Detail="Request contains an invalid argument.", DebugException="Grpc.Core.Internal.CoreErrorDetailException: {"created":"@1611847453.657000000","description":"Error received from peer ipv6:[2607:f8b0:4009:802::200a]:443","file":"......\src\core\lib\surface\call.cc","file_line":1063,"grpc_message":"Request contains an invalid argument.","grpc_status":3}"))

Stack trace at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken) at System.Threading.Tasks.Task.Wait() at Google.Ads.GoogleAds.V6.Services.GoogleAdsServiceClient.SearchStream(String customerId, String query, Action1 responseCallback, SummaryRowSetting summaryRowSetting, CallSettings callSettings) at PaidSearchWindowsService.Worker.GoogleAdsService_SearchStream(Int64 customerId, String query, Action1 writeLine) in C:\MMiProjects\PaidSearchWindowsService\PaidSearchWindowsService\Worker.cs:line 277

Anything else we should know about your project / environment If we use Google Desktop Client ID for authentication instead of Service Account calling the same query works.
We did this by first populating GoogleAdsConfig config, create GoogleAdsClient, then call GetService to acquire GoogleAdsServiceClient GoogleAdsClient_client = new GoogleAdsClient(config); // Get the GoogleAdsService. _googleAdsService = _client.GetService(Services.V6.GoogleAdsService);

AnashOommen commented 3 years ago

The service account flow will also work just fine, if you were to use the GoogleAdsConfig approach. For instance,

GoogleAdsConfig config = new GoogleAdsConfig()
{
    OAuth2ClientId = xxx,
    OAuth2SecretsJsonPath = xxx,
    OAuth2Scope = xxx,
    OAuth2Mode = OAuth2Flow.SERVICE_ACCOUNT,
    OAuth2PrnEmail = xxx
};

GoogleAdsClient_client = new GoogleAdsClient(config);
googleAdsService = _client.GetService(Services.V6.GoogleAdsService);

The minor caveat is that OAuth2Scope can only take a single string, not an array of strings. I can push a fix for this next week.

The service builder approach doesn't work because Google Ads services require additional wiring which is not supported by the default builder stubs. I'll mark them as internal to avoid confusion.

ToddEvansHome commented 3 years ago

I have tried the following and receive the below exception

GoogleAdsConfig config = new GoogleAdsConfig() { OAuth2ClientId = "######", OAuth2SecretsJsonPath = "Keys/adbdff8f747f.json", OAuth2Scope = "https://www.googleapis.com/auth/adwords", OAuth2Mode = OAuth2Flow.SERVICE_ACCOUNT, OAuth2PrnEmail = "xxxxxxxxx.iam.gserviceaccount.com", DeveloperToken = "#######" };

Exception: Message: Status(StatusCode="Unauthenticated", Detail="Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",

DebugException="Grpc.Core.Internal.CoreErrorDetailException: {"created":"@1613243724.561000000","description": "Error received from peer ipv6:[2607:f8b0:4009:80f::200a]:443","file":"......\src\core\lib\surface\call.cc","file_line":1063, "grpc_message":"Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.","grpc_status":16}")

Failure: { "errors": [ { "errorCode": { "authenticationError": "NOT_ADS_USER" }, "message": "User in the cookie is not a valid Ads user." } ] } Request ID: aXZK5HWECFCmv6Qsi4s-Yw

The *.json file is the file we received from our Service Account

AnashOommen commented 3 years ago

Hi,

The OAuth2PrnEmail field should point to the email account in your GSuite domain you are impersonating. This user should also be a valid user in your Google Ads account.

The way you make the call seems fine to me.

AnashOommen commented 3 years ago

Temporarily closing this issue, feel free to reopen if the issue persists.

ToddEvansHome commented 3 years ago

We do not have a GSuite domain. The user we are using for OAuth2PrnEmail is the owner of the Google Ads account. We are currently getting the below error. How may we use Service Account without a GSuite domain.

An error occurred while authorizing the user.: System.AggregateException: One or more errors occurred. (Status(StatusCode="Unavailable", Detail="Getting metadata from plugin failed with error: Exception occurred in metadata credentials plugin. Google.Apis.Auth.OAuth2.Responses.TokenResponseException: 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:"" at Google.Apis.Auth.OAuth2.Responses.TokenResponse.FromHttpResponseAsync(HttpResponseMessage response, IClock clock, ILogger logger) at Google.Apis.Auth.OAuth2.Requests.TokenRequestExtenstions.ExecuteAsync(TokenRequest request, HttpClient httpClient, String tokenServerUrl, CancellationToken taskCancellationToken, IClock clock, ILogger logger) at Google.Apis.Auth.OAuth2.ServiceAccountCredential.RequestAccessTokenAsync(CancellationToken taskCancellationToken) at Google.Apis.Auth.OAuth2.TokenRefreshManager.RefreshTokenAsync() at Google.Apis.Auth.OAuth2.TokenRefreshManager.GetAccessTokenForRequestAsync(CancellationToken cancellationToken) at Google.Apis.Auth.OAuth2.ServiceAccountCredential.GetAccessTokenForRequestAsync(String authUri, CancellationToken cancellationToken) at Google.Apis.Auth.OAuth2.ServiceCredential.GetAccessTokenWithHeadersForRequestAsync(String authUri, CancellationToken cancellationToken) at Grpc.Auth.GoogleAuthInterceptors.<>c__DisplayClass3_0.<b__0>d.MoveNext() ...{brevity}

AnashOommen commented 3 years ago

@ToddEvansHome you cannot use service account setup with Google Ads without GSuite domain. That's a limitation of how Google Ads (the product) works, and not the .NET library per-se.

You need to do the installed application flow with offline access instead (the flow involving refresh tokens).

ToddEvansHome commented 3 years ago

Hi Anash, Thank you for your quick response,

Clarification: We are using Google Workspace(formerly known as G Suite) and we do have both Google Ads & Google Ads Manager services enabled within the Google Admin.. The email I am adding to the OAuth2PrnEmail property is within Google Workspace and that email is "Service Account Token Creator Owner" role for our service account in Google Ads.

I must be missing something. Any help would be greatly appreciated.

On Thu, Mar 11, 2021 at 10:54 AM Anash P. Oommen @.***> wrote:

@ToddEvansHome https://github.com/ToddEvansHome you cannot use service account setup with Google Ads without GSuite domain. That's a limitation of how Google Ads (the product) works, and not the .NET library per-se.

You need to do the installed application flow with offline access instead (the flow involving refresh tokens).

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/googleads/google-ads-dotnet/issues/261#issuecomment-796883930, or unsubscribe https://github.com/notifications/unsubscribe-auth/AKNJOY3FANJKPDPGOZHL6ULTDDRS7ANCNFSM4WXMIJHQ .

-- Thank you, Todd Evans Senior Programmer Media Management, Inc. 314-296-0007

-- https://www.linkedin.com/company/64239  https://twitter.com/MMi_mediaaudit  https://www.facebook.com/MMiMediaAudit  https://plus.google.com/u/0/107322038390188996620/posts  http://www.mediaaudit.com/contact/watchdog-eblast/This e-mail and any files transmitted with it may contain confidential information and is intended solely for use by the individual to whom it is addressed.  If you  received this e-mail in error, please notify the sender, do not disclose the contents to others and delete it from your system.

uamitroy commented 3 years ago

Could anyone please explain me what's difference between Google Ads Api & Google Adwords Api ?

I think both are same.