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 <[email protected]>
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>&nbsp;";
+       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();
+}

Reply via email to