Commit 98ca273e authored by Aaron Wells's avatar Aaron Wells
Browse files

Bug 1620879: Remove extraneous "/webservice" from end of plugin components

Currently, a plugin has to list its component as e.g.
"module/mobileapi/webservice". This could just be "module/mobileapi".

Since we're using the component string as part of the automated token
request system, it's better to change this now to something more sensible.

behatnotneeded: Tests to come later

Change-Id: Ia7663065b79598d92c3bf8450c5539efb6aa3e2d
parent 2bffb229
Loading
Loading
Loading
Loading
+41 −0
Original line number Diff line number Diff line
@@ -506,6 +506,47 @@ function xmldb_auth_webservice_upgrade($oldversion=0) {
        if (!field_exists($table, $field)) {
            add_field($table, $field);
        }

        // The old code listed the components with "/webservice"
        // on the end of them, e.g. "artefact/internal/webservice".
        // This is redundant and makes for a poorer user interface.
        log_debug('Change in "component" format for plugins');
        $oldtail = '/webservice';
        $length = strlen($oldtail);

        // Functions
        execute_sql(
            "UPDATE {external_functions}
            SET
                component = LEFT(
                    component,
                    LENGTH(component) - {$length}
                )
            WHERE
                component <> 'webservice'
                AND RIGHT(
                    component,
                    {$length}
                ) = '{$oldtail}'
            "
        );

        // Services
        execute_sql(
            "UPDATE {external_services}
            SET
                component = LEFT(
                    component,
                    LENGTH(component) - {$length}
                )
            WHERE
                component <> 'webservice'
                AND RIGHT(
                    component,
                    {$length}
                ) = '{$oldtail}'
            "
        );
    }

    // sweep for webservice updates everytime
