Actions

Difference between revisions of "Developer Area/Webservices"

From Mahara Wiki

< Developer Area
(Created page with "This page describes Mahara's '''webservices''' system. The webservices system provides a standardized way for a Mahara site to communicate with other applications, via HTTPS r...")
 
Line 4: Line 4:
  
 
It also allows Mahara to act as a "service requester", with internal Mahara PHP functions triggering HTTP requests that access webservices on outside applications. Mahara plugins can use the "Connection Manager" interface to describe incoming webservices they would like to be able to consume; site & institution admins can then enable/disable these connections, and configure the specific service providers they point to, and their authentication credentials.
 
It also allows Mahara to act as a "service requester", with internal Mahara PHP functions triggering HTTP requests that access webservices on outside applications. Mahara plugins can use the "Connection Manager" interface to describe incoming webservices they would like to be able to consume; site & institution admins can then enable/disable these connections, and configure the specific service providers they point to, and their authentication credentials.
 +
 +
== Concepts ==
 +
 +
Here's a brief explanation of some of the terms used on the Web services admin page.
 +
 +
=== Web service requester master switch ====
 +
 +
This controls whether or not Mahara allows '''outgoing''' webservice requests. That is, your Mahara server sending out HTTP requests to an external server. Specifically, it controls whether or not you can use webservices that have been configured using the Mahara connection manager. You can disable that sitewide, for all plugins and institutions, with this switch.
 +
 +
