Google People API "Request mask cannot be empty"
Asked Answered
A

4

5

I'm attempting to make a request for the profile information of a user logged in via Google OAuth. My request is formed properly, I log in successfully, but when I attempt to make the following request in PHP, I get the error Request Mask cannot be empty. Valid paths are: ...

However, it is clear from the Google People API people.get documentation that the request mask values are optional, and if not passed, will return all values except for people.connections.list. Here is my code:

// The entire OAuth process works up until this point...
// create the service
$service = new Google_Service_People($this->client);

try {
  $results = $service->people->get('people/me');
} catch(\Exception $exception) {
  echo $exception->getMessage();
  exit;
}

Here are is the exception message I get from this error:

{ "error": { "code": 400, "message": "Request mask can not be empty. Valid paths are: [person.addresses, person.age_ranges, person.biographies, person.birthdays, person.bragging_rights, person.cover_photos, person.email_addresses, person.events, person.genders, person.im_clients, person.interests, person.locales, person.memberships, person.metadata, person.names, person.nicknames, person.occupations, person.organizations, person.phone_numbers, person.photos, person.relations, person.relationship_interests, person.relationship_statuses, person.residences, person.skills, person.taglines, person.urls].", "errors": [ { "message": "Request mask can not be empty. Valid paths are: [person.addresses, person.age_ranges, person.biographies, person.birthdays, person.bragging_rights, person.cover_photos, person.email_addresses, person.events, person.genders, person.im_clients, person.interests, person.locales, person.memberships, person.metadata, person.names, person.nicknames, person.occupations, person.organizations, person.phone_numbers, person.photos, person.relations, person.relationship_interests, person.relationship_statuses, person.residences, person.skills, person.taglines, person.urls].", "domain": "global", "reason": "badRequest" } ], "status": "INVALID_ARGUMENT" } }

Can anyone help me out?


Update 1:

When I try to pass in some value for the request mask, $service->people->get('people/me', array("person.names")); I get the exception message: Illegal string offset 'type'

Arlon answered 10/5, 2017 at 19:58 Comment(5)
Are you using the latest version of the PHP library? If not, try updating to the latest version.Johnie
I am getting the same error, but with javascript and a web page. I don't think I changed anything since last time it was working, however. Is this a new issue for you that just started, or did you only just try implementing this?Kopeck
I started getting the same error with Go library since today. (I haven't touched my code for several weeks.) Maybe something has changed lately?Tercet
it happens with plain rest calls as wellLilylivered
Yes, I am using the latest version of the PHP library. I am using composer with the Laravel framework, so the version defined in my composer.json file is "google/apiclient": "^2.0". I deleted the vendor folder and completely reinstalled dependencies with composer install, but was still hitting the same issue. I'm not sure if something changed or not, but it seems like something changed in their internal API logic.Arlon
A
3

I've found a work around. I was able to request partial user profile information from the Plus service instead of People. My hunch as to why this seems to have changed is that Google has modified the internal logic of their API, and have not updated their documentation. (At the time of this writing, Google's PHP OAuth library is in beta).

In my case, what I was really after was getting the user's username and email address. Instead of using the People service to make the profile request, I used the Plus service instead, and asked for some additional scopes to get the email address. Here's the entirety of my PHP implementation. Note the three scopes I am requesting in the constructor:

Google_Service_Plus::USERINFO_PROFILE, Google_Service_People::USERINFO_PROFILE, Google_Service_People::USERINFO_EMAIL

After successfully authenticating, instead of requesting people/me from the People service, I am requesting me from the Plus service instead, with a couple additional requests to get the remaining information:

$plus = new Google_Service_Plus($this->client);

try {
  $plus_results = $plus->people->get('me');
} catch(\Exception $exception) {
  echo $exception->getMessage();
  exit;
}

<?php

