Services

To provide a structured and consistent way of talking with external services, Keyring expects them to be expressed as Services. This means that they all extend the same class (Keyring_Service, found in /service.php). While Services may implement any additional methods and operations they wish, they must implement a base set of functionality to operate with Keyring core. We’ll talk more about that in Writing Your Own Service.

Core Services

Located in the /includes/services/core/ directory, these base classes are actually libraries which should be used as the foundation of more specific Service definitions. They are always loaded before other Services, so that they may be extended safely. Core Services for OAuth1, OAuth2 and HTTP Basic are all provided. Examples of using each may be seen in the Twitter, Facebook and Delicious Extended Services, respectively.

Extended Services

User-facing Services are all referred to as Extended Services since they extend at least Keyring_Service, if not one of the Core Services above (which all extend Keyring_Service themselves). By extending the Core Services, the bulk of the complexity of protocols like OAuth can be avoided, and left up to pre-written code to handle. These Extended Services just define the differences or specific details required for a particular implementation (including labels, URLs/endpoints, verification steps, etc).

Writing Your Own Service

If the service you need to connect to is not included in /includes/services/extended/ then you may need to write your own. Before you start, identify what the authentication protocol is that you need (OAuth etc), if there is a Core Service for that protocol then use it! If the Service you need to connect to uses some custom or exotic protocol which is not covered by the Core Services, then see the section on Writing a Service From Scratch for some additional pointers.

Extending a Core Service

  • Always use an existing Extended Service as a template (hint: there is at least one Extended Service using each of the Core Services bundled with Keyring)
  • If you’re loading your Service as a separate plugin, there’s a load-order problem you need to be aware of. WordPress loads plugin files alphabetically. This means that if your plugin file comes before Keyring’s, then the Keyring classes you need to extend are not loaded yet and you’ll get a fatal PHP error. There are 2 ways around this: name your files something that will ensure they load later (e.g. zz-my-keyring-service.php), or wrap your entire class definition and keyring_load_services hook inside a function definition, and then attach a call to that function on the init action, which should define the class, and at the same time hook into the keyring_load_services action to load and register your Service when appropriate.
  • In the constructor for your custom Service, always call parent::__construct(); as the first thing, to ensure everything is set up correctly, all the way up the inheritance chain.
  • If you’re making a Service intended for distribution, then you should hook into the core-provided admin UI and other components. See existing Extended Services for details how to do that. All of the bundled Services also provide the ability to configure those Services using constants, which you might choose to implement in addition to UI-driven configuration.
  • If your Service requires UI of some sort (to allow the user to enter a username or some other information for example), then you can hook into either the request or verify phases using the keyring_{$service}_request_ui or keyring_{$service}_verify_ui hooks. If you just need a simple username/password combination then have a look at Keyring_Service_HTTP_Basic::request_ui() for an example.

Writing a Service From Scratch

In addition to all of the considerations for Extending a Core Service, here are some tips if you’re starting from scratch:

  • Try to follow one of the other Core Services, along with an implementation of it (e.g. OAuth1 + Twitter) to make sure you’re doing things in an interoperable way.
  • Even if your Service doesn’t have the true concepts of a ‘request’ and ‘verify’ process, you should implement it as if it does. Have a look at HTTP Basic, which doesn’t really work like this, but acts as if it does.
  • Make sure you implement both Request and Access Tokens to ensure compatibility with all Keyring implementations.
  • Always use provided Keyring APIs/methods for interacting with Token Stores and Tokens.
  • Make sure you actually store the Token once it’s verified.
  • In verify_token() be sure to globalize $keyring_request_token to provide for standardized handling for plugins in verified().
  • Along those lines, make sure you call $this->verified( $id, $keyring_request_token ); at the end of verify_token() (once you’ve actually verified the token!)
  • If you need to throw an error, call Keyring::error() with a string, and then return false;
  • request() should use parse_response to parse the response received when the raw_response argument is set to true (which it should default to).
  • Provide a test_connection() method, which might be used to verify that a Token provides a valid connection to a service.

What do you think?

  1. Hi,
    I really like your plugin and try to build my own Site where all my Social Media Activity is imported. There i would like to add own services to import from RSS-Feed or a CSV File. My programming Skills are good enough to write a parser for those file-types and import them to WordPress as posts. But I’m a bit lost on how to do this using the keyring-framework. I already read your Developer-Guide and also checked some of the source-code from the services/extended and /importers.

    As far as i understood, i should do the following:
    1. create a new service for e.g. RSS-Import (from Goodreads)
    2. create new importer for RSS

    Questions:
    1. where do i add the new file for the new service? putting it in the services/extended will overwrite it when updating
    2. where do i put the new file for the importer? putting it in keyring-social-importers/importers will also overwrite it, when updating
    3. in my case i don’t need an authentification method. I just need to grab a publicly available http-URL. I thought of using the http-Basic-Service, but this requires User/Password-Information. Will i have to write my own service? Where to put this service?

    I really thank you in advance for your answers.

    best regards
    Heiko

    • Beau Lebens said:

      Hi Heiko! It sounds like that you’d like to do doesn’t really require Keyring per se, since Keyring’s main purpose is handling authenticated requests (tokens, signatures, custom headers, etc). If you’re requesting publicly-available RSS feeds or CSVs, then all you really need is what I’ve got in the Keyring Social Importers plugin, or you could take a completely separate (but similar) approach since it’s just making requests and then parsing the response.

      That being said, the approach you’re describing above would also work I believe (make a Service + Importer combination for RSS), you’d just need to make the service something like the HTTP Basic one, but without any of the pieces related to sending a user/pass combination (so it’d basically just end up being a “pass through” Service, that makes requests without any additional authentication required).

      Here is an example of someone else who wrote a custom importer, so you should be able to see how/where they put their files (basically you can put them anywhere, and then hook into `init` to load the code properly): https://github.com/cfinke/Keyring-Facebook-Importer

      You should be able to take a similar approach with the files for Services + Importers.

      Hope that helps!

  2. Hi, really love the plugin but am struggling to get it to work with Harvest (time recording) https://help.getharvest.com/api-v2/authentication-api/authentication/authentication/

    I have the following in an extended service I copied from the Eventbrite example.

    const API_BASE = ‘https://id.getharvest.com/api/v2’;
    const OAUTH_BASE = ‘https://id.getharvest.com/oauth2’;

    And have the following in the href to do the authorisation but I just can’t get it to authorise correctly.

    https://id.getharvest.com/oauth2/authorize

    Any help would be much appreciated.

    Thanks

    • Beau Lebens said:

      Hi Nathan! Would you mind putting your code up in a gist or something somewhere so I can see the whole thing and see if I can figure out what’s going on? Alternatively you can just send it to me via my contact form.