Commit aa743c03 authored by Piers Harding's avatar Piers Harding Committed by Aaron Wells
Browse files

Bug 1393536: client connection manager

* added new client connection manager screens
* added client connection manager backend

behatnotneeded

Change-Id: Iac103616c7a9cd68cc94ea301a4cb808fe090669
parent 9fb9d801
Loading
Loading
Loading
Loading
+38 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@ $string['title'] = 'Web services';
$string['description'] = 'Web services-only users authenticated against Mahara\'s database';
$string['webservicesconfig'] = 'Configuration';
$string['webservicesconfigdesc'] = 'Here you can set up the varying web services rules and enable or disable them.';
$string['webserviceconnectionsconfigdesc'] = 'Setup the connection objects that registered plugins can use for communication with external systems';
$string['completeregistration'] = 'Complete registration';
$string['emailalreadytaken'] = 'This email address has already registered here';
$string['iagreetothetermsandconditions'] = 'I agree to the Terms and Conditions.';
@@ -51,11 +52,43 @@ $string['usernameinvalidform'] = 'Usernames may contain letters, numbers and mos
$string['usernameinvalidadminform'] = 'Usernames may contain letters, numbers and most common symbols, and must be from 3 to 236 characters in length. Spaces are not allowed.';
$string['youmaynotregisterwithouttandc'] = 'You may not register unless you agree to abide by the <a href="terms.php">Terms and Conditions</a>.';


$string['pluginconnections'] = 'Plugin Connection Objects';
$string['pcdescription'] = 'Create connections connected to the Connection Objects of registered plugins.';

$string['addconnection'] = 'Add Client Connection';
$string['editconnection'] = 'Edit Client Connection';
$string['clientconnections'] = 'Client Connection';
$string['plugin'] = 'Connection Plugin';
$string['clienturl'] = 'Web Service URL';
$string['password'] = 'Password';
$string['parameters'] = 'Fixed Parameters to pass';
$string['certificate'] = 'XML-RPC Partner Certificate';
$string['enable'] = 'Connection enabled';
$string['json'] = 'JSON encoded';
$string['isfatal'] = 'Is fatal on error';
$string['type'] = 'Web Service Type';
$string['nameexists'] = "Name already in use";
$string['emptytoken'] = 'Token must be supplied';
$string['emptyuserpass'] = 'User and Password must be supplied';
$string['emptycert'] = 'Certificate must be supplied';
$string['header'] = 'Header Name';
$string['useheader'] = 'Put authentication in Header';
$string['invalidauthtypecombination'] = 'Invalid authentication type selected for %s';
$string['emptycertextended'] = 'When using certificate based auth you must also enter a token or username/password';
$string['emptyoauth'] = 'Consumer and Secret must be supplied for OAuth1.x';
$string['consumer'] = 'Consumer Key';
$string['secret'] ='Secret';

// core webservices strings start here
$string['control_webservices1'] = 'Use web services: ';
$string['control_webservices'] = 'Switch web services on or off: ';
$string['control_webservices_connections'] = 'Switch web service connections on or off: ';
$string['masterswitch'] = 'Web services master switch';
$string['connectionsmasterswitch'] = 'Web service client connections master switch';
$string['formatdate'] = '';
$string['protocolswitches'] = 'Switch protocols on or off';
$string['connectionsswitch'] = 'Switch managed client connections on or off';
$string['manage_protocols'] = 'Enable or disable protocols that are to be supported:';
$string['protocol'] = 'Protocol';
$string['rest'] = 'REST';
@@ -95,6 +128,7 @@ $string['missingimplofmeth'] = 'Missing implementation method of "%s"';
$string['cannotfindimplfile'] = 'Cannot find file with external function implementation';

