Note:

If you want to create a new page for developers, you should create it on the Moodle Developer Resource site.

External services description: Difference between revisions

From MoodleDocs
m (Protected "External services description": Developer Docs Migration ([Edit=Allow only administrators] (indefinite)))
 
(39 intermediate revisions by 12 users not shown)
Line 1: Line 1:
{{Moodle_2.0}}
{{Template:Migrated|newDocId=/docs/apis/subsystems/external/description}}
 
== Purpose ==
This document explains how we describe and where we store the external services and functions in Moodle 2.0.
 
===Service discovery===
We store the service descriptions and a part of the function descriptions in the component/db/services.php file. Function implementations are located in externallib.php files - this file name is not mandatory, but it is strongly recommended. externallib.php files also contain the other part of the function descriptions (the function parameters descriptions).
 
During the upgrades the descriptions are parsed by a service discovery function that fills the database tables. Administrative UI may be used to change some configuration details. Services can also be defined via the admin UI, but it is recommended to use the new local plugin <font color="green">(Jerome: do we have a doc for that or more explanation?)</font> when adding custom new functions or services.
 
===Administration pages===
Moodle administrators will be able to manage services. By default all services will be disabled.
 
List of administration functionalities:
* enable a service (all the functions into this service will be available)
* associate a web service user (the user has web service capability) to some services => a user can only call a service that he is associated with
* create a custom service (name the service + add and remove existing external functions from this service)
* search easily function by component in order to create a custom service
 
===external_functions table===
This table list the web service functions. It makes sens to cal it external_functions as 1 web service function <=> 1 external function. This table maps the web service function name to the actual implementation of that function.
 
 
{| class="nicetable"
! Field
! Type
! Default
! Description
|-
| '''id'''
| int(10)
| auto-incrementing
|
|-
| '''name'''
| varchar(200)
|
| the web service name (e.g. the unique name of the external function in the web service API) - a unique identifier for each function; ex.: core_get_users, mod_assignment_submit
|-
| classname
| varchar(100)
|
| name of the class that contains method implementation; ex.: core_user_external, mod_assignment_external
|-
| methodname
| varchar(100)
|
| static method; ex.: get_users, submit
|-
| classpath
| varchar(255)
| NULL
| optional path to file with class definition - recommended for core classes only, null means use component/externallib.php; this path is relative to dirroot; ex.: user/externallib.php
|-
| component
| varchar(100)
|
| Component where function defined, needed for automatic updates. Service description file is found in the db folder of this component.
|-
| <font color="green">description</font>
| <font color="green">varchar(250)</font>
|
| <font color="green">A short human readable description of the function. It will be used to generate documentation and help the administrator to search a web service function.</font>
|}
 
Detailed function description is stored as a complex PHP structure in the implementation class using suffix _parameters and _returns.
 
===external_services table===
''Service'' is defined as a group of external functions. The main purpose of these ''services'' is to allow defining of granular access control for multiple external systems.
 
 
{| class="nicetable"
! Field
! Type
! Default
! Description
|-
| '''id'''
| int(10)
| auto-incrementing
|
|-
| '''name'''
| varchar(150)
|
| Name of service (gradeexport_xml_export, mod_chat_export) - appears on the admin page <font color="green">(This name is not human readable when displayed into administration. We could use lang file to translate them automatically if a lang string exist)</font>
|-
| enabled
| int(1)
| 0
| service enabled, for security reasons some services may be disabled- administrators may disable any service via the admin UI
|-
| requiredcapability
| varchar(255)
| NULL
| if capability name specified, user needs to have this permission in security context or in system context if context restriction not specified
|-
| restrictedusers
| int(1)
| 1
| 1 means on users explicitly listed in external_services_users may access this service, 0 means any user may use this service
|-
| component
| varchar(100)
| NULL
| Component where service defined - null means custom service defined in admin UI.
|}
 
===external_services_functions table===
Lists all functions that are available in each service.
 
{| class="nicetable"
! Field
! Type
! Default
! Description
|-
| '''id'''
| int(10)
| auto-incrementing
|
|-
| '''externalserviceid'''
| int(10)
|
| foreign key, reference external_services.id
|-
| '''functionname'''
| varchar(200)
|
| unique name of external function: references external_functions.name; the string value here simplifies service discovery and maintenance
|}
 
=== Function description ===
 
We need to describe each function in detail, including input and output, so that we can:
 
* help programmers quickly understand what they have to send and what to expect
* validate all incoming data as automatically as possible for security/correctness
* generate WSDL files for SOAP
* generate documentation for other protocols
 
There are three main parts working together to do this:
 
====services.php====
 
In the db/services.php of each component, is a structure something like this to describe the web service functions, and optionally, any larger services built up of several functions.
 
<code php>
$functions = array(
    'moodle_user_create_users' => array(          //web service name (unique in all Moodle)
        'classname'  => 'moodle_user_external', //class containing the function implementation
        'methodname'  => 'create_users',
        'classpath'  => 'user/externallib.php',    //file containing the class (only used for core external function, not needed if your file is 'component/externallib.php')
    )
);
 