namespace App\Auth;
require_once '/var/www/html/oauth/vendor/google/apiclient-services/src/Google/Service/People.php';
require_once '/var/www/html/oauth/vendor/google/apiclient-services/src/Google/Service/Plus.php';
require_once '/var/www/html/oauth/vendor/google/apiclient/src/Google/Client.php';
require_once 'Session.php';
use Google_Client;
use Google_Service_People;
use Google_Service_Plus;
use App\Auth\Session;

/**
 * This class performs a basic oauth authentication
 * using Google sign in and upon calling the handle_auth
 * method, retrieves the user's profile and sets session
 * variables for use throughout an application.
 */
class GoogleAuth {

  private static $DOMAIN = 'google';

  /**
   * Google auth client
   * @var Google_Client
   */
  public $client;

  /**
   * Config json filepath
   * @var String
   */
  public $config_json;

  /**
   * The URI to redirect to after succesful oauth
   * @var String
   */
  public $redirect_uri;

  /**
   * The authorization url
   * @var String
   */
  public $auth_url;

  /**
   * Logout url to redirect to after logout
   * @var String
   */
  public $logout_url;

  /**
   * The name of the application as listed in the Google
   * app Dashboard.
   * @var String
   */
  public $application_name;

  /**
   * The developer hash key available in the Google
   * App Credentials dashboard.
   * @var String
   */
  public $developer_key;

  /**
   * Scopes to request in the oauth request.
   * @var [type]
   */
  public $scope;

  /**
   * Url to redirect to upon successful authentication
   * @var String
   */
  public $auth_success_url;

  public function __construct($config) {
    // Eventually we can extend the scope to handle different
    // values or multiple values. For now, this class only
    // supports user profile information.
    $config['scope'] = array(
      Google_Service_Plus::USERINFO_PROFILE,
      Google_Service_People::USERINFO_PROFILE,
      Google_Service_People::USERINFO_EMAIL
    );

    $this->init($config);
  }

  private function init($config) {
    
    if(!isset($config)) {
      throw new \Exception('Config is not valid.');
    }
    if(!isset($config['config_json'])) {
      throw new \Exception('Path to config json is invalid.');
    }
    if(!file_exists($config['config_json'])) {
      throw new \Exception('Config JSON file could not be found: ' . $config['config_json']);
    }
    if(!isset($config['application_name'])) {
      throw new \Exception('Application name is invalid.');
    }
    if(!isset($config['developer_key'])) {
      throw new \Exception('Developer Key is invalid.');
    }
    if(!isset($config['scope'])) {
      throw new \Exception('Scope is invalid.');
    }
    if(!isset($config['redirect_uri'])) {
      throw new \Exception('Redirect URL is invalid.');
    }
    if(!isset($config['logout_url'])) {
      throw new \Exception('Logout URL is invalid.');
    }

    $this->client = new Google_Client();
    $this->config_json = $config['config_json'];
    $this->redirect_uri = $config['redirect_uri'];
    $this->application_name = $config['application_name'];
    $this->developer_key = $config['developer_key'];
    $this->scope = $config['scope'];
    $this->logout_url = $config['logout_url'];

    // Let the session know where we want to go on logout.
    Session::set_logout_url($this->logout_url, self::$DOMAIN);

    $this->client->setAuthConfig($this->config_json);

    foreach($this->scope as $scope) {
      $this->client->addScope($scope);
    }
    
    $this->client->setApplicationName($this->application_name);
    $this->client->setDeveloperKey($this->developer_key);
    $this->client->setRedirectUri($this->redirect_uri);
    $this->client->setPrompt('select_account');
    $this->auth_url = $this->client->createAuthUrl();
  }

  public static function auth_failure(\Exception $exception) {
    return Session::auth_failure(
      $exception->getMessage(), 
      self::$DOMAIN
    );
  }

  public static function logout() {
    return Session::logout(self::$DOMAIN);
  }