$string['apptokens'] = 'Application connections';
$string['connections'] = 'Connection Manager';
$string['servicetokens'] = 'Manage service access tokens';
$string['tokens'] = 'Service access tokens';
$string['users'] = 'Service users';
@@ -162,6 +196,9 @@ $string['wsdoc'] = 'Web services documentation';
$string['testclient'] = 'Web services test client';
$string['tokenauth'] = 'Token';
$string['userauth'] = 'User';
$string['certauth'] = 'Certificate';
$string['wsseauth'] = 'WSSE';
$string['oauth1auth'] = 'OAuth1.x';
$string['authtype'] = 'Authentication type';
$string['sauthtype'] = 'AuthType';
$string['enterparameters'] = 'Enter function parameters';
@@ -176,6 +213,7 @@ $string['unabletoruntestclient'] = 'Web service test client needs to be run unde
$string['accesstokens'] = 'OAuth access tokens';
$string['notokens'] = 'You have no application tokens';
$string['oauth'] = 'OAuth';
$string['oauth1'] = 'OAuth1.x';
$string['oauthv1sregister'] = 'OAuth service registration';
$string['userapplications'] = 'OAuth consumer keys';
$string['accessto'] = 'Access to';
+39 −4
Original line number Diff line number Diff line
@@ -1326,5 +1326,40 @@
                <KEY NAME="groupfk" TYPE="foreign" FIELDS="group" REFTABLE="group" REFFIELDS="id" />
            </KEYS>
        </TABLE>
        <TABLE NAME="client_connections_institution">
            <FIELDS>
                <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true"/>
                <FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
                <FIELD NAME="plugintype" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"  COMMENT="Plugin type eg: notification"/>
                <FIELD NAME="pluginname" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"  COMMENT="Plugin type eg: log"/>
                <FIELD NAME="priority" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
                <FIELD NAME="class" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"  COMMENT="Plugin class name"/>
                <FIELD NAME="connection" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"  COMMENT="connection name"/>
                <FIELD NAME="institution" TYPE="char" LENGTH="255" NOTNULL="true" />
                <FIELD NAME="url"  TYPE="text" LENGTH="small" NOTNULL="true" SEQUENCE="false"  COMMENT="URL for the web service call"/>
                <FIELD NAME="username" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"  COMMENT="Username for the web service call"/>
                <FIELD NAME="password" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"  COMMENT="Password for the web service call"/>
                <FIELD NAME="consumer" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"  COMMENT="OAuth Consumer Key for the web service call"/>
                <FIELD NAME="secret" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"  COMMENT="OAuth Secret for the web service call"/>
                <FIELD NAME="token" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"  COMMENT="Auth token for the web service call"/>
                <FIELD NAME="useheader" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
                <FIELD NAME="header" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"  COMMENT="HTTP Header name if useheader"/>
                <FIELD NAME="certificate" TYPE="text" LENGTH="small" NOTNULL="false" COMMENT="Auth certificate for the web service call"/>
                <FIELD NAME="parameters" TYPE="text" LENGTH="small" NOTNULL="false" COMMENT="Additional parameters for the web service call"/>
                <FIELD NAME="type" TYPE="char" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
                <FIELD NAME="authtype" TYPE="char" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
                <FIELD NAME="json" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
                <FIELD NAME="enable" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
                <FIELD NAME="isfatal" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
                <FIELD NAME="version" TYPE="char" LENGTH="50" NOTNULL="false" SEQUENCE="false"/>
            </FIELDS>
            <KEYS>
            <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
            <KEY NAME="institution" TYPE="foreign" FIELDS="institution" REFTABLE="institution" REFFIELDS="name"/>
            </KEYS>
          <INDEXES>
            <INDEX NAME="connectionk" UNIQUE="true" FIELDS="name,class,connection,institution" COMMENT="the connections can be repeated but the name must be unique for a given type and institution"/>
          </INDEXES>
        </TABLE>
    </TABLES>
</XMLDB>
+34 −0
Original line number Diff line number Diff line
@@ -4522,5 +4522,39 @@ function xmldb_core_upgrade($oldversion=0) {
        rmdirr(get_config('dataroot') . 'smarty');
    }

    if ($oldversion < 2016070800) {
        log_debug('Add client_connections_institution table');
        $table = new XMLDBTable('client_connections_institution');
        $table->addFieldInfo('id', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL, XMLDB_SEQUENCE);
        $table->addFieldInfo('name', XMLDB_TYPE_CHAR, 255, false, XMLDB_NOTNULL);
        $table->addFieldInfo('plugintype', XMLDB_TYPE_CHAR, 255, false, XMLDB_NOTNULL);
        $table->addFieldInfo('pluginname', XMLDB_TYPE_CHAR, 255, false, XMLDB_NOTNULL);
        $table->addFieldInfo('priority', XMLDB_TYPE_INTEGER, 10, false, XMLDB_NOTNULL);
        $table->addFieldInfo('class', XMLDB_TYPE_CHAR, 255, false, XMLDB_NOTNULL);
        $table->addFieldInfo('connection', XMLDB_TYPE_CHAR, 255, false, XMLDB_NOTNULL);
        $table->addFieldInfo('institution', XMLDB_TYPE_CHAR, 255, false, XMLDB_NOTNULL);
        $table->addFieldInfo('url', XMLDB_TYPE_TEXT, 'small', false, XMLDB_NOTNULL);
        $table->addFieldInfo('username', XMLDB_TYPE_CHAR, 255, false);
        $table->addFieldInfo('password', XMLDB_TYPE_CHAR, 255, false);
        $table->addFieldInfo('consumer', XMLDB_TYPE_CHAR, 255, false);
        $table->addFieldInfo('secret', XMLDB_TYPE_CHAR, 255, false);
        $table->addFieldInfo('token', XMLDB_TYPE_CHAR, 255, false);
        $table->addFieldInfo('header', XMLDB_TYPE_CHAR, 255, false);
        $table->addFieldInfo('useheader', XMLDB_TYPE_INTEGER, 1, false, XMLDB_NOTNULL, null, null, null, 1);
        $table->addFieldInfo('certificate', XMLDB_TYPE_TEXT, 'small', false);
        $table->addFieldInfo('parameters', XMLDB_TYPE_TEXT, 'small', false);
        $table->addFieldInfo('type', XMLDB_TYPE_CHAR, 10, false, XMLDB_NOTNULL, null, null, null, 2);
        $table->addFieldInfo('authtype', XMLDB_TYPE_CHAR, 10, false, XMLDB_NOTNULL);
        $table->addFieldInfo('json', XMLDB_TYPE_INTEGER, 1, false, XMLDB_NOTNULL, null, null, null, 1);
        $table->addFieldInfo('enable', XMLDB_TYPE_INTEGER, 1, false, XMLDB_NOTNULL, null, null, null, 1);
        $table->addFieldInfo('isfatal', XMLDB_TYPE_INTEGER, 1, false, XMLDB_NOTNULL, null, null, null, 1);
        $table->addFieldInfo('version', XMLDB_TYPE_CHAR, 255, false);
        $table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id'));
        $table->addIndexInfo('connectionk', XMLDB_INDEX_UNIQUE, array('name', 'class', 'connection', 'institution'));
        $table->addKeyInfo('institution', XMLDB_KEY_FOREIGN, array('institution'), 'institution', array('name'));
        create_table($table);
        clear_menu_cache();
    }

    return $status;
}
+222 −0
Original line number Diff line number Diff line
@@ -1916,6 +1916,44 @@ interface IPlugin {
    public static function get_plugintype_name();
}