You can check whether it's enabled or not by checking '''$CFG->webservice_requester_enabled'''
 +
 +
Note that this does '''not''' prevent ''all'' code in Mahara from accessing outside services. There is still plenty of code that connects to other servers using Curl to make HTTP requests, and this control has no impact on those. All it controls is the connections configured via the connection manager.
 +
 +
=== Web service provider master switch ===
 +
 +
This controls whether or not Mahara will accept '''incoming''' webservice requests. That is, your Mahara server accepting requests form other servers, which cause stuff to happen on your server. You can disable that sitewide, for all plugins and services and institutions, with this switch.
 +
 +
You can check this with '''$CFG->webservice_provider_enabled'''.
 +
 +
Note that this does '''not''' 100% guarantee that no "webservices" will connect to your application. There are some scripts in Mahara that are designed to be machine-readable, which are not controlled by this setting. (For instance, generated RSS feeds.) There may also be programs that "screenscrape" Mahara, using the same HTTP methods as a normal user's browser. All this setting controls, is webservices that are configured via the Mahara webservices API.
 +
 +
=== Web service protocols ===
 +
 +
Controls which data formats we accept incoming requests in. REST is the most popular. See the Moodle webservices documents for specifics about these protocols.
 +
 +
These can be checked by calling '''webservice_protocol_enabled($protocol)''' where protocol is <tt>rest</tt>, <tt>oauth</tt>, <tt>xmlrpc</tt>, or <tt>soap</tt>.
 +
 +
These settings are dependent on the "Web service provider master switch". If that switch is disabled, all of these services are disabled.
 +
 +
=== Functions ===
 +
 +
These are, as the name implies, individual functions that can be accessed via webservices. They're declared by plugins (or by the /webservice core code), and they map to underlying PHP functions which are executed when the webservice is accessed.
 +
 +
==== Implementing ====
 +
 +
Functions are defined in files that sit in the <tt>services/functions</tt> directory of a plugin, e.g. <tt>htdocs/module/mobileapi/webservice/functions</tt>. The functions are grouped into classes, with each class in a file that shares its name. For each function that's meant to be exposed as a webservice, the class must also define a function called "*_parameters" and one called "*_returns", which return objects describing the parameters and return values of the webservice function.
 +
 +
This part is essentially unchanged from Moodle's webservices, so their documentation is quite helpful on this: https://docs.moodle.org/dev/Adding_a_web_service_to_a_plugin#Write_the_external_function_descriptions
 +
 +
Once the underlying PHP function has been implemented, along with its associated description functions, you also need to describe it in your plugin's <tt>webservice/services.php</tt> file. The Moodle documentation describes the format: https://docs.moodle.org/dev/Adding_a_web_service_to_a_plugin#Declare_the_web_service_function (Mahara doesn't support the new "services" field; instead, services have to list which fields they accept)
 +
 +
All functions share the same namespace for their published names, so the published names should be prefaced with their plugin type and name, e.g. <tt>module_mobileapi_get_blogs</tt> instead of just <tt>get_blogs</tt>. (However, the code doesn't enforce this.)
 +
 +
You can also look at the "mobileapi" module for an example in Mahara.
 +
 +
'''Note:''' for backwards-compatibility, I recommend making '''every''' webservice function return an associative array (an <tt>external_single_structure</tt>), with all return values stored as fields of that associative array. See notes about backward-compatibility design below.
 +
 +
==== Global variables ====
 +
 +
Some things to be aware of when running code in a webservices function.
 +
 +
$USER will be set to the user whose username/password was used for authentication, or to the user who owns the token that was used for authentication. This is handled by the webservices library itself (server.php)
 +
 +
$SESSION should not be relied on. Mahara's sessions rely on cookies, and most webservices clients don't accept and send cookies the way a normal web browser does. Additionally, the user is re-authenticated on each webservice request, which will clear most session data anyhow. You should try to write webservice functions so that each one can act alone, using only the data provided in its parameters.
 +
 +
=== Services / Service groups ===
 +
 +
A service group (called a "service" in Moodle, and sometimes in Mahara), is a collection of functions, grouped together for access control purposes. You can't grant access to individual functions; instead you have to put one or more functions into a service group, and then grant access to that.
 +
 +
Service groups, like functions, are declared by plugins. (Mahara 15.04 through 16.04 also declared some "demo" service groups, but those have been removed in Mahara 16.10). They can also be created manually via the web UI, by selecting which functions should be included.
 +
 +
Service groups provided by plugins have a '''component''', which indicates which plugin provides them. (Due to coding legacy issues, the component always ends with "/webservice", e.g. "module/mobileapi/webservice".) Core service groups provided by "htdocs/webservice" would belong to the component "webservice". Manually created service groups have no component value (this is what indicates they were manually created). The list of functions in a plugin-created service group cannot be modified by admins via the web interface; manually created service groups ''can'' be modified.
 +
 +
Service groups can also have a '''shortname''', to make it easier for automated systems to refer to them. Normally this is unnecessary, because webservice calls usually only need to specify the function's name. However, it is needed when trying to use <tt>htdocs/module/mobileapi/json/token.php</tt> to auto-generate an authentication token for a service.
 +
 +
==== Implementing ====
 +
 +
Services are declared in their plugin's <tt>webservice/services.php</tt> file (e.g. <tt>htdocs/module/mobileapi/webservice/services.php</tt>). Core services would go in <tt>htdocs/webservice/services.php</tt>.
 +
 +
The format for declaring services is unchanged from Moodle: https://docs.moodle.org/dev/Adding_a_web_service_to_a_plugin#Declare_the_service
 +
 +
One addition Mahara has made, is that each service can also have an '''apiversion''' value. This is an optional integer to help webservice clients deal with different servers that may be running different versions of the Mahara software. It's recommended to increment this number each time you change the definition or behavior of any of the functions in the service group.
 +
 +
(Currently Mahara core only exposes this number via <tt>htdocs/module/mobileapi/json/info.php</tt>, and then only for the "maharamobile" service group.)
 +
 +
== Files ==
 +
 +
The webservices plugin is based on a port of Moodle's webservices functionality. So the Moodle webservices documentation may be useful: https://docs.moodle.org/dev/Web_services
 +
 +
Most of the library code is under '''htdocs/webservice'''. This is a kind of "pseudo-plugin", because the original version of Mahara webservices was written in 2011 before we had the general-purpose "module" plugin type.
 +
 +
Plugin-related functionality for web services, is provided under '''htdocs/auth/webservice'''. This acts partly as a placeholder for things like language streams and theme assets. It also provides some actual functionality. When you set a user to have "webservice" as their auth instance, it means that user can authenticate to webservices by username and password; and they can no longer log in to Mahara as a normal user. This can be useful for "bot" users that act as a placeholder to connect to a remote service.
 +
 +
'''module/mobileapi''' is a module added to support the Mahara Mobile application. It also adds some scripts that allow users to self-generate Webservice auth tokens via a JSON script; this makes SSO from the Mahara Mobile application easier. Some of this functionality should probably be generalized out to be available to other plugins; but currently it's hard-coded to this module.
  
 
= History =
 
= History =

Revision as of 19:29, 23 September 2016

This page describes Mahara's webservices system. The webservices system provides a standardized way for a Mahara site to communicate with other applications, via HTTPS requests.

It allows Mahara to act as a "service provider", accepting incoming HTTP requests that run functions in Mahara and/or return data about Mahara. Mahara plugins can expose "service groups", which are sets of functionality to be accessed via HTTP. The site admin can then configure access to these via the Webservices administration page.

It also allows Mahara to act as a "service requester", with internal Mahara PHP functions triggering HTTP requests that access webservices on outside applications. Mahara plugins can use the "Connection Manager" interface to describe incoming webservices they would like to be able to consume; site & institution admins can then enable/disable these connections, and configure the specific service providers they point to, and their authentication credentials.

Concepts

Here's a brief explanation of some of the terms used on the Web services admin page.

Web service requester master switch =

This controls whether or not Mahara allows outgoing webservice requests. That is, your Mahara server sending out HTTP requests to an external server. Specifically, it controls whether or not you can use webservices that have been configured using the Mahara connection manager. You can disable that sitewide, for all plugins and institutions, with this switch.

You can check whether it's enabled or not by checking $CFG->webservice_requester_enabled

Note that this does not prevent all code in Mahara from accessing outside services. There is still plenty of code that connects to other servers using Curl to make HTTP requests, and this control has no impact on those. All it controls is the connections configured via the connection manager.

Web service provider master switch

This controls whether or not Mahara will accept incoming webservice requests. That is, your Mahara server accepting requests form other servers, which cause stuff to happen on your server. You can disable that sitewide, for all plugins and services and institutions, with this switch.

You can check this with $CFG->webservice_provider_enabled.

Note that this does not 100% guarantee that no "webservices" will connect to your application. There are some scripts in Mahara that are designed to be machine-readable, which are not controlled by this setting. (For instance, generated RSS feeds.) There may also be programs that "screenscrape" Mahara, using the same HTTP methods as a normal user's browser. All this setting controls, is webservices that are configured via the Mahara webservices API.

Web service protocols

Controls which data formats we accept incoming requests in. REST is the most popular. See the Moodle webservices documents for specifics about these protocols.

These can be checked by calling webservice_protocol_enabled($protocol) where protocol is rest, oauth, xmlrpc, or soap.

These settings are dependent on the "Web service provider master switch". If that switch is disabled, all of these services are disabled.

Functions

These are, as the name implies, individual functions that can be accessed via webservices. They're declared by plugins (or by the /webservice core code), and they map to underlying PHP functions which are executed when the webservice is accessed.

Implementing

Functions are defined in files that sit in the services/functions directory of a plugin, e.g. htdocs/module/mobileapi/webservice/functions. The functions are grouped into classes, with each class in a file that shares its name. For each function that's meant to be exposed as a webservice, the class must also define a function called "*_parameters" and one called "*_returns", which return objects describing the parameters and return values of the webservice function.

This part is essentially unchanged from Moodle's webservices, so their documentation is quite helpful on this: https://docs.moodle.org/dev/Adding_a_web_service_to_a_plugin#Write_the_external_function_descriptions

Once the underlying PHP function has been implemented, along with its associated description functions, you also need to describe it in your plugin's webservice/services.php file. The Moodle documentation describes the format: https://docs.moodle.org/dev/Adding_a_web_service_to_a_plugin#Declare_the_web_service_function (Mahara doesn't support the new "services" field; instead, services have to list which fields they accept)

All functions share the same namespace for their published names, so the published names should be prefaced with their plugin type and name, e.g. module_mobileapi_get_blogs instead of just get_blogs. (However, the code doesn't enforce this.)

You can also look at the "mobileapi" module for an example in Mahara.

Note: for backwards-compatibility, I recommend making every webservice function return an associative array (an external_single_structure), with all return values stored as fields of that associative array. See notes about backward-compatibility design below.

Global variables

Some things to be aware of when running code in a webservices function.

$USER will be set to the user whose username/password was used for authentication, or to the user who owns the token that was used for authentication. This is handled by the webservices library itself (server.php)

$SESSION should not be relied on. Mahara's sessions rely on cookies, and most webservices clients don't accept and send cookies the way a normal web browser does. Additionally, the user is re-authenticated on each webservice request, which will clear most session data anyhow. You should try to write webservice functions so that each one can act alone, using only the data provided in its parameters.

Services / Service groups

A service group (called a "service" in Moodle, and sometimes in Mahara), is a collection of functions, grouped together for access control purposes. You can't grant access to individual functions; instead you have to put one or more functions into a service group, and then grant access to that.

Service groups, like functions, are declared by plugins. (Mahara 15.04 through 16.04 also declared some "demo" service groups, but those have been removed in Mahara 16.10). They can also be created manually via the web UI, by selecting which functions should be included.

Service groups provided by plugins have a component, which indicates which plugin provides them. (Due to coding legacy issues, the component always ends with "/webservice", e.g. "module/mobileapi/webservice".) Core service groups provided by "htdocs/webservice" would belong to the component "webservice". Manually created service groups have no component value (this is what indicates they were manually created). The list of functions in a plugin-created service group cannot be modified by admins via the web interface; manually created service groups can be modified.

Service groups can also have a shortname, to make it easier for automated systems to refer to them. Normally this is unnecessary, because webservice calls usually only need to specify the function's name. However, it is needed when trying to use htdocs/module/mobileapi/json/token.php to auto-generate an authentication token for a service.

Implementing

Services are declared in their plugin's webservice/services.php file (e.g. htdocs/module/mobileapi/webservice/services.php). Core services would go in htdocs/webservice/services.php.

The format for declaring services is unchanged from Moodle: https://docs.moodle.org/dev/Adding_a_web_service_to_a_plugin#Declare_the_service

One addition Mahara has made, is that each service can also have an apiversion value. This is an optional integer to help webservice clients deal with different servers that may be running different versions of the Mahara software. It's recommended to increment this number each time you change the definition or behavior of any of the functions in the service group.

(Currently Mahara core only exposes this number via htdocs/module/mobileapi/json/info.php, and then only for the "maharamobile" service group.)

Files

The webservices plugin is based on a port of Moodle's webservices functionality. So the Moodle webservices documentation may be useful: https://docs.moodle.org/dev/Web_services

Most of the library code is under htdocs/webservice. This is a kind of "pseudo-plugin", because the original version of Mahara webservices was written in 2011 before we had the general-purpose "module" plugin type.

Plugin-related functionality for web services, is provided under htdocs/auth/webservice. This acts partly as a placeholder for things like language streams and theme assets. It also provides some actual functionality. When you set a user to have "webservice" as their auth instance, it means that user can authenticate to webservices by username and password; and they can no longer log in to Mahara as a normal user. This can be useful for "bot" users that act as a placeholder to connect to a remote service.

module/mobileapi is a module added to support the Mahara Mobile application. It also adds some scripts that allow users to self-generate Webservice auth tokens via a JSON script; this makes SSO from the Mahara Mobile application easier. Some of this functionality should probably be generalized out to be available to other plugins; but currently it's hard-coded to this module.

History

Legacy APIs

The htdocs/api directory contains code for previous webservice functionality in Mahara.

MNet is a custom protocol implemented for Moodle and Mahara, which allows for single-sign-on between Moodle and/or Mahara sites, and for remote procedure calls between them. Webservices should, eventually, replace MNet.

api/mobile was implemented to support the MaharaDroid native Android application (and some third-party applications have used it as well). It is activated via the "Allow mobile uploads" site config setting. When activated, it makes the scripts under api/mobile live, and it adds a field to the user's account settings page, where the user can manually created "mobile access tokens".

The idea is that the user manually creates an easy-to-type mobile access token. Then they launch their mobile app, and paste the token in there. The mobile app can then authenticate itself to the api/mobile scripts by sending the access token along with the user's username. The mobile access token is "rotated", replaced by a new random GUID, on each page request, and the new value is sent back to the mobile app with the response. This was meant to provide some scant additional security, back when most Mahara sites went out over HTTP rather than HTTPS. However, in practice, it meant that the application's connection would die if a communication error prevented it from getting the new code, requiring an inconvenient new connection.

The 3rd-party webservices plugin

In 2011, a Mahara Dev ported Moodle's (then-experimental) webservices plugin to Mahara. This is described here: https://wiki.mahara.org/wiki/Plugins/Auth/WebServices

The version of the plugin in Mahara 15.04+ is based on this optional plugin, and it attempts to allow a site to migrate from the optional plugin to the new core systems.

Mahara 15.04: Webservices in core

Mahara 15.04 moved the webservices plugin into core. This was primarily an update of the old plugin, as well as cleaning up its user interface. This version of the plugin shipped with some standard "Service groups" which were not designed for any specific application, but were meant to be a kind of demo of the technology.

Mahara 16.04: Connection manager

Mahara 16.04 added the Connection Manager system, which is meant to streamline the ability for Mahara plugins to make outgoing webservices requests. It provides an API by which a plugin may describe the kind of connection it is able to handle (for instance, a plugin to communicate with Moodle might describe which Moodle webservices component and function it wants to call, and which authentication data it needs). Site and Institution admins can then configure "instances" of these connections, with different values for different institutions (to allow, for instance, different institutions to connect to different Moodle sites).

Mahara 16.10: module/mobileapi

Work on Mahara Mobile required cleaning up the webservices code some more, and creating an actual webservice to be used by a real app. The specific webservices needed by the Mahara Mobile app were placed under a dedicated module, mobileapi, which also includes some JSON-based scripts that aren't using the standard Mahara webservices engine.

Changes to the core webservices functionality outside of that module, include:

  • Adding a "shortname" component to each service group, to make them easier for connecting applications to identify
  • Clarifying how "restrictedusers" and "tokenusers" interact
  • Allowing a webservice function parameter's default value to not match the type definition for that parameter. (The idea being that the type-enforcement is for incoming user data; whereas the default is a trustable hard-coded value, and being a different data type is often a useful signal to indicate the lack of a user-supplied value)
  • Allowing individual users to generate and delete their own webservice access tokens
    • Although, lacking a general permissions system like Mahara, this functionality is currently limited to the mobileapi module, by hard-coding.