  public function authenticate($request) {
    if (!$request->has('code')) {

      // User is unauthenticated, send them through the auth process
      return filter_var($this->auth_url, FILTER_SANITIZE_URL);

    } else {
      $code = $request->input('code');

      // process the code received from the auth process
      $token_response = $this->process_code($code);
      
      // Ensure the token response is valid
      Validator::token_response($token_response);
      
      // Process and retrieve the access token
      $raw_token = $this->process_token_response($token_response);

      if(isset($raw_token)) {
        // Handle the token and process the id_token
        $this->handle_id_token($raw_token);
         
        // Create the people service and make requests
        return $this->make_profile_request();

      } else {
        throw new \Exception('Failed to retrieve the access token');
      }
    }
  }

  private function process_code($code) {
    // grab the code from the URL and generate an access token
    $response = $this->client->fetchAccessTokenWithAuthCode($code);

    if(!is_array($response)) {
      throw new \Exception('Token response was invalid.');
    }

    return $response;
  }

  private function process_token_response($token_response) {
    $this->client->setAccessToken($token_response);
    return $this->client->getAccessToken();
  }

  private function handle_id_token($token) {

    $id_token = null;

    try {
      $id_token = $this->client->verifyIdToken($token['id_token']);
    } catch(\Exception $exception) {
      // clear the access token to disable any
      // approved permissions for the user's account
      $this->client->revokeToken();
      
      throw new \Exception('Google Login failed');
    }

    if(!$id_token) {
      throw new \Exception('Id Token is null or undefined');
    }

    // grab the domain from the id_token
    $email = $id_token['email'];

    // Stuff it into the session
    Session::set_email($email, self::$DOMAIN);
  }

  private function make_profile_request() {
    // create the service
    $plus = new Google_Service_Plus($this->client);

    try {
      $plus_results = $plus->people->get('me');
    } catch(\Exception $exception) {
      echo $exception->getMessage();
      exit;
    }
    
    if(!$plus_results) {
      throw new \Exception('No matching profile results.');
    }

    // Get the user's display name
    $username = $plus_results->getDisplayName();

    // Stuff it into the session
    Session::set_username($username, self::$DOMAIN);
      
    // Login. Session handles the redirect
    return Session::login(
      $username, 
      Session::get_email(self::$DOMAIN), 
      self::$DOMAIN
    );
  }
}
?>
Arlon answered 12/5, 2017 at 19:50 Comment(1)
Thanks for posting your solution Danny. As I mentioned above, I'm using the javascript api, but your solution lead me to mine. If anyone is reading this and wants to know, I also switched from the people api to the plus api. I changed: gapi.client.people.people.get({ resourceName: "people/me" }).then( ... ) into gapi.client.plus.people.get({ userId: 'me' }).then( ... )Kopeck
S
6

Its too late , but maybe this will be useful to anyone else.

Use below array as second argument to add requestMask with api call

$optParams = array('requestMask.includeField'=>'person.names' );
Shive answered 17/5, 2017 at 10:3 Comment(1)
Thanks, this is it! I didn't realize requestMask.includeField was to be passed using dot syntax..Sparrowgrass
T
3

I started getting the same error with Go library since ~May 11th. My code without includeField was working fine before the Google API change. The field was optional.

In the Google documentation, now "includeField" is required field. I cannot find any announcement elsewhere.

https://developers.google.com/people/api/rest/v1/RequestMask

includeField

Required. Comma-separated list of person fields to be included in the response. Each path should start with person.: for example, person.names or person.photos.

Last updated May 19, 2017

To solve my golang case, I had to provide RequestMaskIncludeField field before making People.Get call.

people_get_call := peopleService.People.Get("people/me").RequestMaskIncludeField("person.addresses,person.age_ranges,person.biographies,person.birthdays,person.bragging_rights,person.cover_photos,person.email_addresses,person.events,person.genders,person.im_clients,person.interests,person.locales,person.memberships,person.metadata,person.names,person.nicknames,person.occupations,person.organizations,person.phone_numbers,person.photos,person.relations,person.relationship_interests,person.relationship_statuses,person.residences,person.skills,person.taglines,person.urls")
google_account, err := people_get_call.Do()
Tercet answered 11/5, 2017 at 13:38 Comment(0)
A
3