/**
 * defines for web services types and authentication
 *
 *
 */
define('WEBSERVICE_TYPE_SOAP', 'soap');
define('WEBSERVICE_TYPE_XMLRPC', 'xmlrpc');
define('WEBSERVICE_TYPE_REST', 'rest');
define('WEBSERVICE_TYPE_OAUTH1', 'oauth1');

define('WEBSERVICE_AUTH_USERPASS', 'user');
define('WEBSERVICE_AUTH_TOKEN', 'token');
define('WEBSERVICE_AUTH_CERT', 'cert');
define('WEBSERVICE_AUTH_WSSE', 'wsse');

/**
 * Generate an HTTP context object
 *
 * @param string $url
 * @return object - stream context object
 */

function webservice_create_context($url) {
    $hostname = parse_url($url, PHP_URL_HOST);
    $context = array('http' => array ('method' => 'POST',
                                'request_fulluri' => true,),
            );
    if (get_config('disablesslchecks')) {
        $context['ssl'] = array('verify_host' => false,
                           'verify_peer' => false,
                           'verify_peer_name' => false,
                           'SNI_server_name' => $hostname,
                           'SNI_enabled'     => true,);
    }
    $context = stream_context_create($context);
    return $context;
}

/**
 * Base class for all plugintypes.
 */
