Keyring plugin for WordPress tips & gotcha’s
Keyring is a wonderful WordPress plugin that makes it easy to develop features against different services, e.g. Google APIs. However it has its own set of gotcha’s that are not covered in the documentation or not made clear at all.
Tip #1: Handling “No Content” response
For some unknown reason Keyring sees HTTP 204 No Content
response as an
error while it is a valid and successful response code for some of the batch
operations. If 204 code is expected but response is an instance of the
Keyring_Error
, it is possible to verify response code in the following path:
$response->get_error_message()['response']['code']
Tip #2: Extending scope of an application connection
Keyring provides an extensive set of connection classes. Unfortunately, those have hardcoded OAuth scopes, mostly limited to read operations. Documentation provides some hints about the solution but lacks clear explanation.
Actual solution is to create a new class based on the service needed (e.g. for
Gmail API parent class would be Keyring_Service_GoogleMail
). In this new class
you can set desired scope in the SCOPE
constant. But the real catch is the
order of class loading which may result in the new class being loaded before
Keyring service classes. PHP handles this situation as a Fatal Error, but fear
not as solution is less complicated than the problem description.
First of all, let’s look at the example service connection. Notice that service name has also to be changed to avoid collisions with Keyring-provided classes.
<?php
/**
* Google Mail (Gmail) service definition for Keyring.
* With updated scopes.
*
* Gmail API: https://developers.google.com/gmail/api/v1/reference/
* OAuth implementation: https://developers.google.com/identity/protocols/OAuth2WebServer
* App registration: https://console.developers.google.com/
*/
class Keyring_Service_My_Gmail extends Keyring_Service_GoogleMail {
const NAME = 'my-gmail';
const LABEL = 'Google Mail (my scopes)';
const SCOPE = 'https://www.googleapis.com/auth/gmail.readonly https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/gmail.modify'; // See https://developers.google.com/identity/protocols/googlescopes
const ACCESS_TYPE = 'offline';
}
Now to the tricky part: loading classes in the proper order. Let’s assume that
aforementioned class is saved as class-keyring-service-my-gmail.php
. To load
it at the proper time and avoid fatal error we can hook to the
keyring_load_services
action. At the same time Keyring documentation
recommends to hook into our plugin init
action. The result would look like
this (this code should be in the main plugin PHP file):
<?php
function register_keyring_service() {
require_once('class-keyring-service-my-gmail.php');
Keyring_Service_My_Gmail::init();
}
add_action( 'init', 'register_keyring_service' );
add_action( 'keyring_load_services', 'register_keyring_service' );
That’s it. Once plugin is activated it would be added into the Keyring list of services.
Tip #3: Extending a scope without a subclass
Previous method works well for almost every case. However there is a shorter path to extend scopes if you have a full control over the system where plugin is going to be used.
Every Keyring service based on OAuth2 applies a filter to the token request
parameters, which include a scope. Filter names are service-specific and have
a format 'keyring_' . <service-name> . '_request_token_params'
. E.g. for
Gmail it would be keyring_google-mail_request_token_params
. Service names can
be looked up in the source code.
Registering a hook to change access scopes can be done in a main plugin file like this:
<?php
function register_keyring_scope_filter() {
add_filter(
'keyring_google-mail_request_token_params',
function( $params ) {
$params['scope'] = implode(
' ',
array(
'https://www.googleapis.com/auth/gmail.readonly',
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/gmail.modify'
)
);
return $params;
},
PHP_INT_MAX
);
}
add_action( 'init', 'register_keyring_scope_filter' );
Note the PHP_INT_MAX
constant. It is used to apply the largest priority to
this filter and ensure it is the last one to be executed.