$services = array(
    'servicename' => array(
        'functions' => array ('functionname', 'secondfunctionname'),
        'requiredcapability' => 'some/capability:specified',
        'restrictedusers' = >1,
        'enabled'=>0, //used only when installing the services
    )
);
</code>
 
The function name is arbitrary, but it must be globally unique so we highly recommend using the component name as prefix (and "moodle" for core functions).
 
The actual param description and description of returned values can be obtained from the same class by static method calls by adding '_parameters' and '_returns' to the methodname value.
 
====externallib.php====
 
In the file referenced as the location for the web service function, there are also complete descriptions of the parameters required.
 
''Base description classes:''
<code php>
abstract class external_description {
    public $desc;
    public $required;
 
    public function __construct($desc, $required) {
        $this->desc = $desc;
        $this->required = $required;
    }
}
 
class external_value extends external_description {
    public $type;
    public $default;
    public $allownull;
 
    public function __construct($type, $desc='', $required=true, $default=null, $allownull=true) {
        parent::_construct($desc, $required);
        $this->type      = $type;
        $this->default  = $default;
        $this->allownull = $allownull;
    }
}
 
class external_single_structure extends external_description {
    public $keys;
 
    public function __construct(array $keys, $desc='', $required=true) {
        parent::_construct($desc, $required);
        $this->keys = $keys; //key=>external_description
    }
}
 
class external_multiple_structure extends external_description {
    public $content;
 
    public function __construct(external_description $content, $desc='', $required=true) {
        parent::_construct($desc, $required);
        $this->content = $content;
    }
}
 
class external_function_parameters extends external_single_structure {
}
 
</code>
 
''Externallib.php examples:''
<code php>
class moodle_group_external extends external_api {
    public static function add_member_parameters() {
        return new external_function_parameters(
            array(
                'groupid' => new external_value(PARAM_INT, 'some group id'),
                'userid'  => new external_value(PARAM_INT, 'some user id')
            )
        );
    }
    public static function add_member($groupid, $userid) {
        $params = self::validate_prameters(self::add_member_parameters(), array('groupid'=>$groupid, 'userid'=>$userid));
 
        // all the parameter/behavioural checks and security constrainsts go here,
        // throwing exceptions if neeeded and and calling low level (grouplib)
        // add_member() function that will be one in charge of the functionality without
        // further checks.
 
    }
    public static function add_member_returns() {
        return null;
    }
 
 
    public static function add_members_parameters() {
        return new external_function_parameters(
            array(
                'membership' => new external_multiple_structure(
                    self::add_member_parameters()
                )
            )
        );
    }
    public static function add_members(array $membership) {
        $params = self::validate_prameters(self::add_members_parameters(), array('membership'=>$membership));
        foreach($params['membership'] as $one) { // simply one iterator over the "single" function if possible
            self::add_member($one->groupid, $one->userid);
        }
    }
    public static function add_members_returns() {
        return null;
    }
 
 
    public static function get_groups_parameters() {
        return new external_function_parameters(
            array(
                'groups' => new external_multiple_structure(
                    new external_single_structure(
                        array(
                            'groupid' => new external_value(PARAM_INT, 'some group id')
                        )
                    )
                )
            )
        );
    }
    public static function get_groups(array $groups) {
        $params = self::validate_prameters(self::get_groups_parameters(), array('groups'=>$groups));
 
        // all the parameter/behavioural checks and security constrainsts go here,
        // throwing exceptions if neeeded and and calling low level (grouplib)
        // get_groups() function that will be one in charge of the functionality without
        // further checks.
 
    }
    public static function get_groups_returns() {
        return new external_multiple_structure(
            new external_single_structure(
                array(
                    'id' => new external_value(PARAM_INT, 'some group id'),
                    'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
                    'description' => new external_value(PARAM_RAW, 'just some text'),
                    'enrolmentkey' => new external_value(PARAM_RAW, 'group enrol secret phrase')
                )
            )
        );
    }
}
 
 
class moodle_user_external extends external_api {
    public static function create_users_parameters() {
        new external_function_parameters(
            array(
                'users' => new external_multiple_structure(
                    new external_single_structure(
                        array(
                            'username' => new external_value(PARAM_USERNAME, 'Username policy is defined in Moodle security config'),
                            'password' => new external_value(PARAM_RAW, 'Moodle passwords can consist of any character'),
                            'firstname' => new external_value(PARAM_NOTAGS, 'The first name(s) of the user'),
                            'lastname' => new external_value(PARAM_NOTAGS, 'The family name of the user'),
                            'email' => new external_value(PARAM_EMAIL, 'A valid and unique email address'),
                            'auth' => new external_value(PARAM_AUTH, 'Auth plugins include manual, ldap, imap, etc', false),
                            'confirmed' => new external_value(PARAM_NUMBER, 'Active user: 1 if confirmed, 0 otherwise', false),
                            'idnumber' => new external_value(PARAM_RAW, 'An arbitrary ID code number perhaps from the institution', false),
                            'emailstop' => new external_value(PARAM_NUMBER, 'Email is blocked: 1 is blocked and 0 otherwise', false),
                            'lang' => new external_value(PARAM_LANG, 'Language code such as "en_utf8", must exist on server', false),
                            'theme' => new external_value(PARAM_THEME, 'Theme name such as "standard", must exist on server', false),
                            'timezone' => new external_value(PARAM_ALPHANUMEXT, 'Timezone code such as Australia/Perth, or 99 for default', false),
                            'mailformat' => new external_value(PARAM_INTEGER, 'Mail format code is 0 for plain text, 1 for HTML etc', false),
                            'description' => new external_value(PARAM_TEXT, 'User profile description, as HTML', false),
                            'city' => new external_value(PARAM_NOTAGS, 'Home city of the user', false),
                            'country' => new external_value(PARAM_ALPHA, 'Home country code of the user, such as AU or CZ', false),
                            'preferences' => new external_multiple_structure(
                                new external_single_structure(
                                    array(
                                        'type' => new external_value(PARAM_ALPHANUMEXT, 'The name of the preference'),
                                        'value' => new external_value(PARAM_RAW, 'The value of the preference')
                                    )
                                ), 'User preferences', false),
                            'customfields' => new external_multiple_structure(
                                new external_single_structure(
                                    array(
                                        'type' => new external_value(PARAM_ALPHANUMEXT, 'The name of the custom field'),
                                        'value' => new external_value(PARAM_RAW, 'The value of the custom field')
                                    )
                                ), 'User custom fields', false)
                        )
                    )
                )
            )
        );
    }
    public static function create_users(array $users) {
        $params = self::validate_prameters(self::create_users_parameters(), array('users'=>$users));
 
        foreach ($params['users'] as $user) {
            // all the parameter/behavioural checks and security constrainsts go here,
            // throwing exceptions if neeeded and and calling low level (userlib)
            // add_user() function that will be one in charge of the functionality without
            // further checks.
        }
    }
    public static function create_users_returns() {
        return new external_multiple_structure(
            new external_value('userid', PARAM_INT, 'id of the created user')
        );
    }
}
</code>
 