@@ -1958,6 +1996,190 @@ abstract class Plugin implements IPlugin {
        return array();
    }

    /**
     * This function returns an array of client connection by unique name.
     *
     * The return value should be array of objects. Each object should have these fields:
     *
     *  - connection: The name of the client connection handle.
     *  - name: The descriptive name of the client connection for display purposes
     *  - version: A version required for the connection
     *  - notes: descriptive text that developer might want to show user
     *  - type: Protocol type required eg: WEBSERVICE_CLIENT_TYPE_SOAP
     *  - isfatal: Should an error be fatal
     *
     * @return array
     */
    public static function define_webservice_connections() {
        return array();
    }

    /**
     * This function returns an array of client connection records.
     *
     * @param array $institutions the institutions for the context of selecting
     *        the client connections
     *
     * @return array
     */
    public static function calculate_webservice_connections($institutions) {

        $me = get_called_class();
        $connection_defs = call_user_func($me . '::define_webservice_connections');
        $connections = array();
        foreach ($connection_defs as $def) {
            if (isset($def['connection'])) {
                $cname = $def['connection'];
                if ($results = get_records_sql_assoc(
                    'SELECT cci.*
                     FROM client_connections_institution AS cci
                     WHERE cci.class = ? AND
                           cci.connection = ? AND
                           cci.institution IN ('.join(',', array_map('db_quote', $institutions)).') AND enable = 1', array($me, $cname))
                ) {
                    foreach ($results as $c) {
                        $c->version = $def['version'];
                        $c->connectorname = $def['name'];
                        $connections[]= $c;
                    }
                }
            }
        }
        return $connections;
    }

    /**
     * This function returns an array of client connections.
     *
     * @param object $user the user for the context of selecting the client connections
     *
     * @return array
     */
    public static function get_webservice_connections($user=null) {
        global $USER;

        // are web service connections enabled?
        if (!get_config('webservice_connections_enabled')) {
            error_log('disabled');
            return array();
        }

        require_once(get_config('docroot') . 'webservice/lib.php');

        $userinstitutions = ($user == null ? $USER->get('institutions') : load_user_institutions($user->id));
        $userinstitutions[]= 'mahara';
        $cdefs = self::calculate_webservice_connections($userinstitutions);

        $connections = array();
        foreach ($cdefs as $c) {
            $client = null;
            $auth = array();
            $authtype = null;
            if (!empty($c->token)) {
                $authtype = 'token';
                if ($c->useheader) {
                    $auth['header'] = (empty($c->header) ? 'Authorization' : $c->header.": ".$c->token);
                }
                else {
                    if (strpos($c->token, '=')) {
                        list($k, $v) = explode('=', $c->token);
                        $auth[$k] = $v;
                    }
                    else {
                        $auth['wstoken'] = $c->token;
                    }
                }
            }
            else if (!empty($c->username) && !empty($c->password) ) {
                $authtype = 'user';
                if (strpos($c->username, '=')) {
                    list($k, $v) = explode('=', $c->token);
                    $auth[$k] = $v;
                }
                else {
                    $auth['wsusername'] = $c->username;
                }
                if (strpos($c->password, '=')) {
                    list($k, $v) = explode('=', $c->password);
                    $auth[$k] = $v;
                }
                else {
                    $auth['wspassword'] = $c->password;
                }
            }

            // other static parameters - one per line
            // error_log('connection: '.var_export($c, true));
            if (!empty($c->parameters)) {
                $params = explode("\n", $c->parameters);
                foreach ($params as $p) {
                    if (strpos($p, '=')) {
                        list($k, $v) = explode('=', $p);
                        $auth[$k] = $v;
                    }
                }
            }

            switch ($c->type) {
                case WEBSERVICE_TYPE_SOAP:
                    require_once(get_config('docroot') . "webservice/soap/lib.php");
                    libxml_disable_entity_loader(true);
                    if ($c->authtype == WEBSERVICE_AUTH_WSSE) {
                        //force SOAP synchronous mode
                        $client = new webservice_soap_client($c->url,
                                          $auth,
                                          array("features" => SOAP_WAIT_ONE_WAY_CALLS,
                                                'stream_context' => webservice_create_context($c->url),));
                        //when function return null
                        $wsseSoapClient = new webservice_soap_client_wsse(array($client, '_doRequest'), $client->wsdlfile, $client->getOptions());
                        $wsseSoapClient->__setUsernameToken($c->username, $c->password);
                        $client->setSoapClient($wsseSoapClient);
                    }
                    else {
                        //force SOAP synchronous mode
                        $client = new webservice_soap_client($c->url,
                                        $auth,
                                        array("features" => SOAP_WAIT_ONE_WAY_CALLS,
                                              'stream_context' => webservice_create_context($c->url),));
                    }
                    $client->setWsdlCache(false);
                    break;

                case WEBSERVICE_TYPE_XMLRPC:
                    require_once(get_config('docroot') . "webservice/xmlrpc/lib.php");
                    error_log('xmlrpc auth: '.var_export($auth, true));
                    $client = new webservice_xmlrpc_client($c->url, $auth);
                    if ($c->authtype == WEBSERVICE_AUTH_CERT) {
                        $client->setCertificate($c->certificate);
                    }
                    break;

                case WEBSERVICE_TYPE_REST:
                    require_once(get_config('docroot') . "webservice/rest/lib.php");
                    if ($c->authtype == WEBSERVICE_TYPE_OAUTH1) {
                        $client = new webservice_rest_client($c->url, $auth, 'oauth', $c->json);
                         $client->set_2legged($c->consumer, $c->secret);
                    }
                    else {
                        $client = new webservice_rest_client($c->url, $auth, $c->authtype, $c->json);
                    }
                    break;

                default:
                    error_log("Unknown WEBSERVICE_TYPE: ".$c->type);
                    break;
            }
            if ($client) {
                $client->set_connection($c);
                $connections[]= $client;
            }
        }

        syslog(LOG_INFO, "Mahara triggered get_webservice_connections");
        error_log("Mahara triggered get_webservice_connections");

        return $connections;
    }

    /**
     * This function will be run after every upgrade to the plugin.
+1 −1
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@ $config = new stdClass();
// See https://wiki.mahara.org/wiki/Developer_Area/Version_Numbering_Policy
// For upgrades on stable branches, increment the version by one.  On master, use the date.

$config->version = 2016070700;
$config->version = 2016070800;
$config->series = '16.10';
$config->release = '16.10dev';
$config->minupgradefrom = 2012080604;
Loading