From: jerome mouneyrac Date: Sun, 13 Dec 2009 10:48:22 +0000 (+0000) Subject: webservice MDL-20803 add web service documentation generator X-Git-Url: http://git.mjollnir.org/gw?a=commitdiff_plain;h=d4c6ef705cec26692a0b72cb3ab983b16a8a9978;p=moodle.git webservice MDL-20803 add web service documentation generator --- diff --git a/group/externallib.php b/group/externallib.php index b3074e9fdc..3dea054d1f 100644 --- a/group/externallib.php +++ b/group/externallib.php @@ -43,7 +43,7 @@ class moodle_group_external extends external_api { 'description' => new external_value(PARAM_RAW, 'group description text'), 'enrolmentkey' => new external_value(PARAM_RAW, 'group enrol secret phrase'), ) - ) + ), 'List of group object. A group has a courseid, a name, a description and an enrolment key.' ) ) ); @@ -103,7 +103,7 @@ class moodle_group_external extends external_api { 'description' => new external_value(PARAM_RAW, 'group description text'), 'enrolmentkey' => new external_value(PARAM_RAW, 'group enrol secret phrase'), ) - ) + ), 'List of group object. A group has an id, a courseid, a name, a description and an enrolment key.' ); } @@ -114,7 +114,8 @@ class moodle_group_external extends external_api { public static function get_groups_parameters() { return new external_function_parameters( array( - 'groupids' => new external_multiple_structure(new external_value(PARAM_INT, 'Group ID')), + 'groupids' => new external_multiple_structure(new external_value(PARAM_INT, 'Group ID') + ,'List of group id. A group id is an integer.'), ) ); } diff --git a/lang/en_utf8/webservice.php b/lang/en_utf8/webservice.php index 5d392f245d..ac90f51e2b 100644 --- a/lang/en_utf8/webservice.php +++ b/lang/en_utf8/webservice.php @@ -6,12 +6,17 @@ $string['addfunctionhelp'] = 'Select the function to add to the service.'; $string['addrequiredcapability'] = 'Assign/Unassign the required capability'; $string['addservice'] = 'Add a new service: $a->name (id: $a->id)'; $string['actwebserviceshhdr'] = 'Active web service protocols'; +$string['apiexplorer'] = 'API explorer'; +$string['apiexplorernotavalaible'] = 'API explorer not available yet.'; +$string['arguments'] = 'Arguments'; $string['configwebserviceplugins'] = 'For security reasons enable only protocols that are used.'; $string['deleteservice'] = 'Delete the service: $a->name (id: $a->id)'; $string['deleteserviceconfirm'] = 'Do you really want to delete external service \"$a\"?'; $string['disabledwarning'] = 'All webs service protocols are disabled, the \Enable web services\" setting can be found in the \"Advanced features\" section.'; $string['editservice'] = 'Edit the service: $a->name (id: $a->id)'; $string['enabled'] = 'Enabled'; +$string['error'] = 'Error: $a'; +$string['errorcodes'] = 'Error Codes'; $string['execute'] = 'Execute'; $string['executewarnign'] = 'WARNING: if you press execute your database will be modified and changes can not be reverted automatically!'; $string['externalservices'] = 'External services'; @@ -22,15 +27,21 @@ $string['function'] = 'Function'; $string['functions'] = 'Functions'; $string['iprestriction'] = 'IP restriction'; $string['manageprotocols'] = 'Manage protocols'; +$string['noerrorcode'] = 'No error code'; $string['norequiredcapability'] = 'No required capability'; +$string['optional'] = 'Optional'; $string['potusers'] = 'Not authorised users'; $string['potusersmatching'] = 'Not authorised users matching'; $string['protocol'] = 'Protocol'; $string['removefunction'] = 'Remove'; $string['removefunctionconfirm'] = 'Do you really want to remove function \"$a->function\" from service \"$a->service\"?'; +$string['requireauthentication'] = 'This method requires authentication with xxx permission.'; +$string['required'] = 'Required'; $string['requiredcapability'] = 'Required capability'; -$string['selectedcapabilitydoesntexit'] = 'The currently set required capability ($a) doesn\'t exist anymore. Please change it and save the changes.'; +$string['response'] = 'Response'; +$string['restcode'] = 'REST code'; $string['restrictedusers'] = 'Authorised users only'; +$string['selectedcapabilitydoesntexit'] = 'The currently set required capability ($a) doesn\'t exist anymore. Please change it and save the changes.'; $string['selectedcapability'] = 'Selected'; $string['servicename'] = 'Service name'; $string['servicesbuiltin'] = 'Built-in services'; @@ -41,3 +52,9 @@ $string['serviceuserssettings'] = 'Change settings for the authorised users'; $string['testclient'] = 'Web service test client'; $string['validuntil'] = 'Valid until'; $string['webservices'] = 'Web services'; +$string['wsdocumentation'] = 'Web service documentation'; +$string['wsdocumentationintro'] = 'Following a listing of web service functions available for the username $a.
In order to create a client we advice you to read the Moodle documentation'; +$string['wsdocumentationlogin'] = 'Enter your web service username and password.'; +$string['wspassword'] = 'Web service password'; +$string['wsusername'] = 'Web service username'; +$string['xmlrpcstructure'] = 'XML-RPC structure'; diff --git a/webservice/wsdoc.php b/webservice/wsdoc.php index b7d09e2a44..4e28bef300 100644 --- a/webservice/wsdoc.php +++ b/webservice/wsdoc.php @@ -19,115 +19,260 @@ // // /////////////////////////////////////////////////////////////////////////// -// TODO: this needs to be rewritten to use the new description format -// the problem here is that the list of functions is different for each use or even token -// I guess this should be moved to server itself and it should require user auth, -// SOAP does already support WSDL when parameters &wsdl=1 used -die('TODO'); -/** - * This file generate a web service documentation in HTML - * This documentation describe how to call a Moodle Web Service - */ +// disable moodle specific debug messages and any errors in output +define('NO_DEBUG_DISPLAY', true); +define('NO_MOODLE_COOKIES', true); + require_once('../config.php'); +require_once('./wsdocrenderer.php'); require_once('lib.php'); -$protocol = optional_param('protocol',"soap",PARAM_ALPHA); -$username = optional_param('username',"",PARAM_ALPHA); -$password = optional_param('password',"",PARAM_ALPHA); -/// TODO Retrieve user (authentication) -$user = ""; -/// PAGE settings -$PAGE->set_course($COURSE); -$PAGE->set_url('webservice/wsdoc.php'); -$PAGE->set_title(get_string('wspagetitle', 'webservice')); -$PAGE->set_heading(get_string('wspagetitle', 'webservice')); -$PAGE->set_generaltype("form"); +/** + * This class generate the web service documentation specific to one + * web service user + * @package webservice + * @copyright 2009 Moodle Pty Ltd (http://moodle.com) + * @author Jerome Mouneyrac + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class webservice_documentation_generator { + + /** @property array all external function description + * they */ + protected $functions; + + /** @property string $username name of local user */ + protected $username = null; -// Display the documentation -echo $OUTPUT->header(); -generate_documentation($protocol); //documentation relatif to the protocol -generate_functionlist($protocol, $user); //documentation relatif to the available function -echo $OUTPUT->footer(); + /** @property string $password password of the local user */ + protected $password = null; + /** + * Contructor + */ + public function __construct() { + $this->functionsdescriptions = array(); + $this->functions = array(); + } -function generate_functionlist($protocol, $user) { + /** + * Run the documentation generation + * @param bool $simple use simple authentication + * @return void + */ + public function run() { - /// retrieve all function that the user can access - /// => - /// retrieve all function that are available into enable services that - /// have (no restriction user or the user is into the restricted user list) - /// and (no required capability or the user has the required capability) + // init all properties from the request data + $this->get_authentication_parameters(); - // do SQL request here + // this sets up $USER TODO: and $SESSION for the environment.php + try { + $this->authenticate_user(); + } catch(moodle_exception $e) { + $errormessage = $e->debuginfo; + $displayloginpage = true; + } - /// load once all externallib.php of the retrieved functions + if (!empty($displayloginpage)){ + $this->display_login_page_html($errormessage); + } else { + // make a descriptions list of all function that user is allowed to excecute + $this->generate_documentation(); - /// foreach retrieved functions display the description + //finally display the documentation + $this->display_documentation_html(); + } - // in order to display the description we need to use an algo similar to the validation - // every time we get a scalar value, we need to convert it into a human readable value as - // PARAM_INT => 'integer' or PARAM_TEXT => 'string' or PARAM_BOOL => 'boolean' ... + die; + } -} +/////////////////////////// +/////// CLASS METHODS ///// +/////////////////////////// -/** - * Generate documentation specific to a protocol - * @param string $protocol - */ -function generate_documentation($protocol) { - switch ($protocol) { - - case "soap": - $documentation = get_string('soapdocumentation','webservice'); - break; - case "xmlrpc": - $documentation = get_string('xmlrpcdocumentation','webservice'); - break; - default: - break; + /** + * This method parses the $_REQUEST superglobal and looks for + * the following information: + * user authentication - username+password + * @return void + */ + protected function get_authentication_parameters() { + if (isset($_REQUEST['wsusername'])) { + $this->username = $_REQUEST['wsusername']; + } + if (isset($_REQUEST['wspassword'])) { + $this->password = $_REQUEST['wspassword']; + } } - echo $documentation; - echo "".get_string('wsuserreminder','webservice').""; -} + /** + * Generate the documentation specific to the auhenticated webservice user + * @return void + */ + protected function generate_documentation() { + global $USER, $DB; + /// first of all get a complete list of services user is allowed to access + $params = array(); + $wscond1 = ''; + $wscond2 = ''; + + // make sure the function is listed in at least one service user is allowed to use + // allow access only if: + // 1/ entry in the external_services_users table if required + // 2/ validuntil not reached + // 3/ has capability if specified in service desc + // 4/ iprestriction + + $sql = "SELECT s.*, NULL AS iprestriction + FROM {external_services} s + JOIN {external_services_functions} sf ON (sf.externalserviceid = s.id AND s.restrictedusers = 0) + WHERE s.enabled = 1 $wscond1 + + UNION + + SELECT s.*, su.iprestriction + FROM {external_services} s + JOIN {external_services_functions} sf ON (sf.externalserviceid = s.id AND s.restrictedusers = 1) + JOIN {external_services_users} su ON (su.externalserviceid = s.id AND su.userid = :userid) + WHERE s.enabled = 1 AND su.validuntil IS NULL OR su.validuntil < :now $wscond2"; + + $params = array_merge($params, array('userid'=>$USER->id, 'now'=>time())); + + $serviceids = array(); + $rs = $DB->get_recordset_sql($sql, $params); + + // make sure user may access at least one service + $remoteaddr = getremoteaddr(); + $allowed = false; + foreach ($rs as $service) { + if (isset($serviceids[$service->id])) { + continue; + } + if ($service->requiredcapability and !has_capability($service->requiredcapability, $this->restricted_context)) { + continue; // cap required, sorry + } + if ($service->iprestriction and !address_in_subnet($remoteaddr, $service->iprestriction)) { + continue; // wrong request source ip, sorry + } + $serviceids[$service->id] = $service->id; + } + $rs->close(); + + // now get the list of all functions + if ($serviceids) { + list($serviceids, $params) = $DB->get_in_or_equal($serviceids); + $sql = "SELECT f.* + FROM {external_functions} f + WHERE f.name IN (SELECT sf.functionname + FROM {external_services_functions} sf + WHERE sf.externalserviceid $serviceids)"; + $functions = $DB->get_records_sql($sql, $params); + } else { + $functions = array(); + } + + foreach ($functions as $function) { + $this->functions[$function->name] = external_function_info($function); + } + } + + /** + * Authenticate user using username+password + * This function sets up $USER global. + * called into the Moodle header + * @return void + */ + protected function authenticate_user() { + global $CFG, $DB, $USER; + + if (!NO_MOODLE_COOKIES) { + throw new coding_exception('Cookies must be disabled!'); + } + + if (!is_enabled_auth('webservice')) { + throw new webservice_access_exception('WS auth not enabled'); + } + + if (!$auth = get_auth_plugin('webservice')) { + throw new webservice_access_exception('WS auth missing'); + } + + if (!$this->username) { + throw new webservice_access_exception('Missing username'); + } + + if (!$this->password) { + throw new webservice_access_exception('Missing password'); + } + + if (!$auth->user_login_webservice($this->username, $this->password)) { + throw new webservice_access_exception('Wrong username or password'); + } + + $USER = $DB->get_record('user', array('username'=>$this->username, 'mnethostid'=>$CFG->mnet_localhost_id, 'deleted'=>0), '*', MUST_EXIST); + + + } + +//////////////////////////////////////////////// +///// DISPLAY METHODS ///// +//////////////////////////////////////////////// + + /** + * Generate and display the documentation + */ + protected function display_documentation_html() { + global $PAGE, $OUTPUT, $SITE; + + $PAGE->set_url('/webservice/wsdoc'); + $PAGE->set_docs_path(''); + $PAGE->set_title($SITE->fullname." ".get_string('wsdocumentation', 'webservice')); + $PAGE->set_heading($SITE->fullname." ".get_string('wsdocumentation', 'webservice')); + $PAGE->set_generaltype('popup'); + + echo $OUTPUT->header(); + $renderer = $PAGE->theme->get_renderer('core_wsdoc',$OUTPUT); + echo $renderer->documentation_html($this->functions, $this->username); + echo $OUTPUT->footer(); + + } + + /** + * Display login page to the web service documentation + * @global $PAGE + * @global $OUTPUT + * @global $SITE + * @global $CFG + * @param string $errormessage error message displayed if wrong login + */ + protected function display_login_page_html($errormessage) { + global $PAGE, $OUTPUT, $SITE, $CFG; + + $PAGE->set_url('/webservice/wsdoc'); + $PAGE->set_docs_path(''); + $PAGE->set_title($SITE->fullname." ".get_string('wsdocumentation', 'webservice')); + $PAGE->set_heading($SITE->fullname." ".get_string('wsdocumentation', 'webservice')); + $PAGE->set_generaltype('popup'); + + echo $OUTPUT->header(); + $renderer = $PAGE->theme->get_renderer('core_wsdoc',$OUTPUT); + echo $renderer->login_page_html($errormessage); + echo $OUTPUT->footer(); -/** - * Convert a Moodle type (PARAM_ALPHA, PARAM_NUMBER,...) as a SOAP type (string, interger,...) - * @param integer $moodleparam - * @return string SOAP type - */ -function converterMoodleParamIntoWsParam($moodleparam) { - switch ($moodleparam) { - case PARAM_NUMBER: - return "integer"; - break; - case PARAM_INT: - return "integer"; - break; - case PARAM_BOOL: - return "boolean"; - break; - case PARAM_ALPHANUM: - return "string"; - break; - case PARAM_ALPHA: - return "string"; - break; - case PARAM_RAW: - return "string"; - break; - case PARAM_ALPHANUMEXT: - return "string"; - break; - case PARAM_NOTAGS: - return "string"; - break; - case PARAM_TEXT: - return "string"; - break; } + } + + +/////////////////////////// +/////// RUN THE SCRIPT //// +/////////////////////////// + +//run the documentation generator +$generator = new webservice_documentation_generator(); +$generator->run(); +die; diff --git a/webservice/wsdocrenderer.php b/webservice/wsdocrenderer.php new file mode 100644 index 0000000000..192637d15b --- /dev/null +++ b/webservice/wsdocrenderer.php @@ -0,0 +1,282 @@ +. // +// // +/////////////////////////////////////////////////////////////////////////// + +/** + * Web service documentation renderer. + * @package webservice + * @copyright 2009 Moodle Pty Ltd (http://moodle.com) + * @author Jerome Mouneyrac + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +class moodle_core_wsdoc_renderer extends moodle_renderer_base { + + /** + * Create documentation for a description object + * @param object $params a part of parameter/return description + * @return string the html to display + */ + public function detailed_html_description($params) { + $paramdesc = ""; + if (!empty($params->desc)) { + $paramdesc = "//".$params->desc."
"; + } + if ($params instanceof external_multiple_structure) { + + return $paramdesc."list of (
". $this->detailed_html_description($params->content).")"; + } else if ($params instanceof external_single_structure) { + //var_dump($params->keys); + $singlestructuredesc = $paramdesc."object {
"; + foreach ($params->keys as $attributname => $attribut) { + $singlestructuredesc .= "".$attributname." ".$this->detailed_html_description($params->keys[$attributname]); + } + $singlestructuredesc .= "}
"; + return $singlestructuredesc; + } else { + switch($params->type) { + case PARAM_BOOL: // 0 or 1 only for now + case PARAM_INT: + $type = 'int'; + break; + case PARAM_FLOAT; + $type = 'double'; + break; + default: + $type = 'string'; + } + return $type." ".$paramdesc; + } + + + } + + /** + * Create description in indented xml format + * It is indented in order to be displayed into
 tag
