Decouple Google API client creation logic to support individual clients for different users
See original GitHub issueCurrently 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 methodcreate_client
which creates a new configuredGoogle_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 currentOAuth_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 extendUser_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 theUser_Options
instance, in accordance with howUser_Setting
works (which this might in the future extend). The constructor can then instantiateEncrypted_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
andset_token
, using the newToken::get
andToken::set
methods (see above). Having these methods sticks closer to the convention, allows to passset_token
directly as callback to set a token, and it is easier to use them. - Update
get_access_token
to internally rely onget_token
. - Deprecate
set_access_token
,get_refresh_token
,set_refresh_token
, and make them all useget_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.
- Update the
- 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 abstractOAuth_Client_Base
class which theOAuth_Client
class then extends. This prepares for a future in which another class (e.g.OAuth_Client_Delegated
) could also extendOAuth_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 currentOAuth_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 theOAuth_Client_Base
class to access credentials from users other than the current one.
- For example, functionality like setting up a
- Complete and merge #3695.
Test Coverage
- Update test coverage for deprecated methods.
- Add test coverage for new
OAuth_Client
methodsget_token
andset_token
. - Add test coverage for the new
Client_Factory
class. - Split test coverage for
OAuth_Client
according to the separation ofOAuth_Client
and its new base classOAuth_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:
- Created 2 years ago
- Comments:11 (3 by maintainers)
@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 👍@aaemnnosttv I think it makes sense to only pass
User_Options
into the constructor and instantiateEncrypted_User_Options
based on it internally. I’ve added a point to the IB.Regarding
User_Aware_Interface
, let’s not implement it inToken
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 aUser_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.