question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Decouple Google API client creation logic to support individual clients for different users

See original GitHub issue

Currently Site Kit only uses one instance of a Google_(Site_Kit_)Client, since all requests are made on behalf of the current user. In the future, dashboard sharing will enable access delegation, in which for certain modules and read actions requests can be made on behalf of another user than the current one. When this happens, multiple different client instances may need to be used in combination.

The logic to currently create a new Site Kit client with all the configuration that is needed is currently baked into the OAuth_Client class, which creates and uses a single client instance throughout. We should keep this overall approach as is, having the OAuth_Client class always act on behalf of the current user - actions like granting scopes do not work without involving the user that owns the credentials anyway. However, we need to decouple the logic to generate a new client, so that we can also instantiate clients based on other existing read credentials.


Do not alter or remove anything below. The following sections will be managed by moderators only.

Acceptance criteria

  • A new class Google\Site_Kit\Core\Authentication\Clients\Client_Factory should be introduced with a public static method create_client which creates a new configured Google_Site_Kit_Client based on given arguments.
    • Dependencies like should be passed to the method.
    • The client should be set up exactly like the client currently is in OAuth_Client::setup_client, except in a more dynamic way, i.e. relying on dependencies instead of class properties.
    • This should e.g. allow instantiating a client based on data for a different user than the current user.
  • The OAuth_Client class should have all related logic that is now moved to the new class removed and instead use the new class itself.

Implementation Brief

  • Create Google\Site_Kit\Core\Authentication\Clients\Client_Factory class with method per the above, taking over logic from the current OAuth_Client::setup_client.
    • Adjust the implementation to be completely agnostic from any dynamic data that e.g. comes from WordPress options or user options. Such information (e.g. client credentials, token) should be passed into it instead.
    • A few other pieces of information that do not come from the database should still be passed into the method, in preparation for dashboard sharing, e.g. the required scopes, which will in the future not always come from the same single method.
    • The callbacks to set a token from a refresh and to handle a token refresh exception should also be passed into the method.
  • Introduce a new Google\Site_Kit\Core\Authentication\Token class which represents the entire associative token array (access_token, expires_in, created, refresh_token), similar to how the underlying Google client library operates. These encompass multiple user options, so the class should not extend User_Setting - however, it should follow the method signatures for convenience, which allows it to be used in the same way as if these four values were stored in the database together (which might even make sense to change eventually, but not here).
    • User_Setting classes for the four actual user options should not be introduced at this point, since we should eventually change the way the token is stored (combining into a single user option), and having the classes right now wouldn’t really come with a benefit. Since they would be temporary, it’s not worth it.
    • The Token constructor should receive the User_Options instance, in accordance with how User_Setting works (which this might in the future extend). The constructor can then instantiate Encrypted_User_Options internally.
  • Update the OAuth_Client class accordingly:
    • Update the get_client method to use the new static class method above, passing in details from its class variables as needed.
    • Implement new public methods get_token and set_token, using the new Token::get and Token::set methods (see above). Having these methods sticks closer to the convention, allows to pass set_token directly as callback to set a token, and it is easier to use them.
    • Update get_access_token to internally rely on get_token.
    • Deprecate set_access_token, get_refresh_token, set_refresh_token, and make them all use get_token / set_token respectively. All usages of those deprecated methods should be adjusted.
    • Remove any private unused methods that are no longer needed due to logic being moved to the new static class.
  • Split the OAuth_Client class, moving all the pieces that are also crucial in case of a secondary (i.e. not the current user) into a new abstract OAuth_Client_Base class which the OAuth_Client class then extends. This prepares for a future in which another class (e.g. OAuth_Client_Delegated) could also extend OAuth_Client_Base to represent the client for a user other than the current one (that itself should not be part of this issue though).
    • For example, functionality like setting up a Google_Site_Kit_Client, reading and writing tokens etc is essential either way. Other functionality, like sending the user to OAuth, manually refresh a token, revoke a token etc. is not relevant (or not even possible) for a secondary user, so these parts should be kept in the current OAuth_Client class.
    • While it might be a good idea to rename the OAuth_Client class as part of this, the approach above ensures backward-compatibility and minimizes consuming code changes as they’ll be able to use the class like before. In the future, we’ll be able to extend the OAuth_Client_Base class to access credentials from users other than the current one.
  • Complete and merge #3695.

Test Coverage

  • Update test coverage for deprecated methods.
  • Add test coverage for new OAuth_Client methods get_token and set_token.
  • Add test coverage for the new Client_Factory class.
  • Split test coverage for OAuth_Client according to the separation of OAuth_Client and its new base class OAuth_Client_Base (use an empty extended class for the latter).

Visual Regression Changes

  • N/A

QA Brief

  • Ensure that going through the plugin setup still works.
  • Ensure that refreshing an access token still works (for this you’ll need to ensure the current access token is considered expired, which you can easily accomplish by manually updating the database user option googlesitekit_access_token_expires_in to something like “1”, leading to it expiring 1 second after the original creation time).
  • Ensure all tests pass.

Changelog entry

  • Decouple Google API client creation logic from main OAuth client tied to the current WordPress user.

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:11 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
aaemnnosttvcommented, Aug 4, 2021

@wpdarren sure I can explain – when you go through the oAuth flow (signing in with Google) and land back in Site Kit, you get an access token which is what we need to make requests with to the Google APIs. This is only valid for 1 hour though, after which it’s automatically refreshed on-demand later when another request is made. So what that part of the QAB is describing is a way to manually expire the access token so that it would be refreshed on the next request. The next request is key there – if all you do is set the expiration and refresh, it’s likely that no request would be made due to cache in the browser. So after setting the expiration, clear your session storage and then refresh again to ensure requests will be made, and then if everything works then the token should have been refreshed. It would be good to check the token expiration was updated though which should be back to (or close to) 3600. I can provide commands for you to do that if you’d like, or you could always pair with an engineer to cover this part specifically 👍

1reaction
felixarntzcommented, Jul 19, 2021

@aaemnnosttv I think it makes sense to only pass User_Options into the constructor and instantiate Encrypted_User_Options based on it internally. I’ve added a point to the IB.

Regarding User_Aware_Interface, let’s not implement it in Token for now, since I think we won’t need it. If we need it later we can always add it, or once it eventually becomes a User_Setting it would by definition have it anyway. I’m not strictly opposed to adding it, but it’s also not necessary, so let’s leave it out.

Read more comments on GitHub >

github_iconTop Results From Across the Web

RESTful web API design - Best Practices - Microsoft Learn
Such an API may require a client application to send multiple requests to find all of the data that it requires. Instead, you...
Read more >
Understanding APIs and API proxies | Apigee X | Google Cloud
An API proxy consists of a bundle of XML configuration files and code (such as JavaScript and Java). Apigee provides several ways for...
Read more >
OAuth 2.0 | API Client Library for .NET - Google Developers
This document describes OAuth 2.0, when to use it, how to acquire client IDs, and how to use it with the Google API...
Read more >
REST API: Key Concepts, Best Practices, and Benefits
Understand the basics of REST architecture and what makes an API truly RESTful. Get the difference between REST and other popular approaches ...
Read more >
Application and API client settings - Akamai TechDocs
API clients control site-specific behavior and data collection when users interact with the Registration UI or OAuth API operations through the flow ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found