+     * @param object $returndescription
+     * @param string $indentation composed by space only
+     * @return string the html to diplay
+     */
+    public function description_in_indented_xml_format($returndescription, $indentation = "") {
+        $indentation = $indentation . "    ";
+        $brakeline = <<".$brakeline;
+            $return .= $this->description_in_indented_xml_format($returndescription->content, $indentation);
+            $return .= $indentation."".$brakeline;
+            return $return;
+        } else if ($returndescription instanceof external_single_structure) {
+            $singlestructuredesc = $indentation."".$brakeline;
+            $keyindentation = $indentation."    ";
+            foreach ($returndescription->keys as $attributname => $attribut) {
+                $singlestructuredesc .= $keyindentation."".$brakeline.
+                        $this->description_in_indented_xml_format($returndescription->keys[$attributname], $keyindentation).
+                        $keyindentation."".$brakeline;
+            }
+            $singlestructuredesc .= $indentation."".$brakeline;
+            return $singlestructuredesc;
+        } else {
+            switch($returndescription->type) {
+                case PARAM_BOOL: // 0 or 1 only for now
+                case PARAM_INT:
+                    $type = 'int';
+                    break;
+                case PARAM_FLOAT;
+                    $type = 'double';
+                    break;
+                default:
+                    $type = 'string';
+            }
+            return $indentation."".$type."".$brakeline;
+        }
+    }
+
+    /**
+     * Return the REST response (xml code display in 
 tag)
+     * @param string $functionname
+     * @param object $returndescription
+     * @return string the html to diplay
+     */
+    public function rest_response_html($functionname, $returndescription) {
+
+        $restresponsehtml = "";
+
+        $restresponsehtml .= "
";
+        $restresponsehtml .= "
"; + $restresponsehtml .= 'REST code
'; + $brakeline = <<".$brakeline."".$brakeline; + $content .= $this->description_in_indented_xml_format($returndescription); + $content .="".$brakeline; + $restresponsehtml .= htmlentities($content); + $restresponsehtml .= "
"; + $restresponsehtml .= "
"; + return $restresponsehtml; + } + + /** + * This display all the documentation + * @param array $functions contains all decription objects + * @param string $username + * @return string the html to diplay + */ + public function documentation_html($functions, $username) { + + $documentationhtml = ""; + + $documentationhtml .= "
"; + $documentationhtml .= get_string('wsdocumentationintro', 'webservice', $username); + $documentationhtml .= "


"; + + foreach ($functions as $functionname => $description) { + $documentationhtml .= print_collapsible_region_start('', 'aera_'.$functionname,"".$functionname."",false,true,true); + + $documentationhtml .= "
"; + $documentationhtml .= "
"; + $documentationhtml .= $description->description; + $documentationhtml .= "
"; + $documentationhtml .= "

"; + + $documentationhtml .= "Authentication
"; + $documentationhtml .= ""; + $documentationhtml .= get_string('requireauthentication', 'webservice'/*,$description->type*/); + $documentationhtml .= ""; + $documentationhtml .= "

"; + + $documentationhtml .= "".get_string('arguments', 'webservice')."
"; + foreach ($description->parameters_desc->keys as $paramname => $paramdesc) { + $documentationhtml .= ""; + $required = $paramdesc->required?get_string('required', 'webservice'):get_string('optional', 'webservice'); + $documentationhtml .= "".$paramname . " (" .$required. ")
"; + $documentationhtml .= "        ".$paramdesc->desc."

"; + $documentationhtml .= "
"; + $documentationhtml .= "
";
+                $documentationhtml .= print_collapsible_region_start('', 'aera_'.$functionname."_".$paramname,''.get_string('xmlrpcstructure', 'webservice').'',false,true,true);
+                //echo ''.get_string('xmlrpcstructure', 'webservice').'
'; + $documentationhtml .= $this->detailed_html_description($paramdesc); + $documentationhtml .= print_collapsible_region_end(true); + $documentationhtml .= "
"; + $documentationhtml .= "

"; + $documentationhtml .= "
";
+                $documentationhtml .= "
"; + $documentationhtml .= ''.get_string('restcode', 'webservice').'
'; + $documentationhtml .= htmlentities($this->description_in_indented_xml_format($paramdesc)); + $documentationhtml .= "
"; + $documentationhtml .= "
"; + $documentationhtml .= "
"; + } + $documentationhtml .= "

"; + + $documentationhtml .= "".get_string('response', 'webservice')."
"; + $documentationhtml .= ""; + if (!empty($description->returns_desc->desc)) { + $documentationhtml .= $description->returns_desc->desc."

"; + } + + if (!empty($description->returns_desc)) { + $documentationhtml .= "
"; + $documentationhtml .= "
";
+                $documentationhtml .= print_collapsible_region_start('', 'aera_'.$functionname."_xmlrpc_return",''.get_string('xmlrpcstructure', 'webservice').'',false,true,true);
+                //echo ''.get_string('xmlrpcstructure', 'webservice').'
'; + $documentationhtml .= $this->detailed_html_description($description->returns_desc); + $documentationhtml .= print_collapsible_region_end(true); + $documentationhtml .= "
"; + $documentationhtml .= "

"; + $documentationhtml .=$this->rest_response_html($functionname, $description->returns_desc); + } + $documentationhtml .= "
"; + $documentationhtml .= "

"; + + + + $documentationhtml .= "".get_string('errorcodes', 'webservice')."
"; + $documentationhtml .= ""; + $documentationhtml .= get_string('noerrorcode', 'webservice'); + $documentationhtml .= ""; + $documentationhtml .= "

"; + + + $documentationhtml .= "".get_string('apiexplorer', 'webservice')."
"; + $documentationhtml .= ""; + $documentationhtml .= get_string('apiexplorernotavalaible', 'webservice'); + $documentationhtml .= ""; + $documentationhtml .= "

"; + + $documentationhtml .= print_collapsible_region_end(true); + } + + $documentationhtml .= "
"; + return $documentationhtml; + + } + + /** + * Return the login page html + * @param string $errormessage - the error message to display + * @return string the html to diplay + */ + public function login_page_html($errormessage) { + global $CFG, $OUTPUT; + + $htmlloginpage = ""; + $htmlloginpage .= "
"; + $htmlloginpage .= get_string('wsdocumentationlogin', 'webservice'); + $htmlloginpage .= "


"; + +// echo get_string('error','webservice',$errormessage); +// echo "

"; + + //login form - we cannot use moodle form are we don't have sessionkey + $form = new html_form(); + $form->url = new moodle_url($CFG->wwwroot.'/webservice/wsdoc.php', array()); // Required + $form->button = new html_button(); + $form->button->text = get_string('wsdocumentation','webservice'); // Required + $form->button->disabled = false; + $form->button->title = get_string('wsdocumentation','webservice'); + $form->method = 'post'; + + $field = new html_field(); + $field->name = 'wsusername'; + $field->value = get_string('wsusername', 'webservice'); + $field->style = 'width: 30em;'; + $contents = $OUTPUT->textfield($field); + $contents .= "

"; + $field = new html_field(); + $field->name = 'wspassword'; + $field->value = get_string('wspassword', 'webservice'); + $field->style = 'width: 30em;'; + $contents .= $OUTPUT->textfield($field); + $contents .= "

"; + + $htmlloginpage .= $OUTPUT->form($form, $contents); + + + $htmlloginpage .= "
"; + return $htmlloginpage; + + } +}