+20 −9
Original line number Diff line number Diff line
@@ -1501,8 +1501,11 @@ function check_dir_exists($dir, $create=true, $recursive=true) {
 * @param string $filename the name of the file to include within the plugin structure
 * @param string $function (optional, defaults to require) the require/include function to use
 * @param string $nonfatal (optional, defaults to false) just returns false if the file doesn't exist
 * @param array $returnvars (optional, defaults to null) Variables (defined in the file) to return.
 * Useful for files like version.php that simply define variables. If null, instead returns the
 * value of the include/require operation.
 */
function safe_require($plugintype, $pluginname, $filename='lib.php', $function='require_once', $nonfatal=false) {
function safe_require($plugintype, $pluginname, $filename='lib.php', $function='require_once', $nonfatal=false, $returnvars = null) {
    $plugintypes = plugin_types();
    if (!in_array($plugintype, $plugintypes)) {
        throw new SystemException("\"$plugintype\" is not a valid plugin type");
@@ -1561,11 +1564,17 @@ function safe_require($plugintype, $pluginname, $filename='lib.php', $function='
        throw new SystemException ("File $fullpath was outside document root!");
    }

    if ($function == 'require') { return require($realpath); }
    if ($function == 'include') { return include($realpath); }
    if ($function == 'require_once') { return require_once($realpath); }
    if ($function == 'include_once') { return include_once($realpath); }
    if ($function == 'require') { $isloaded = require($realpath); }
    if ($function == 'include') { $isloaded = include($realpath); }
    if ($function == 'require_once') { $isloaded = require_once($realpath); }
    if ($function == 'include_once') { $isloaded = include_once($realpath); }

    if ($isloaded && $returnvars && is_array($returnvars)) {
        return compact($returnvars);
    }
    else {
        return $isloaded;
    }
}

/**
@@ -1583,11 +1592,13 @@ function safe_require($plugintype, $pluginname, $filename='lib.php', $function='
 * @param string $filename the name of the file to include within the plugin structure
 * @param string $function (optional, defaults to require) the require/include function to use
 * @param string $nonfatal (optional, defaults to false) just returns false if the file doesn't exist
 * @param array $returnvars (optional, defaults to null) Variables (defined in the file) to return.
 * Useful for files like version.php that simply define variables. If null, instead returns the
 * value of the include/require operation.
 */
function safe_require_plugin($plugintype, $pluginname, $filename='lib.php', $function='require_once', $nonfatal=false) {
function safe_require_plugin($plugintype, $pluginname, $filename='lib.php', $function='require_once', $nonfatal=false, $returnvars = null) {
    try {
        safe_require($plugintype, $pluginname, $filename, $function, $nonfatal);
        return true;
        return safe_require($plugintype, $pluginname, $filename, $function, $nonfatal, $returnvars);
    }
    catch (SystemException $e) {
        if (get_field($plugintype . '_installed', 'active', 'name', $pluginname) == 1) {
@@ -3618,7 +3629,7 @@ function get_my_tags($limit=null, $cloud=true, $sort='freq') {
        array($id, $id, $id)
    );
    if (!$tagrecords) {
        return false;
        return array();
    }
    if ($cloud) {
        $minfreq = $tagrecords[count($tagrecords) - 1]->count;
+205 −68
Original line number Diff line number Diff line
@@ -46,14 +46,14 @@ define('EXTERNAL_TOKEN_EMBEDDED', 1);
define('EXTERNAL_TOKEN_OAUTH1', 2);

/**
 * OAuth Token type for registered applications oauth v1
 * Security token self-generated by a normal user
 */
define('EXTERNAL_TOKEN_USER', 3);

/**
 * Personal User Tokens expiry time
 * Personal User Tokens expiry time (12 weeks)
 */
define('EXTERNAL_TOKEN_USER_EXPIRES', (30 * 24 * 60 * 60));
define('EXTERNAL_TOKEN_USER_EXPIRES', (12 * 7 * 24 * 60 * 60));

define('WEBSERVICE_AUTHMETHOD_USERNAME', 0);
define('WEBSERVICE_AUTHMETHOD_PERMANENT_TOKEN', 1);
@@ -62,6 +62,7 @@ define('WEBSERVICE_AUTHMETHOD_OAUTH_TOKEN', 3);
define('WEBSERVICE_AUTHMETHOD_USER_TOKEN', 4);

// strictness check
define('IGNORE_MISSING', 1);
define('MUST_EXIST', 2);

/** Get remote addr constant */
@@ -169,25 +170,22 @@ function webservice_validate_user($dbuser) {
}

/**
 * List all installed component web service directories
 * List all potential webservice locations
 * (i.e. plugins, local, and the "pseudo-module" /webservice).
 *
 * @return array of web service plugin directories
 */
function get_ws_subsystems() {
    static $plugindirs = null;
    static $plugindirs = false;

    if (!$plugindirs) {
        // add the root webservice first which is empty because it is docroot, and local
        $plugindirs = array(WEBSERVICE_DIRECTORY, 'local/' . WEBSERVICE_DIRECTORY);

        foreach (plugin_types_installed() as $t) {
            foreach (plugins_installed($t) as $name => $plugindata) {
                $plugindir = $t . '/' . $name;
                if (!empty($plugindata->authplugin)) {
                    $plugindir = 'auth/' . $plugindata->authplugin . '/' . $plugindir;
                }
                $plugindirs[] = $plugindir . '/' . WEBSERVICE_DIRECTORY;
            }
        $plugindirs = [
            'webservice',
            'local'
        ];
        $activeplugins = plugin_all_installed();
        foreach ($activeplugins as $plugindata) {
            $plugindirs[] = "{$plugindata->plugintype}/{$plugindata->name}";
        }
    }

@@ -263,6 +261,47 @@ function webservice_create_service_token($servicename, $userid, $institution = '
    return webservice_generate_token(EXTERNAL_TOKEN_EMBEDDED, $service, $userid, $institution,  $validuntil, $iprestriction);
}

/**
 * Calculate where the webservices directory should be, for a given value of
 * "component".
 *
 * There are three general types of "component" value we expect to see.
 * 1. "webservice": Indicates a the "core" webservices, which are in htdocs/webservice
 * 2. "local": Indicates custom webservices under htdocs/local/webservice
 * 3. "{plugintype}/{pluginname}": Indicates webservices for a plugin.
 *
 * @param string $component The component to look for
 * @param reference $plugintype If the component represents a plugin, the plugin's type will
 * be returned via this variable, passed by reference.
 * @param reference $pluginname If the component represents a plugin, the plugin's name will
 * be returned via this variable, passed by reference.
 * @return string Relative path to the component's webservices directory. If the component
 * is a plugin, this path will be relative the plugin's directory. Otherwise, it'll be
 * relative to $CFG->docroot.
 * @throws WebserviceCodingException
 */
function webservice_component_ws_directory($component, &$plugintype, &$pluginname) {
    if ($component == WEBSERVICE_DIRECTORY) {
        $plugintype = false;
        $pluginname = false;
        return WEBSERVICE_DIRECTORY;
    }

    if ($component == 'local') {
        $plugintype = false;
        $pluginname = false;
        return 'local/' . WEBSERVICE_DIRECTORY;
    }

    $bits = explode('/', $component);
    if (count($bits) == 2) {
        list($plugintype, $pluginname) = $bits;
        return WEBSERVICE_DIRECTORY;
    }

    throw new WebserviceCodingException("Invalid component name: '{$component}'");
}

/**
 * Returns detailed function information
 * @param string|object $function name of external function or record from external_function
@@ -270,46 +309,100 @@ function webservice_create_service_token($servicename, $userid, $institution = '
 *                        MUST_EXIST means throw exception if no record or multiple records found
 * @return object description or false if not found or exception thrown
 */
function webservice_function_info($function, $strictness=MUST_EXIST) {
function webservice_function_info($function, $strictness=MUST_EXIST, $component = null) {
    $mustexist = ($strictness === MUST_EXIST);
    if (!is_object($function)) {
        if (!$function = get_record('external_functions', 'name', $function, NULL, NULL, NULL, NULL, '*')) {
        if ($component) {
            $function = get_record('external_functions', 'name', $function, 'component', $component);
        }
        else {
            $function = get_record('external_functions', 'name', $function);
        }
        if (!$function) {
            return false;
        }
        $component = $function->component;
    }

    //first find and include the ext implementation class
    $function->classpath = empty($function->classpath) ? get_config('docroot') . $function->component : get_config('docroot') . $function->classpath;
    if (!file_exists($function->classpath . '/functions/' . $function->classname . '.php')) {
    if (!class_exists($function->classname)) {

        $wsdir = webservice_component_ws_directory(
            $function->component,
            $plugintype,
            $pluginname
        );

        if ($plugintype && $pluginname) {
            // Standard plugin; can use safe_require
            $foundfile = safe_require_plugin(
                $plugintype,
                $pluginname,
                $wsdir . '/functions/' . $function->classname . '.php',
                'require_once',
                true
            );
            if (!$foundfile) {
                if ($mustexist) {
                    throw new WebserviceCodingException(get_string('cannotfindimplfile', 'auth.webservice'));
                }
                return false;
            }
        }
        else {
            // Not a plugin, must handle manually
            $filepath = get_config('docroot') . $wsdir . '/functions/' . $function->classname . '.php';
            if (!file_exists($filepath)) {
                if ($mustexist) {
                    throw new WebserviceCodingException(get_string('cannotfindimplfile', 'auth.webservice'));
                }
    require_once($function->classpath . '/functions/' . $function->classname . '.php');
                return false;
            }
            require_once($filepath);
        }
    }

    $function->parameters_method = $function->methodname . '_parameters';
    $function->returns_method    = $function->methodname . '_returns';

    // make sure the implementaion class is ok
    if (!method_exists($function->classname, $function->methodname)) {
        if ($mustexist) {
            throw new WebserviceCodingException(get_string('missingimplofmeth', 'auth.webservice', $function->classname . '::' . $function->methodname));
        }
        return false;
    }
    if (!method_exists($function->classname, $function->parameters_method)) {
        if ($mustexist) {
            throw new WebserviceCodingException(get_string('missingparamdesc', 'auth.webservice'));
        }
        return false;
    }
    if (!method_exists($function->classname, $function->returns_method)) {
        if ($mustexist) {
            throw new WebserviceCodingException(get_string('missingretvaldesc', 'auth.webservice'));
        }
        return false;
    }

    // fetch the parameters description
    $function->parameters_desc = call_user_func(array($function->classname, $function->parameters_method));
    if (!($function->parameters_desc instanceof external_function_parameters)) {
        if ($mustexist) {
            throw new WebserviceCodingException(get_string('invalidparamdesc', 'auth.webservice'));
        }
        return false;
    }

    // fetch the return values description
    $function->returns_desc = call_user_func(array($function->classname, $function->returns_method));
    // null means void result or result is ignored
    if (!is_null($function->returns_desc) and !($function->returns_desc instanceof external_description)) {
        if ($mustexist) {
            throw new WebserviceCodingException(get_string('invalidretdesc', 'auth.webservice'));
        }
        return false;
    }

    //now get the function description
    //TODO: use localised lang pack descriptions, it would be nice to have
@@ -317,15 +410,10 @@ function webservice_function_info($function, $strictness=MUST_EXIST) {
    //      on the other hand this is still a bit in a flux and we need to find some new naming
    //      conventions for these descriptions in lang packs
    $function->description = null;

    $servicesfile = $function->classpath . '/services.php';

    if (file_exists($servicesfile)) {
        $functions = null;
        include($servicesfile);
        if (isset($functions[$function->name]['description'])) {
            $function->description = $functions[$function->name]['description'];
        }
    $result = webservice_load_services_file($function->component);
    $functionlist = $result['functions'];
    if (isset($functionlist[$function->name]['description'])) {
        $function->description = $functionlist[$function->name]['description'];
    }

    return $function;
@@ -488,11 +576,7 @@ class external_api {
                        throw new WebserviceInvalidParameterException(get_string('errormissingkey', 'auth.webservice', $key));
                    }
                    if ($subdesc->required == VALUE_DEFAULT) {
                        try {
                            $result[$key] = self::validate_parameters($subdesc, $subdesc->default);
                        } catch (WebserviceInvalidParameterException $e) {
                            throw new WebserviceParameterException('invalidextparam', $key);
                        }
                        $result[$key] = $subdesc->default;
                    }
                }
                else {
@@ -1835,14 +1919,10 @@ abstract class webservice_base_server extends webservice_server {
/**
 * Delete all service and external functions information defined in the specified component.
 * @param string $component name of component (mahara, local, etc.)
 * @param bool $dir does this component name have the directory on it
 * @return void
 */
function external_delete_descriptions($component, $dir=true) {
function external_delete_descriptions($component) {

    if ($dir) {
        $component .= ($component ? '/' : '') . WEBSERVICE_DIRECTORY;
    }
    $params = array($component);

    delete_records_select('external_services_users', "externalserviceid IN (SELECT id FROM {external_services} WHERE component = ?)", $params);
@@ -1872,48 +1952,97 @@ function webservice_clean_webservice_logs() {
function external_reload_webservices() {

    // first - prune all components that are nolonger available/installed
    $dead_components = get_records_sql_array('SELECT DISTINCT component AS component FROM {external_functions} WHERE component NOT IN ('.
                                                implode(', ', array_fill(1, count(get_ws_subsystems()), '?')).')', get_ws_subsystems());
    $dead_components = get_records_sql_array(
        'SELECT DISTINCT component AS component
        FROM {external_functions}
        WHERE
            component != \'\'
            AND component NOT IN ('.
                implode(', ', array_fill(1, count(get_ws_subsystems()), '?'))
        .')',
        get_ws_subsystems()
    );
    if ($dead_components) {
        foreach ($dead_components as $component) {
            external_delete_descriptions($component->component, false);
            external_delete_descriptions($component->component);
        }
    }
    foreach (get_ws_subsystems() as $component) {
        external_reload_component($component, false);
        external_reload_component($component);
    }

    return true;
}


/**
 * Reload the webservice descriptions for a single plugins
 * Utility function to load up the $services and $functions arrays
 * from the services.php file for the specified component. (Calling it
 * from its own function in order to avoid polluting the namespace.)
 *
 * @param string $component
 * @param bool $dir does this component name have the directory on it
 * @return bool true = success
 * @return array [$services, $functions]
 */
function webservice_load_services_file($component) {

function external_reload_component($component, $dir=true) {
    $wsdir = webservice_component_ws_directory(
        $component,
        $plugintype,
        $pluginname
    );

    if ($plugintype && $pluginname) {
        // Standard plugin; can use safe_require
        $file = safe_require(
            $plugintype,
            $pluginname,
            WEBSERVICE_DIRECTORY . '/services.php',
            'include',
            true,
            array('services', 'functions')
        );
        $services = $file['services'];
        $functions = $file['functions'];
    }
    else {
        // Not a plugin, must handle manually
        $filepath = get_config('docroot') . $wsdir . '/services.php';
        if (file_exists($filepath . '/services.php')) {
            include($filepath . '/services.php');
        }
    }

    // are there web service plugins
    if ($dir) {
        $component .= ($component ? '/' : '') . WEBSERVICE_DIRECTORY;
    if (empty($services)) {
        $services = array();
    }
    if (empty($functions)) {
        $functions = array();
    }
    $basepath = get_config('docroot') . $component;
    return array(
        'services' => $services,
        'functions' => $functions
    );
}

/**
 * Reload the webservice descriptions for a single plugins
 *
 * @param string $component ("webservice", "local", or a plugin e.g.
 * "artefact/internal".
 * @return bool Whether or not we found webservices for this component
 */
function external_reload_component($component) {

    // Load arrays $services and $functions from the plugin or component's
    // {path_to_plugin}/webservice/services.php file.
    $result = webservice_load_services_file($component);
    $services = $result['services'];
    $functions = $result['functions'];

    // is there a webservice directory with the right files
    if (!file_exists($basepath) || !file_exists($basepath.'/services.php')) {
    // Does the component have a valid services.php file?
    if (!$services && !$functions) {
        external_delete_descriptions($component);
        return false;
    }
    $defpath = $basepath . '/services.php';

    // load new info
    $functions = array();
    $services = array();
    include($defpath);

    // update all function first
    $dbfunctions = get_records_array('external_functions', 'component', $component);
@@ -1927,7 +2056,15 @@ function external_reload_component($component, $dir=true) {
            }
            $function = $functions[$dbfunction->name];
            unset($functions[$dbfunction->name]);
            $function['classpath'] = empty($function['classpath']) ? $component : $function['classpath'];
            // Fill in default classpath
            if (empty($function['classpath'])) {
                if ($component === WEBSERVICE_DIRECTORY) {
                    $function['classpath'] = WEBSERVICE_DIRECTORY;
                }
                else {
                    $function['classpath'] = $component . '/' . WEBSERVICE_DIRECTORY;
                }
            }

            $update = false;
            if ($dbfunction->classname != $function['classname']) {