diff --git a/htdocs/auth/webservice/db/install.xml b/htdocs/auth/webservice/db/install.xml
index a4746d477c4c158c4f0746ecf955239843ae02f5..209adb51b1a8c3550209f37c0bf42fe986471b1a 100644
--- a/htdocs/auth/webservice/db/install.xml
+++ b/htdocs/auth/webservice/db/install.xml
@@ -58,7 +58,7 @@
-
+
@@ -175,7 +175,7 @@
-
+
diff --git a/htdocs/auth/webservice/db/upgrade.php b/htdocs/auth/webservice/db/upgrade.php
index b787a1b984b2dc820cd254722b58e46b84bc28e6..178a4eeb896c42cc2ae42f557a0616b6f4dbafb9 100644
--- a/htdocs/auth/webservice/db/upgrade.php
+++ b/htdocs/auth/webservice/db/upgrade.php
@@ -564,6 +564,20 @@ function xmldb_auth_webservice_upgrade($oldversion=0) {
add_field($table, $field);
}
+ if ($oldversion < 2016101100) {
+ log_debug('Make external_tokens.institution nullable');
+ $table = new XMLDBTable('external_tokens');
+ $field = new XMLDBField('institution');
+ $field->setAttributes(XMLDB_TYPE_CHAR, 255, null, null);
+ change_field_notnull($table, $field, false);
+
+ log_debug('Allow null institution in external_services_logs');
+ $table = new XMLDBTable('external_services_logs');
+ $field = new XMLDBField('institution');
+ $field->setAttributes(XMLDB_TYPE_CHAR, 255);
+ change_field_notnull($table, $field, false);
+ }
+
// sweep for webservice updates everytime
$status = external_reload_webservices();
diff --git a/htdocs/auth/webservice/version.php b/htdocs/auth/webservice/version.php
index 4a4c9bb8512e3bcce217599c6906a25ee5e296be..5413cf04727c67ae973fb1c35c4721e9be872875 100644
--- a/htdocs/auth/webservice/version.php
+++ b/htdocs/auth/webservice/version.php
@@ -12,7 +12,7 @@
defined('INTERNAL') || die();
$config = new stdClass();
-$config->version = 2016090700;
-$config->release = '2.0.0';
+$config->version = 2016101100;
+$config->release = '2.0.1';
$config->requires_config = 0;
$config->requires_parent = 0;
diff --git a/htdocs/module/mobileapi/apps.php b/htdocs/module/mobileapi/apps.php
new file mode 100644
index 0000000000000000000000000000000000000000..8854d3a0c0a9bb6f61a1482889a58cdc44f64472
--- /dev/null
+++ b/htdocs/module/mobileapi/apps.php
@@ -0,0 +1,295 @@
+docroot . 'webservice/lib.php');
+safe_require('module', 'mobileapi');
+define('TITLE', get_string('mytokenspagetitle', 'module.mobileapi'));
+
+// Users shouldn't be able to access this page if webservices are not enabled.
+if (!PluginModuleMobileapi::is_service_ready()) {
+ throw new AccessDeniedException(get_string('featuredisabled', 'auth.webservice'));
+}
+
+// get the list of services that are available for User Access Tokens usage
+// determine if there is a corresponding token for the service
+$dbservices = get_records_sql_array(
+ "SELECT
+ es.id || '_' || et.id || '_' || es.id as dispid,
+ es.id,
+ es.name,
+ es.enabled,
+ es.restrictedusers,
+ et.token,
+ " . db_format_tsfield('et.mtime', 'token_mtime') . ',
+ ' . db_format_tsfield('et.ctime', 'token_ctime') . ',
+ et.institution,
+ et.validuntil as token_validuntil,
+ et.clientname,
+ et.clientenv,
+ esu.validuntil as user_validuntil,
+ esu.iprestriction
+ FROM
+ {external_services} es
+ LEFT JOIN {external_tokens} et
+ ON et.externalserviceid = es.id
+ AND et.userid = ?
+ AND et.tokentype = ?
+ LEFT JOIN {external_services_users} esu
+ ON esu.externalserviceid = es.id
+ AND esu.userid = ?
+ WHERE
+ es.tokenusers = 1
+ AND (es.restrictedusers = 0 OR esu.id IS NOT NULL)
+ AND (et.id IS NOT NULL OR esu.id IS NOT NULL)'
+ ,array(
+ $USER->get('id'),
+ EXTERNAL_TOKEN_USER,
+ $USER->get('id')
+ )
+);
+
+/*
+ * display the access tokens for services
+ */
+if (empty($dbservices)) {
+ $userform = get_string('nopersonaltokens', 'module.mobileapi');
+}
+else {
+ $userform = array(
+ 'name' => 'webservices_user_tokens',
+ 'elementclasses' => false,
+ 'successcallback' => 'webservices_user_tokens_submit',
+ 'renderer' => 'multicolumntable',
+ );
+ $elements = array();
+ $elements['client_info'] = array(
+ 'title' => ' ',
+ 'datatable' => true,
+ 'type' => 'html',
+ 'value' => get_string('clientinfo', 'module.mobileapi'),
+ );
+
+ if (get_config_plugin('module', 'mobileapi', 'manualtokens')) {
+ $elements['token'] = array(
+ 'title' => ' ',
+ 'datatable' => true,
+ 'type' => 'html',
+ 'value' => get_string('token', 'module.mobileapi'),
+ );
+ }
+
+ $elements['created'] = array(
+ 'title' => ' ',
+ 'datatable' => true,
+ 'type' => 'html',
+ 'value' => get_string('tokencreated', 'module.mobileapi'),
+ );
+
+ // Action buttons (no title)
+ $elements['actions'] = array(
+ 'title' => ' ',
+ 'datatable' => true,
+ 'type' => 'html',
+ 'value' => '',
+ );
+ $userform['elements'] = $elements;
+
+ foreach ($dbservices as $service) {
+
+ $client = '
';
+ if ($service->clientname) {
+ $client .= $service->clientname;
+ }
+ else {
+ $client .= get_string('clientnotspecified', 'module.mobileapi');
+ }
+ $client .= '
';
+
+ if ($service->clientenv) {
+ $client .= " ({$service->clientenv})";
+ }
+
+ // information about the client that generated it
+ $userform['elements']['id' . $service->dispid . '_client_info'] = array(
+ 'value' => $client,
+ 'type' => 'html',
+ 'key' => $service->dispid,
+ );
+
+ if (get_config_plugin('module', 'mobileapi', 'manualtokens')) {
+ $userform['elements']['id' . $service->dispid . '_token'] = array(
+ 'value' => $service->token,
+ 'type' => 'html',
+ 'key' => $service->dispid,
+ );
+ }
+
+ $userform['elements']['id' . $service->dispid . '_ctime'] = array(
+ 'value' => format_date($service->token_ctime),
+ 'type' => 'html',
+ 'key' => $service->dispid,
+ );
+
+ // generate button
+ // delete button
+ $userform['elements']['id' . $service->dispid . '_actions'] = array(
+ 'value' => pieform(
+ array(
+ 'name' => 'webservices_user_token_delete_' . $service->dispid,
+ 'renderer' => 'div',
+ 'elementclasses' => false,
+ 'successcallback' => 'webservices_user_token_submit',
+ 'class' => 'form-as-button pull-left',
+ 'jsform' => false,
+ 'elements' => array(
+ 'token' => array('type' => 'hidden', 'value' => $service->token),
+ 'action' => array('type' => 'hidden', 'value' => 'delete'),
+ 'submit' => array(
+ 'type' => 'button',
+ 'usebuttontag' => true,
+ 'class' => 'btn-default btn-sm',
+ 'value' => '' . get_string('delete'),
+ 'elementtitle' => get_string('deletespecific', 'mahara', $service->dispid),
+ ),
+ ),
+ )
+ ),
+ 'type' => 'html',
+ 'key' => $service->dispid,
+ 'class' => 'webserviceconfigcontrols' . (empty($service->token) ? ' only-button only-button-top' : ''),
+ );
+ }
+ $pieform = pieform_instance($userform);
+ $userform = $pieform->build(false);
+}
+
+$page_elements = array(
+ // fieldset for managing service function list
+ 'user_tokens' => array(
+ 'type' => 'fieldset',
+ 'legend' => get_string('mytokenspagedesc', 'module.mobileapi'),
+ 'elements' => array(
+ 'sflist' => array(
+ 'type' => 'html',
+ 'value' => $userform,
+ )
+ ),
+ 'collapsible' => false,
+ )
+);
+
+// TODO: Currently this is hardcoded to only allow self-generation of the
+// maharamobile service.
+$service = get_record('external_services', 'component', 'module/mobileapi', 'shortname', 'maharamobile');
+if (get_config_plugin('module', 'mobileapi', 'manualtokens')) {
+ $page_elements['generate_user_token'] = array(
+ 'type' => 'fieldset',
+ 'legend' => get_string('generateusertoken', 'module.mobileapi'),
+ 'elements' => array(
+ 'generate_user_token_html' => array(
+ 'type' => 'html',
+ 'value' => pieform(
+ array(
+ 'name' => 'webservices_user_token_generate_' . $service->id,
+ 'renderer' => 'div',
+ 'elementclasses' => false,
+ 'successcallback' => 'webservices_user_token_submit',
+ 'class' => 'form-as-button pull-left',
+ 'jsform' => false,
+ 'elements' => array(
+ 'action' => array('type' => 'hidden', 'value' => 'generate'),
+ 'submit' => array(
+ 'type' => 'button',
+ 'usebuttontag' => true,
+ 'class' => 'btn-default btn-sm',
+ 'value' => ' ' . get_string('gen', 'auth.webservice'),
+ 'elementtitle' => get_string('gen', 'auth.webservice')
+ ),
+ ),
+ )
+ )
+ )
+ )
+ );
+}
+
+$form = array(
+ 'renderer' => 'div',
+ 'type' => 'div',
+ 'id' => 'maintable',
+ 'name' => 'maincontainer',
+ 'dieaftersubmit' => false,
+ 'successcallback' => 'webservice_main_submit',
+ 'elements' => $page_elements,
+);
+
+/**
+ * handle the callback for actions on the user token panel
+ * - generate noew token
+ * - delete token
+ *
+ * @param Pieform $form
+ * @param array $values
+ */
+function webservices_user_token_submit(Pieform $form, $values) {
+ global $USER, $SESSION;
+ if ($values['action'] == 'generate') {
+ // TODO: Currently this is hard-coded to only the maharamobile service
+ if (
+ get_config_plugin('module', 'mobileapi', 'manualtokens')
+ && ($service = get_record('external_services', 'component', 'module/mobileapi', 'shortname', 'maharamobile', 'tokenusers', 1))
+ ) {
+ $token = webservice_generate_token(
+ EXTERNAL_TOKEN_USER,
+ $service,
+ $USER->get('id'),
+ null,
+ null,
+ null,
+ get_string('tokenmanuallycreated', 'auth.webservice')
+ );
+ $SESSION->add_ok_msg(get_string('token_generated', 'auth.webservice'));
+ }
+ else {
+ $SESSION->add_error_msg(get_string('noservices', 'auth.webservice'));
+ }
+ }
+ else if ($values['action'] == 'delete') {
+ delete_records('external_tokens', 'userid', $USER->get('id'), 'token', $values['token']);
+ $SESSION->add_ok_msg(get_string('appaccessrevoked', 'module.mobileapi'));
+ }
+ redirect('/module/mobileapi/apps.php');
+}
+
+// render the page
+$pieform = pieform_instance($form);
+$form = $pieform->build(false);
+
+$smarty = smarty();
+setpageicon($smarty, 'icon-globe');
+safe_require('auth', 'webservice');
+
+$smarty->assign('form', $form);
+
+$smarty->display('form.tpl');
diff --git a/htdocs/module/mobileapi/lang/en.utf8/module.mobileapi.php b/htdocs/module/mobileapi/lang/en.utf8/module.mobileapi.php
index 49a0553cdbbb983ca46bc6b153dfbd786cf57e20..411f03dae3d5f121e34563971d4233668d48cee2 100644
--- a/htdocs/module/mobileapi/lang/en.utf8/module.mobileapi.php
+++ b/htdocs/module/mobileapi/lang/en.utf8/module.mobileapi.php
@@ -24,4 +24,17 @@ $string['noticenotenabled'] = 'The Mahara mobile apps API is not currentl
$string['notreadylabel'] = 'Not ready';
$string['readylabel'] = 'Ready';
$string['restprotocolenabled'] = 'REST protocol enabled';
-$string['webserviceproviderenabled'] = 'Incoming web service requests allowed';
\ No newline at end of file
+$string['webserviceproviderenabled'] = 'Incoming web service requests allowed';
+
+// User management of webservice access tokens
+$string['mytokensmenutitle'] = 'Apps';
+$string['mytokenspagetitle'] = 'Applications';
+$string['mytokenspagedesc'] = 'These applications can access your Mahara account.';
+$string['nopersonaltokens'] = 'You have not granted access to any applications.';
+$string['clientinfo'] = 'App';
+$string['token'] = 'Access Token';
+$string['tokencreated'] = 'Created';
+$string['tokenmanuallycreated'] = 'Manually created';
+$string['clientnotspecified'] = '(Unknown)';
+$string['generateusertoken'] = 'Generate an app access token';
+$string['appaccessrevoked'] = 'Access revoked';
\ No newline at end of file
diff --git a/htdocs/module/mobileapi/lib.php b/htdocs/module/mobileapi/lib.php
index d7b1c27f510cf85542af221d73f294bf131f5512..17d6c957c6c5fbe55dc72b875a6b0148e5be1005 100644
--- a/htdocs/module/mobileapi/lib.php
+++ b/htdocs/module/mobileapi/lib.php
@@ -143,5 +143,21 @@ class PluginModuleMobileapi extends PluginModule {
return true;
}
+ public static function right_nav_menu_items() {
+ if (PluginModuleMobileapi::is_service_ready()) {
+ return array(
+ 'settings/webservice' => array(
+ 'path' => 'settings/webservice',
+ 'url' => 'module/mobileapi/apps.php',
+ 'title' => get_string('mytokensmenutitle', 'module.mobileapi'),
+ 'weight' => 50,
+ 'iconclass' => 'flag'
+ ),
+ );
+ }
+ else {
+ return array();
+ }
+ }
}
diff --git a/htdocs/webservice/admin/index.php b/htdocs/webservice/admin/index.php
index c998f99b3c03c5d89f7538c0f018db90e20d4d46..990c0b77d09401b21052ec2492634446fd4d1cb7 100644
--- a/htdocs/webservice/admin/index.php
+++ b/htdocs/webservice/admin/index.php
@@ -476,6 +476,8 @@ function webservice_provider_protocols_submit(Pieform $form, $values) {
$enabled = $values['enabled'] ? 0 : 1;
$proto = $values['protocol'];
set_config('webservice_provider_'.$proto.'_enabled', $enabled);
+ // Show/hide the account settings webservice tokens page
+ clear_menu_cache();
if (param_boolean('ajax')) {
exit;
}