I've found a work around. I was able to request partial user profile information from the Plus service instead of People. My hunch as to why this seems to have changed is that Google has modified the internal logic of their API, and have not updated their documentation. (At the time of this writing, Google's PHP OAuth library is in beta).

In my case, what I was really after was getting the user's username and email address. Instead of using the People service to make the profile request, I used the Plus service instead, and asked for some additional scopes to get the email address. Here's the entirety of my PHP implementation. Note the three scopes I am requesting in the constructor:

Google_Service_Plus::USERINFO_PROFILE, Google_Service_People::USERINFO_PROFILE, Google_Service_People::USERINFO_EMAIL

After successfully authenticating, instead of requesting people/me from the People service, I am requesting me from the Plus service instead, with a couple additional requests to get the remaining information:

$plus = new Google_Service_Plus($this->client);

try {
  $plus_results = $plus->people->get('me');
} catch(\Exception $exception) {
  echo $exception->getMessage();
  exit;
}

<?php

namespace App\Auth;
require_once '/var/www/html/oauth/vendor/google/apiclient-services/src/Google/Service/People.php';
require_once '/var/www/html/oauth/vendor/google/apiclient-services/src/Google/Service/Plus.php';
require_once '/var/www/html/oauth/vendor/google/apiclient/src/Google/Client.php';
require_once 'Session.php';
use Google_Client;
use Google_Service_People;
use Google_Service_Plus;
use App\Auth\Session;

/**
 * This class performs a basic oauth authentication
 * using Google sign in and upon calling the handle_auth
 * method, retrieves the user's profile and sets session
 * variables for use throughout an application.
 */
class GoogleAuth {

  private static $DOMAIN = 'google';

  /**
   * Google auth client
   * @var Google_Client
   */
  public $client;

  /**
   * Config json filepath
   * @var String
   */
  public $config_json;

  /**
   * The URI to redirect to after succesful oauth
   * @var String
   */
  public $redirect_uri;

  /**
   * The authorization url
   * @var String
   */
  public $auth_url;

  /**
   * Logout url to redirect to after logout
   * @var String
   */
  public $logout_url;

  /**
   * The name of the application as listed in the Google
   * app Dashboard.
   * @var String
   */
  public $application_name;

  /**
   * The developer hash key available in the Google
   * App Credentials dashboard.
   * @var String
   */
  public $developer_key;

  /**
   * Scopes to request in the oauth request.
   * @var [type]
   */
  public $scope;

  /**
   * Url to redirect to upon successful authentication
   * @var String
   */
  public $auth_success_url;

  public function __construct($config) {
    // Eventually we can extend the scope to handle different
    // values or multiple values. For now, this class only
    // supports user profile information.
    $config['scope'] = array(
      Google_Service_Plus::USERINFO_PROFILE,
      Google_Service_People::USERINFO_PROFILE,
      Google_Service_People::USERINFO_EMAIL
    );

    $this->init($config);
  }

  private function init($config) {
    
    if(!isset($config)) {
      throw new \Exception('Config is not valid.');
    }
    if(!isset($config['config_json'])) {
      throw new \Exception('Path to config json is invalid.');
    }
    if(!file_exists($config['config_json'])) {
      throw new \Exception('Config JSON file could not be found: ' . $config['config_json']);
    }
    if(!isset($config['application_name'])) {
      throw new \Exception('Application name is invalid.');
    }
    if(!isset($config['developer_key'])) {
      throw new \Exception('Developer Key is invalid.');
    }
    if(!isset($config['scope'])) {
      throw new \Exception('Scope is invalid.');
    }
    if(!isset($config['redirect_uri'])) {
      throw new \Exception('Redirect URL is invalid.');
    }
    if(!isset($config['logout_url'])) {
      throw new \Exception('Logout URL is invalid.');
    }

    $this->client = new Google_Client();
    $this->config_json = $config['config_json'];
    $this->redirect_uri = $config['redirect_uri'];
    $this->application_name = $config['application_name'];
    $this->developer_key = $config['developer_key'];
    $this->scope = $config['scope'];
    $this->logout_url = $config['logout_url'];

    // Let the session know where we want to go on logout.
    Session::set_logout_url($this->logout_url, self::$DOMAIN);

    $this->client->setAuthConfig($this->config_json);

    foreach($this->scope as $scope) {
      $this->client->addScope($scope);
    }
    
    $this->client->setApplicationName($this->application_name);
    $this->client->setDeveloperKey($this->developer_key);
    $this->client->setRedirectUri($this->redirect_uri);
    $this->client->setPrompt('select_account');
    $this->auth_url = $this->client->createAuthUrl();
  }

