You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@vcl.apache.org by jf...@apache.org on 2025/12/17 20:33:54 UTC
(vcl) 01/01: VCL-1153 - add support for personal access tokens to use with web API
This is an automated email from the ASF dual-hosted git repository.
jfthomps pushed a commit to branch VCL-1153_personal_access_tokens
in repository https://gitbox.apache.org/repos/asf/vcl.git
commit 3992e01dfcb6ab340d1979a59811f98179ce178f
Author: Josh Thompson <jf...@ncsu.edu>
AuthorDate: Wed Dec 17 15:11:51 2025 -0500
VCL-1153 - add support for personal access tokens to use with web API
vcl.sql, update-vcl.sql: added personalaccesstoken table
states.php:
-added these states to $noHTMLwrappers and userPreferences:
- AJaddAccessToken
- AJdeleteAccessToken
- AJtokenList
userpreferences.php:
-modified userpreferences: added new "Manage Tokens" section of user preferences
-added AJtokenList
-added AJaddAccessToken
-added AJdeleteAccessToken
-modified printUserprefJavascript: set tokens section to be hidden on page load; added call to initTokens
utils.php:
-modified checkAccess: modified conditional checking for $_SERVER['HTTP_X_USER'] not being set to also include a check for $_SERVER['HTTP_X_AUTHORIZATION'] not being set; added section to validate Bearer token if $_SERVER['HTTP_X_AUTHORIZATION'] is set
-added createUserAccessToken
-added deleteUserAccessToken
-added getUserAccessTokens
-added validateUserAccessToken
-modified getDojoHTML: added dijit.form.ValidationTextBox and dijit.form.Button to userpreferences mode; added userpreferences and submitgeneralprefs modes to block of code that generates javascript header content
vcl.css:
-added .tokenfieldsetbuffer
-added .tokenrow
-added .tokenname
-added .tokenexp
-added .newtoke
-added .newtokenbox
userpreferences.js: initial commit
---
mysql/update-vcl.sql | 24 ++++++
mysql/vcl.sql | 24 ++++++
web/.ht-inc/states.php | 9 +++
web/.ht-inc/userpreferences.php | 111 ++++++++++++++++++++++++++
web/.ht-inc/utils.php | 171 +++++++++++++++++++++++++++++++++++++++-
web/css/vcl.css | 31 ++++++++
web/js/userpreferences.js | 111 ++++++++++++++++++++++++++
7 files changed, 480 insertions(+), 1 deletion(-)
diff --git a/mysql/update-vcl.sql b/mysql/update-vcl.sql
index da77a52e..e02ed20d 100644
--- a/mysql/update-vcl.sql
+++ b/mysql/update-vcl.sql
@@ -1274,6 +1274,30 @@ CREATE TABLE IF NOT EXISTS `openstackimagerevision` (
-- --------------------------------------------------------
+--
+-- Table structure for table `personalaccesstoken`
+--
+
+CREATE TABLE IF NOT EXISTS `personalaccesstoken` (
+ `id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
+ `userid` mediumint(8) unsigned NOT NULL,
+ `name` varchar(60) NOT NULL,
+ `created` datetime NOT NULL DEFAULT current_timestamp(),
+ `expires` datetime NOT NULL DEFAULT current_timestamp(),
+ `tokenkey` varchar(8) NOT NULL,
+ `tokenhash` varchar(64) NOT NULL,
+ `salt` varchar(8) NOT NULL,
+ `deleted` tinyint(3) unsigned NOT NULL DEFAULT 0,
+ `datedeleted` datetime DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `userid` (`userid`),
+ KEY `tokenkey` (`tokenkey`),
+ KEY `expires` (`expires`),
+ KEY `deleted` (`deleted`)
+) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci;
+
+-- --------------------------------------------------------
+
--
-- Table structure change for table `provisioning`
--
diff --git a/mysql/vcl.sql b/mysql/vcl.sql
index 197939f7..5209619a 100644
--- a/mysql/vcl.sql
+++ b/mysql/vcl.sql
@@ -898,6 +898,30 @@ CREATE TABLE IF NOT EXISTS `OStype` (
-- --------------------------------------------------------
+--
+-- Table structure for table `personalaccesstoken`
+--
+
+CREATE TABLE IF NOT EXISTS `personalaccesstoken` (
+ `id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
+ `userid` mediumint(8) unsigned NOT NULL,
+ `name` varchar(60) NOT NULL,
+ `created` datetime NOT NULL DEFAULT current_timestamp(),
+ `expires` datetime NOT NULL DEFAULT current_timestamp(),
+ `tokenkey` varchar(8) NOT NULL,
+ `tokenhash` varchar(64) NOT NULL,
+ `salt` varchar(8) NOT NULL,
+ `deleted` tinyint(3) unsigned NOT NULL DEFAULT 0,
+ `datedeleted` datetime DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `userid` (`userid`),
+ KEY `tokenkey` (`tokenkey`),
+ KEY `expires` (`expires`),
+ KEY `deleted` (`deleted`)
+) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci
+
+-- --------------------------------------------------------
+
--
-- Table structure for table `platform`
--
diff --git a/web/.ht-inc/states.php b/web/.ht-inc/states.php
index 9ac803e0..c92809c5 100644
--- a/web/.ht-inc/states.php
+++ b/web/.ht-inc/states.php
@@ -228,6 +228,9 @@ $noHTMLwrappers = array('sendRDPfile',
'AJsetTZoffset',
'AJconfirmDeleteGroup',
'AJsubmitDeleteGroup',
+ 'AJaddAccessToken',
+ 'AJdeleteAccessToken',
+ 'AJtokenList',
);
# main
@@ -337,11 +340,17 @@ $actions['mode']['confirmrdpprefs'] = "confirmUserPrefs";
$actions['args']['confirmrdpprefs'] = 1;
$actions['mode']['submituserprefs'] = "submitUserPrefs";
$actions['mode']['submitgeneralprefs'] = "submitGeneralPreferences";
+$actions['mode']['AJaddAccessToken'] = "AJaddAccessToken";
+$actions['mode']['AJdeleteAccessToken'] = "AJdeleteAccessToken";
+$actions['mode']['AJtokenList'] = "AJtokenList";
$actions['pages']['userpreferences'] = "userPreferences";
$actions['pages']['confirmpersonalprefs'] = "userPreferences";
$actions['pages']['confirmrdpprefs'] = "userPreferences";
$actions['pages']['submituserprefs'] = "userPreferences";
$actions['pages']['submitgeneralprefs'] = "userPreferences";
+$actions['pages']['AJaddAccessToken'] = "userPreferences";
+$actions['pages']['AJdeleteAccessToken'] = "userPreferences";
+$actions['pages']['AJtokenList'] = "userPreferences";
# manage groups
$actions['mode']['viewGroups'] = "viewGroups"; # entry
diff --git a/web/.ht-inc/userpreferences.php b/web/.ht-inc/userpreferences.php
index 52daf03d..15ac460c 100644
--- a/web/.ht-inc/userpreferences.php
+++ b/web/.ht-inc/userpreferences.php
@@ -76,6 +76,8 @@ function userpreferences() {
print "</li>\n";
print " <li><a href=#uiprefs onclick=\"javascript:show('uiprefs'); ";
print "return false\">" . i("General Preferences") . "</a></li>\n";
+ print " <li><a href=#tokens onclick=\"javascript:show('tokens'); ";
+ print "return false\">" . i("Manage Tokens") . "</a></li>\n";
print " </ul>\n";
print " </div>\n";
print " </TD>\n";
@@ -354,7 +356,42 @@ function userpreferences() {
print " <INPUT type=submit value=\"" . i("Submit General Preferences") . "\">\n";
print " </FORM>\n";
print " </fieldset>\n";
+ print " </div>\n"; # end uiprefs
+
+ # tokens
+ print " <div id=tokens class=hidden>\n";
+ print " <fieldset>\n";
+ print " <legend>" . i("Manage Personal Access Tokens") . "</legend>\n";
+ print " <div class=\"tokenfieldsetbuffer\">\n";
+ print i("Personal Access Tokens are used to access the VCL XMLRPC API.") . "<br><br>\n";
+ print "<div id=tokenlist class=hidden>Existing tokens:<br></div>\n";
+ $cont = addContinuationsEntry('AJtokenList');
+ print "<input type=hidden id=tokenlistcont value=\"$cont\">\n";
+ $cont = addContinuationsEntry('AJdeleteAccessToken');
+ print "<input type=hidden id=deletetokencont value=\"$cont\">\n";
+ print "<div id=notokens class=hidden>You don't have any existing tokens. Use the form below to create one.<br></div>\n";
+ print "<div id=createdtokendiv class=hidden>\n";
+ print "<br><strong>New Token</strong>\n";
+ print "<div class=\"newtokenbox\">\n";
+ print "<span id=\"newtoken\"></span> ";
+ print "<a id=\"newtokena\">";
+ print "<img src=\"images/copy_icon.png\" style=\"height: 1.1em; width: 1em;\"></a><br>\n";
+ print "</div>\n";
+ print "<span class=\"newtokennote\"><strong>" . i('Note') . ":</strong> " . i('This is the only time the token value will be displayed. Copy it now.'). "</span>\n";
+ print "</div>\n";
+ print "<br>\n";
+ print "<strong>Create New Token</strong>:<br>\n";
+ print "<div class=\"newtoken\">\n";
+ $errmsg = i('Token name can be 2-60 characters and include A-Z, a-z, 0-9, spaces, and these characters - @ # ( ) _ :');
+ print labeledFormItem('tokenname', i('Token Name'), 'text', '^([-A-Za-z0-9@#\(\)_: ]){2,60}$', 1, '', $errmsg);
+ print "</div>\n";
+ print "<div id=tokenerrmsg class=\"hidden msgboxerror\"></div>\n";
+ print dijitButton('addtokenbtn', i('Create Token'), 'createToken();');
print " </div>\n";
+ print " </fieldset>\n";
+ $cont = addContinuationsEntry('AJaddAccessToken', array(), 1800);
+ print " <input type=hidden id=addtokencont value=\"$cont\">\n";
+ print " </div>\n"; # end tokens
print " </TD>\n";
print " </TR>\n";
print "</table>\n";
@@ -677,6 +714,78 @@ function processUserPrefsInput($checks=1) {
return $return;
}
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn AJtokenList()
+///
+/// \brief sends a lit of logged in user's tokens
+///
+////////////////////////////////////////////////////////////////////////////////
+function AJtokenList() {
+ $tokens = getUserAccessTokens();
+ sendJSON(array('tokens' => $tokens));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn AJaddAccessToken()
+///
+/// \brief adds a new token and sends info about it back to user
+///
+////////////////////////////////////////////////////////////////////////////////
+function AJaddAccessToken() {
+ $tokenname = processInputVar("name", ARG_STRING, '');
+ if(! preg_match('/^([-A-Za-z0-9@#\(\)_: ]){2,60}$/', $tokenname)) {
+ $arr = array('status' => 'invalidname',
+ 'msg' => i("Submitted name is invalid"));
+ sendJSON($arr);
+ return;
+ }
+ $tokens = getUserAccessTokens();
+ foreach($tokens as $token) {
+ if($tokenname == $token['name']) {
+ $arr = array('status' => 'duplicatename',
+ 'msg' => i("Token with this name already exists"));
+ sendJSON($arr);
+ return;
+ }
+ }
+ $data = createUserAccessToken($tokenname);
+ $value = $data['value'];
+ $arr = array('status' => 'success',
+ 'id' => $data['tokenid'],
+ 'expires' => $data['expires'],
+ 'name' => $tokenname,
+ 'value' => $value);
+ sendJSON($arr);
+ return;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn AJdeleteAccessToken()
+///
+/// \brief deletes a token
+///
+////////////////////////////////////////////////////////////////////////////////
+function AJdeleteAccessToken() {
+ global $user, $mysqli_link_vcl;
+ $tokenid = processInputVar("tokenid", ARG_NUMERIC);
+ $tokens = getUserAccessTokens();
+ if(! array_key_exists($tokenid, $tokens)) {
+ $arr = array('status' => 'invalidtokenid',
+ 'msg' => i("Submitted token is invalid"));
+ sendJSON($arr);
+ return;
+ }
+ $cnt = deleteUserAccessToken($tokenid);
+ if($cnt == 0)
+ $arr = array('status' => 'failed', 'msg' => i('Error deleting token'), 'id' => $tokenid);
+ else
+ $arr = array('status' => 'success', 'id' => $tokenid);
+ sendJSON($arr);
+}
+
////////////////////////////////////////////////////////////////////////////////
///
/// \fn printUserprefJavascript()
@@ -694,6 +803,7 @@ function show(id) {
obj.className = "hidden";
document.getElementById("rdpfile").className = "hidden";
document.getElementById("uiprefs").className = "hidden";
+ document.getElementById("tokens").className = "hidden";
document.getElementById("status").className = "hidden";
if(id == 'personal' && ! obj)
id = 'rdpfile';
@@ -728,6 +838,7 @@ HTMLdone;
print <<<HTMLdone
document.getElementById("preflinks").className = "shown";
document.getElementById("status").className = "visible";
+initTokens();
</script>
HTMLdone;
diff --git a/web/.ht-inc/utils.php b/web/.ht-inc/utils.php
index 191341b1..8fcfb4dd 100644
--- a/web/.ht-inc/utils.php
+++ b/web/.ht-inc/utils.php
@@ -397,7 +397,26 @@ function checkAccess() {
dbDisconnect();
exit;
}
- if(! isset($_SERVER['HTTP_X_USER'])) {
+ if(! isset($_SERVER['HTTP_X_USER']) && ! isset($_SERVER['HTTP_X_AUTHORIZATION'])) {
+ printXMLRPCerror(3); # access denied
+ dbDisconnect();
+ exit;
+ }
+ if(isset($_SERVER['HTTP_X_AUTHORIZATION'])) {
+ $typetest = substr($_SERVER['HTTP_X_AUTHORIZATION'], 0, 7);
+ if($typetest != 'Bearer ') {
+ printXMLRPCerror(3); # access denied
+ dbDisconnect();
+ exit;
+ }
+ $token = substr($_SERVER['HTTP_X_AUTHORIZATION'], 7, 88);
+ if(! $tokenownerid = validateUserAccessToken($token)) {
+ printXMLRPCerror(3); # access denied
+ dbDisconnect();
+ exit;
+ }
+ if($user = getUserInfo($tokenownerid, 1, 1))
+ return;
printXMLRPCerror(3); # access denied
dbDisconnect();
exit;
@@ -11932,6 +11951,136 @@ function getNodePath($nodeid) {
return $path;
}
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn createUserAccessToken($name)
+///
+/// \param $name - name of token
+///
+/// \return array with keys tokenid, value, expires
+///
+/// \brief generates a new personal access token and adds it to the database
+///
+////////////////////////////////////////////////////////////////////////////////
+function createUserAccessToken($name) {
+ global $user;
+ do {
+ $token = base64_encode(openssl_random_pseudo_bytes(64));
+ $tokenkey = substr(sha1($token), 0, 8);
+ $query = "SELECT id FROM personalaccesstoken WHERE tokenkey = '$tokenkey'";
+ $qh = doQuery($query);
+ } while (mysqli_num_rows($qh));
+ $salt = generateString(8);
+ $tokenhash = hash('sha256', "$salt$token");
+ $query = "INSERT INTO personalaccesstoken "
+ . "(userid, "
+ . "name, "
+ . "created, "
+ . "expires, "
+ . "tokenkey, "
+ . "tokenhash, "
+ . "salt, "
+ . "deleted) "
+ . "VALUES "
+ . "({$user['id']}, "
+ . "'$name', "
+ . "NOW(), "
+ . "DATE_ADD(NOW(), INTERVAL 1 YEAR), "
+ . "'$tokenkey', "
+ . "'$tokenhash', "
+ . "'$salt', "
+ . "0)";
+ $qh = doQuery($query);
+ $tokenid = dbLastInsertID();
+ $data = getUserAccessTokens($tokenid);
+ return array('tokenid' => $tokenid,
+ 'value' => $token,
+ 'expires' => $data[$tokenid]['expires']);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn deleteUserAccessToken($id)
+///
+/// \param $id - id of token from database
+///
+/// \return number of tokens affected
+///
+/// \brief set a token as deleted in the database
+///
+////////////////////////////////////////////////////////////////////////////////
+function deleteUserAccessToken($id) {
+ global $user, $mysqli_link_vcl;
+ $query = "UPDATE personalaccesstoken "
+ . "SET deleted = 1, "
+ . "datedeleted = NOW() "
+ . "WHERE id = $id AND "
+ . "userid = {$user['id']}";
+ doQuery($query);
+ $cnt = mysqli_affected_rows($mysqli_link_vcl);
+ return $cnt;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn getUserAccessTokens($id)
+///
+/// \param $id - (optional) id of token from database
+///
+/// \return array of tokens with these keys for each one: id, name, expires
+///
+/// \brief gets information about tokens for logged in user
+///
+////////////////////////////////////////////////////////////////////////////////
+function getUserAccessTokens($id=0) {
+ global $user;
+ $query = "SELECT id, "
+ . "name, "
+ . "expires "
+ . "FROM personalaccesstoken "
+ . "WHERE userid = '{$user['id']}' AND "
+ . "expires > NOW() AND "
+ . "deleted = 0";
+ if($id != 0)
+ $query .= " AND id = $id";
+ $tokens = array();
+ $qh = doQuery($query);
+ while($row = mysqli_fetch_assoc($qh)) {
+ $tokens[$row['id']] = $row;
+ }
+ return $tokens;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn validateUserAccessToken($token)
+///
+/// \param $token - token submitted by user
+///
+/// \return 0 if invalid, id of user token belongs to if valid
+///
+/// \brief checks if the token matches a token in the database, and if so,
+/// returns the id of the user it belongs to
+///
+////////////////////////////////////////////////////////////////////////////////
+function validateUserAccessToken($token) {
+ $tokenkey = substr(sha1($token), 0, 8);
+ $query = "SELECT userid, "
+ . "tokenhash, "
+ . "salt "
+ . "FROM personalaccesstoken "
+ . "WHERE tokenkey = '$tokenkey' AND "
+ . "expires > NOW() AND "
+ . "deleted = 0";
+ $qh = doQuery($query);
+ while($row = mysqli_fetch_assoc($qh)) {
+ $tokenhash = hash('sha256', "{$row['salt']}$token");
+ if($row['tokenhash'] === $tokenhash)
+ return $row['userid'];
+ }
+ return 0;
+}
+
////////////////////////////////////////////////////////////////////////////////
///
/// \fn sortKeepIndex($a, $b)
@@ -13945,6 +14094,8 @@ function getDojoHTML($refresh) {
case 'submitgeneralprefs':
$filename = 'vclUserPreferences.js';
$dojoRequires = array('dojo.parser',
+ 'dijit.form.ValidationTextBox',
+ 'dijit.form.Button',
'dijit.form.Textarea');
break;
case 'viewstats':
@@ -14352,6 +14503,24 @@ function getDojoHTML($refresh) {
$rt .= "</script>\n";
return $rt;
+ case "userpreferences":
+ case 'submitgeneralprefs':
+ $rt .= "<style type=\"text/css\">\n";
+ $rt .= " @import \"themes/$skin/css/dojo/$skin.css\";\n";
+ $rt .= "</style>\n";
+ $rt .= "<script type=\"text/javascript\" src=\"js/userpreferences.js?v=$v\"></script>\n";
+ $rt .= "<script type=\"text/javascript\" src=\"dojo/dojo/dojo.js\"\n";
+ $rt .= " djConfig=\"parseOnLoad: true, locale: '$jslocale'\">\n";
+ $rt .= "</script>\n";
+ $rt .= $customfile;
+ $rt .= "<script type=\"text/javascript\">\n";
+ $rt .= " dojo.addOnLoad(function() {\n";
+ foreach($dojoRequires as $req)
+ $rt .= " dojo.require(\"$req\");\n";
+ $rt .= " });\n";
+ $rt .= "</script>\n";
+ return $rt;
+
case "viewstats":
$rt .= "<style type=\"text/css\">\n";
$rt .= " @import \"themes/$skin/css/dojo/$skin.css\";\n";
diff --git a/web/css/vcl.css b/web/css/vcl.css
index 1d7287fd..6f2d5b57 100644
--- a/web/css/vcl.css
+++ b/web/css/vcl.css
@@ -172,6 +172,37 @@ body {
font-size: 15px;
}
+.tokenfieldsetbuffer {
+ padding: 6px 12px;
+}
+
+.tokenrow {
+ border: 1px solid black;
+ padding: 4px;
+ margin-top: -1px;
+ display: flex;
+ justify-content: space-between;
+}
+
+.tokenname {
+ font-weight: bold;
+}
+
+.tokenexp {
+ font-size: 0.8em;
+}
+
+.newtoken {
+ margin: 4px 0;
+}
+
+.newtokenbox {
+ padding: 6px;
+ margin: 2px 0;
+ border: 1px solid black;
+ background-color: #f2f2f2;
+}
+
.whenusefieldset {
border-width: 0px;
}
diff --git a/web/js/userpreferences.js b/web/js/userpreferences.js
new file mode 100755
index 00000000..76f1e055
--- /dev/null
+++ b/web/js/userpreferences.js
@@ -0,0 +1,111 @@
+var newtokenid = 0;
+
+function initTokens() {
+ RPCwrapper({continuation: dojo.byId('tokenlistcont').value}, tokenListCB, 1);
+}
+
+function tokenListCB(data, ioArgs) {
+ var keys = Object.keys(data.items.tokens);
+ var len = keys.length
+ for(var i = 0; i < len; i++) {
+ console.log(data.items.tokens[keys[i]]);
+ var item = data.items.tokens[keys[i]];
+ addTokenToList(item.id, item.name, item.expires);
+ }
+ checkTokenListLength();
+}
+
+function createToken() {
+ dojo.addClass('tokenerrmsg', 'hidden');
+ var data = {
+ name: dijit.byId('tokenname').value,
+ continuation: dojo.byId('addtokencont').value
+ };
+ dijit.byId('addtokenbtn').set('disabled', true);
+ RPCwrapper(data, createTokenCB, 1);
+}
+
+function createTokenCB(data, ioArgs) {
+ if(data.items.status == 'invalidname') {
+ dijit.byId('tokenname').isValid();
+ dijit.byId('addtokenbtn').set('disabled', false);
+ return;
+ }
+ if(data.items.status == 'duplicatename') {
+ dojo.removeClass('tokenerrmsg', 'hidden');
+ dojo.byId('tokenerrmsg').innerHTML = data.items.msg;
+ dijit.byId('addtokenbtn').set('disabled', false);
+ return;
+ }
+ newtokenid = data.items.id;
+ addTokenToList(data.items.id, data.items.name, data.items.expires);
+ dojo.removeClass('createdtokendiv', 'hidden');
+ dojo.byId('newtoken').innerHTML = data.items.value;
+ dojo.byId('newtokena').onclick = function() {navigator.clipboard.writeText(data.items.value)};
+ dijit.byId('tokenname').reset();
+ dijit.byId('addtokenbtn').set('disabled', false);
+}
+
+function checkTokenListLength() {
+ if(dojo.byId('tokenlist').childNodes.length <= 2) {
+ dojo.addClass('tokenlist', 'hidden');
+ dojo.removeClass('notokens', 'hidden');
+ }
+ else {
+ dojo.removeClass('tokenlist', 'hidden');
+ dojo.addClass('notokens', 'hidden');
+ }
+}
+
+function addTokenToList(id, name, expires) {
+ var ce = document.createElement.bind(document);
+ var rowspan = ce('span');
+ rowspan.setAttribute('id', id + 'span');
+ rowspan.setAttribute('class', 'tokenrow');
+ var txtspan = ce('span');
+ rowspan.appendChild(txtspan);
+ var namespan = ce('span');
+ namespan.setAttribute('class', 'tokenname');
+ namespan.innerHTML = name;
+ txtspan.appendChild(namespan);
+ txtspan.appendChild(ce('br'));
+ var expspan = ce('span');
+ expspan.setAttribute('class', 'tokenexp');
+ expspan.innerHTML = 'Expires: ' + expires;
+ txtspan.appendChild(expspan);
+ var btn = new dijit.form.Button({
+ id: id + 'delbtn',
+ label: _('Delete'),
+ onClick: function() {
+ deleteToken(id);
+ }
+ }, document.createElement('div'));
+ rowspan.appendChild(btn.domNode);
+ dojo.byId('tokenlist').appendChild(rowspan);
+ checkTokenListLength();
+}
+
+function deleteToken(id) {
+ dijit.byId(id + 'delbtn').set('disabled', true);
+ var data = {
+ tokenid: id,
+ continuation: dojo.byId('deletetokencont').value
+ };
+ RPCwrapper(data, deleteTokenCB, 1);
+}
+
+function deleteTokenCB(data, ioArgs) {
+ if(data.items.status == 'failed') {
+ dijit.byId(data.items.id + 'delbtn').set('disabled', false);
+ alert(data.items.msg);
+ return;
+ }
+ var id = data.items.id;
+ dijit.byId(id + 'delbtn').destroy();
+ dojo.destroy(id + 'span');
+ if(newtokenid == data.items.id) {
+ dojo.byId('createdtokendiv').innerHTML = '';
+ dojo.addClass('createdtokendiv', 'hidden');
+ }
+ checkTokenListLength();
+}