Note the use of Moodle-oriented PARAM_XXXX constants to define the format of each field.  These are used by the validation function to verify the data and throw an exception and abort the operation completely if the data changed during cleaning (ie it was malformed).
 
====Params====
 
We use the clean_param function to check data.  We have added some new checks for common Moodle data so that we get better cleaning.  eg
 
<code php>
...
        case PARAM_AUTH:
            $param = clean_param($param, PARAM_SAFEDIR);
            if (exists_auth_plugin($param)) {
                return $param;
            } else {
                return '';
            }
 
        case PARAM_LANG:
            $param = clean_param($param, PARAM_SAFEDIR);
            $langs = get_list_of_languages(false, true);
            if (in_array($param, $langs)) {
                return $param;
            } else {
                return '';  // Specified language is not installed
            }
 
        case PARAM_THEME:
            $param = clean_param($param, PARAM_SAFEDIR);
            if (file_exists($CFG->dirroot.'/theme/'.$param)) {
                return $param;
            } else {
                return '';  // Specified theme is not installed
            }
...
</code>
 
=== Function implementation ===
 
The function are generally stored in externallib.php files, as methods in a class that extends ''''external_api''''.  The actual file location is referenced by ''''classpath'''' in the services.php description.
 
<code php>
class moodle_user_external extends external_api {
    public static function create_users($users)
    $params = self::validate_prameters(self::create_users_parameters(), array('users'=>$users)); //ws object are associative array, ws list are non associative array
        foreach ($params['users'] as $user) {
            // all the parameter/behavioural checks and security constraints go here,
            // throwing exceptions if needed and and calling low level (userlib)
            // add_user() function that will be one in charge of the functionality without
            // further checks.
        }
    }
}
</code>
 
Note: there is more examples in the previous chapter
 
=== Way to fill the database tables ===
The Moodle upgrade will take care of the updates. We will need to add a function into lib/upgradelib.php/upgrade_plugins() that will check all web service description.
 
=== Return values are filtered by the servers ===
Web service function should be able to return whatever they want. Each server should be looking at the web services description and returns the expected values.
 
Some bulk requests may end up in the middle of processing elements, these partial failures should be indicated by special exceptions classes which include list of completed elements and original exception.
 
=See also=
* [[Web services]]
* [[External services security]]
 
[[Category:Web Services]]

Latest revision as of 06:12, 22 December 2022

Important:

This content of this page has been updated and migrated to the new Moodle Developer Resources. The information contained on the page should no longer be seen up-to-date.

Why not view this page on the new site and help us to migrate more content to the new site!