  public static function auth_failure(\Exception $exception) {
    return Session::auth_failure(
      $exception->getMessage(), 
      self::$DOMAIN
    );
  }

  public static function logout() {
    return Session::logout(self::$DOMAIN);
  }

  public function authenticate($request) {
    if (!$request->has('code')) {

      // User is unauthenticated, send them through the auth process
      return filter_var($this->auth_url, FILTER_SANITIZE_URL);

    } else {
      $code = $request->input('code');

      // process the code received from the auth process
      $token_response = $this->process_code($code);
      
      // Ensure the token response is valid
      Validator::token_response($token_response);
      
      // Process and retrieve the access token
      $raw_token = $this->process_token_response($token_response);

      if(isset($raw_token)) {
        // Handle the token and process the id_token
        $this->handle_id_token($raw_token);
         
        // Create the people service and make requests
        return $this->make_profile_request();

      } else {
        throw new \Exception('Failed to retrieve the access token');
      }
    }
  }

  private function process_code($code) {
    // grab the code from the URL and generate an access token
    $response = $this->client->fetchAccessTokenWithAuthCode($code);

    if(!is_array($response)) {
      throw new \Exception('Token response was invalid.');
    }

    return $response;
  }

  private function process_token_response($token_response) {
    $this->client->setAccessToken($token_response);
    return $this->client->getAccessToken();
  }

  private function handle_id_token($token) {

    $id_token = null;

    try {
      $id_token = $this->client->verifyIdToken($token['id_token']);
    } catch(\Exception $exception) {
      // clear the access token to disable any
      // approved permissions for the user's account
      $this->client->revokeToken();
      
      throw new \Exception('Google Login failed');
    }

    if(!$id_token) {
      throw new \Exception('Id Token is null or undefined');
    }

    // grab the domain from the id_token
    $email = $id_token['email'];

    // Stuff it into the session
    Session::set_email($email, self::$DOMAIN);
  }

  private function make_profile_request() {
    // create the service
    $plus = new Google_Service_Plus($this->client);

    try {
      $plus_results = $plus->people->get('me');
    } catch(\Exception $exception) {
      echo $exception->getMessage();
      exit;
    }
    
    if(!$plus_results) {
      throw new \Exception('No matching profile results.');
    }

    // Get the user's display name
    $username = $plus_results->getDisplayName();

    // Stuff it into the session
    Session::set_username($username, self::$DOMAIN);
      
    // Login. Session handles the redirect
    return Session::login(
      $username, 
      Session::get_email(self::$DOMAIN), 
      self::$DOMAIN
    );
  }
}
?>
Arlon answered 12/5, 2017 at 19:50 Comment(1)
Thanks for posting your solution Danny. As I mentioned above, I'm using the javascript api, but your solution lead me to mine. If anyone is reading this and wants to know, I also switched from the people api to the plus api. I changed: gapi.client.people.people.get({ resourceName: "people/me" }).then( ... ) into gapi.client.plus.people.get({ userId: 'me' }).then( ... )Kopeck
N
0

Like @gonbe pointed out, RequestMaskIncludeField wasn't required, but for some time it is. For latest java lib (for now its rev139-1.22.0) You just need add method setRequestMaskIncludeField() to the request, e.g.

 peopleService.people().get("people/me").setRequestMaskIncludeField("person.email_addresses").execute();
Neoplatonism answered 15/9, 2017 at 19:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.