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.