blob: 2361ce3f414fd60421a5146c2f40b0a88dfbfac8 [file] [log] [blame]
// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* Namespace object for the client side code.
*/
var client = new Object();
/**
* Port to contact the policy's callback server.
*/
client.policyCallbackPort = 5199;
client.cryptohome_init_pkcs11 = false;
/**
* Initialize the client.
*/
client.onLoad =
function onLoad() {
client.modalShade = document.getElementById('modal-shade');
client.loadManifest();
};
client.onManifestLoaded =
function onManifestLoaded() {
client.loadSessionId();
}
client.onSessionIdLoaded =
function onSessionIdLoaded() {
client.loadInfo();
}
/**
* Called when client.initPkcs11() completes successfully.
*/
client.onPkcs11Ready =
function onPkcs11Ready() {
client.loadTokens();
client.loadCertificates();
};
/**
* Return the last line of a newline delimited log, skipping blank lines.
*/
client.getLastLogLine =
function getLastLogLine(log) {
var ary = log.split(/\r?\n/);
for (var i = 1; i < ary.length; ++i) {
if (ary[ary.length - i])
return ary[ary.length - i];
}
return '';
}
client.reload =
function reload() {
document.location.href = document.location.pathname + "?reload";
}
/**
* Determine if entd is deprecated based on the Chrome version.
*/
client.isEntdDeprecated =
function isEntdDeprecated() {
var match = navigator.userAgent.match(/Chrome\/([\d]+)/);
if (!match || match.length != 2)
return false;
return match[1] >= 16;
}
/**
* Message used to indicate to the user that entd and entd-style
* extensions (like this reference extension) is deprecated.
*/
client.entdDeprecationMessage =
'This enterprise extension has been deprecated.';
/**
* Get a url to a resource inside the extension.
*
* @param {string} url The path portion of the URL you are interested in.
*/
client.getURL =
function getURL(url) {
if (typeof 'chrome' != undefined && chrome.extension)
return chrome.extension.getURL(url);
return url;
};
/**
* Load the manifest.json file for this extension.
*
*/
client.loadManifest =
function loadManifest() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (this.readyState != 4)
return;
if (this.status == 0 || this.status == 200) {
// Status was 0 in early versions of Chrome.
client.manifest = JSON.parse(this.responseText);
console.log('manifest: ' + this.responseText);
client.onManifestLoaded();
}
};
xhr.open('GET', client.getURL('manifest.json'));
xhr.send();
};
/**
* Load the session-id.json file for this session of entd.
*
*/
client.loadSessionId =
function loadSessionId() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (this.readyState != 4)
return;
if (this.status == 0 || this.status == 200) {
// Status was 0 in earlier versions of Chrome.
if (this.responseText != "") {
client.session_id = JSON.parse(this.responseText);
} else {
// Assume an old Chrome OS is being used with this extension.
console.log('no session-id.json - old chrome os?');
client.session_id = { session_id: '' }
}
console.log('session_id: ' + this.responseText);
client.onSessionIdLoaded();
}
};
xhr.open('GET', client.getURL('session-id.json'));
xhr.send();
};
/**
* Load the basic information from the enterprise daemon.
*
* This invokes cb:info and populates the user interface with the results.
*/
client.loadInfo =
function loadInfo() {
var daemonError = false;
var pkcs11Error = false;
var tpmError = false;
function oncomplete(retval) {
var ready = false;
if (retval instanceof client.CallbackSuccess) {
$("#entd-status").
text('Ready').
attr('status', 'green');
$('#policy-status').
text(retval.data.description + ', ' + retval.data.version);
$('#user-status').
text(retval.data.username);
if ('systemVersion' in retval.data) {
var board = 'Unknown hardware';
if ('lsbRelease' in retval.data &&
'CHROMEOS_RELEASE_BOARD' in retval.data.lsbRelease) {
board = retval.data.lsbRelease.CHROMEOS_RELEASE_BOARD;
}
$('#system-info').text('Chrome OS ' +
retval.data.systemVersion.join('.') + ', ' +
board);
} else {
$('#system-info').text('Chrome OS earlier than 0.14');
}
var pkcs11 = retval.data.pkcs11;
if (pkcs11.state != 'stop:ready') {
$("#pkcs11-status").
text('Waiting...').
attr('status', 'red');
if (!pkcs11Error) {
pkcs11Error = true;
client.showError(
'PKCS#11 services have not started, you may need to clear your ' +
'TPM to recover.', 'Error',
{ details: 'current state: ' + pkcs11.state + '\n' +
pkcs11.log });
}
} else {
$("#pkcs11-status").
text('Ready').
attr('status', 'green');
// Use presence of isTokenReady to determine if
// cryptohome_init_pkcs11 is true.
// TODO(crosbug.com/14277): Remove this conditional and code
// to recognize if TPM has been initialized (only check token).
client.cryptohome_init_pkcs11 = 'isTokenReady' in pkcs11;
if (retval.data.isLibcrosLoaded && !retval.data.tpm.isEnabled) {
if (!tpmError) {
client.showError("Your TPM is not enabled. Please enable " +
"it in the BIOS.");
$('#entd-message').
text('Please reboot and enable your TPM.').
attr('status', 'red');
tpmError = true;
}
} else if (retval.data.isLibcrosLoaded && !retval.data.tpm.isReady) {
if (!tpmError) {
var options = { details: JSON.stringify(retval.data.tpm) };
if (retval.data.tpm.isBeingOwned) {
client.showAlert('Please wait while your TPM is owned. This ' +
'dialog should go away on its own when the ' +
'process completes.', 'Alert', options);
} else if (!retval.data.tpm.isEnabled) {
client.showError('Your TPM is not enabled. Please enable it ' +
'in your BIOS settings.', 'Error', options);
} else {
client.showError('Your TPM is not properly configured. Please ' +
'clear your TPM and try again.', 'Error',
options);
}
$('#entd-message').
text('Waiting for TPM.').
attr('status', 'red');
tpmError = true;
}
} else if (retval.data.isLibcrosLoaded &&
client.cryptohome_init_pkcs11 &&
!pkcs11.isTokenReady) {
if (!tpmError) {
client.showAlert('Please wait while your TPM Token is being ' +
'created. This dialog should go away on its ' +
'own when the process completes.', 'Alert',
options);
$('#entd-message').
text('Waiting for TPM Token.').
attr('status', 'red');
tpmError = true;
}
} else {
ready = true;
}
}
} else {
$("#entd-status").
text('Waiting...').
attr('status', 'red');
$("#pkcs11-status").
text('Unknown');
if (!daemonError) {
daemonError = true;
var msg;
if (document.location.search.match(/reload/)) {
msg = 'Please wait while the Enterprise Daemon restarts.';
} else {
if (client.isEntdDeprecated()) {
msg = client.entdDeprecationMessage;
} else {
msg = 'The Enterprise Daemon has not started. Make sure you ' +
'have approved an enterprise certificate authority, and log ' +
'out or reboot after installing the policy and approving.';
}
}
client.showError(msg, 'Error', { details: JSON.stringify(retval) } );
}
}
if (ready) {
if (retval.data.browserPolicyChanged) {
$('#entd-message').
text('Your browser settings have been changed, you ' +
'must log out before they take effect.').
attr('status', 'red');
} else {
$('#entd-message').text('');
}
client.hideModal();
client.onPkcs11Ready();
} else {
setTimeout(function () {
client.invokePolicyCallback('info', null, oncomplete);
}, 2000);
}
}
client.invokePolicyCallback('info', null, oncomplete);
};
/**
* Load token information from the enterprise daemon and update the UI with
* the results.
*/
client.loadTokens =
function loadTokens() {
function oncomplete(retval) {
if (retval instanceof client.CallbackSuccess) {
if (client.tokens) {
// If we've loaded certificates at least once, then just
// update the existing UI.
client.refreshTokens(retval.data.tokenList);
} else {
// Otherwise we have to create it from scratch.
client.resetTokens(retval.data.tokenList);
}
} else {
throw new Error('Callback failed: ' + retval);
}
}
client.invokePolicyCallback('listTokens', null, oncomplete);
};
/**
* Load the list of certificates for this policy.
*
* If the certificate list has not already been loaded, this create the initial
* UI for each known certificate. If it has been loaded, this will update
* the UI to reflect the current state.
*/
client.loadCertificates =
function loadCertificates() {
function oncomplete(retval) {
if (retval instanceof client.CallbackSuccess) {
if (client.certificates) {
// If we've loaded certificates at least once, then just
// update the existing UI.
client.refreshCertificates(retval.data);
} else {
// Otherwise we have to create it from scratch.
client.resetCertificates(retval.data);
}
} else {
throw new Error('Callback failed: ' + retval);
}
}
client.invokePolicyCallback('listCertificates', null, oncomplete);
};
/**
* Initiate a certificate installation.
*
* This causes the certificate progress dialog to be shown, and manages
* the asynchronous installation of a certificate.
*/
client.installCert =
function installCert(cert, variables) {
var key = cert.key;
// Called for any kind of error from the enterprise daemon.
function onerror(retval) {
if (retval instanceof client.CallbackError) {
client.hideModal(function () {
if (retval && retval.arg_ && retval.arg_.variables &&
retval.arg_.variables.password) {
retval.arg_.variables.password = '** PASSWORD WAS HERE **';
}
client.showError(retval.data, 'Error',
{ details: JSON.stringify(retval) });
});
} else {
// Status changes are known via certificate status. Refreshing.
client.invokePolicyCallback('listCertificates', null, oncomplete_list);
}
};
// Called when we get an updated list of certificates.
function oncomplete_list(retval) {
if (retval instanceof client.CallbackError)
return onerror(retval);
client.refreshCertificates(retval.data);
cert = retval.data[key];
if (!cert)
return alert("No cert: " + key);
if (cert.state == 'stop:key') {
// Key generation is done, proceed to CSR generation.
$('#cert-status').find('.cert-key').attr('status', 'green');
$('#cert-status').find('.cert-csr').attr('status', 'progress');
} else if (cert.state == 'stop:csr') {
// CSR is done, now fetch the cert.
$('#cert-status').find('.cert-key').attr('status', 'green');
$('#cert-status').find('.cert-csr').attr('status', 'green');
$('#cert-status').find('.cert-request').attr('status', 'progress');
client.invokePolicyCallback('getCert', { certificateId: cert.id },
onerror);
} else if (cert.state == 'stop:cert') {
// Cert is fetched, configure the network.
$('#cert-status').find('.cert-request').attr('status', 'green');
$('#cert-status').find('.cert-install').attr('status', 'progress');
client.invokePolicyCallback('installCert', { certificateId: cert.id },
onerror);
} else if (cert.state == 'stop:ready') {
$('#cert-status').find('.cert-install').attr('status', 'green');
client.showSuccess('Your certificate has been installed',
'Certificate Installed');
return;
} else if (cert.state == 'stop:error') {
client.showCertDetails(cert);
return;
}
setTimeout(function () {
client.invokePolicyCallback('listCertificates', null,
oncomplete_list);
}, 1000);
};
client.showCertProgress(cert);
$('#cert-status').find('.cert-key').attr('status', 'progress');
client.invokePolicyCallback(
'initiateCSR', { certificateId: cert.id, variables: variables },
onerror);
}
/**
* Initiate a token initialization.
*
* This causes the token initialization progress dialog to be shown, and manages
* the asynchronous initialization of a token.
* TODO(crosbug.com/14277): Remove token initialization UI.
*/
client.initToken =
function initToken(token, force) {
var slotId = token.slotId;
// Called for any kind of error from the enterprise daemon.
function onerror(retval) {
if (retval instanceof client.CallbackError)
client.showError('There was an error initializing your token. ' +
'If the problem persists, clear your TPM and try ' +
'again.', 'Error',
{ details: JSON.stringify(retval) });
};
// Called when we get an updated list of tokens.
function oncomplete_list(retval) {
if (retval instanceof client.CallbackError)
return onerror(retval);
client.tokens = retval.data.tokenList;
client.refreshTokens(client.tokens);
token = retval.data.tokenList[slotId];
if (!token)
return alert("No token: " + slotId);
if (token.state == 'stop:init') {
// Initialization is done, reset the SO pin.
$('.status.token-init').attr('status', 'green');
$('.status.token-so').attr('status', 'progress');
client.invokePolicyCallback('setSoPin', { slotId: token.slotId },
onerror);
} else if (token.state == 'stop:so-pin') {
// SO pin is set, set the user pin.
$('.status.token-so').attr('status', 'green');
$('.status.token-user').attr('status', 'progress');
client.invokePolicyCallback('setUserPin', { slotId: token.slotId },
onerror);
} else if (token.state == 'stop:ready') {
// User pin is set, all done.
$('.status.token-user').attr('status', 'green');
client.showSuccess('The PKCS#11 token has been successfully ' +
'initialized. You may now install certificates.',
'Token Initialized');
client.onPkcs11Ready();
return;
} else if (token.state == 'stop:error') {
// If the token failed to initialize, it might be left in a broken
// state. This can happen if the hardware times out. We ask entd
// to restart, because it should be able to detect the broken token
// and delete it.
client.invokePolicyCallback("restart");
client.showTokenDetails(token, { okCallback: client.reload });
return;
}
setTimeout(function () {
client.invokePolicyCallback('listTokens', null,
oncomplete_list);
}, 1000);
};
if (!force && token.state == 'stop:ready') {
client.showQuery('Re-initializing this token will remove all keys ' +
'and certificates from the device. Would you like ' +
'to continue?', 'Re-Initialize?',
{ okCallback: function () {
client.initToken(token, true);
}});
return;
}
client.showTokenProgress(token);
$('.status.token-init').attr('status', 'progress');
client.invokePolicyCallback('initToken', { slotId: token.slotId },
onerror);
// This needs to happen on a timeout, or sometimes the listTokens returns
// before the initToken.
setTimeout(function () {
client.invokePolicyCallback('listTokens', null, oncomplete_list);
}, 1000);
}
/**
* Invoked when the 'Install' (sometimes shown as 'Reinstall') button is
* clicked.
*
* @param {string} id The certificate id for the certificate whose button was
* clicked.
*/
client.onCertInstall_ =
function onCertInstall_(cert) {
var tokenOk = false;
for (var i = 0; i < client.tokens.length; ++i) {
if (client.tokens[i].state == "stop:ready")
tokenOk = true;
}
if (!tokenOk) {
client.showAlert("Please initialize your token first");
return;
}
if (cert.userVariables) {
client.showCertQuery(cert);
} else {
client.installCert(cert);
}
};
/**
* Called when a user clicks on the 'Initialize' button on a token.
*/
client.onTokenClick_ =
function onTokenClick_(token) {
client.initToken(token);
}
/**
* Called when a user clicks on the 'Ok' button in an alert dialog.
*/
client.onAlertOk_ =
function onAlertOk() {
client.hideModal(client.modalContext.okCallback);
}
/**
* Called when a user clicks on the 'Cancel' button in an alert dialog.
*/
client.onAlertCancel_ =
function onAlertCancel() {
client.hideModal(client.modalContext.cancelCallback);
}
client.isModalVisible =
function isModalVisible() {
return $('#modal-shade').css('display') == 'inherit';
}
/**
* Common code for showing different kinds of modal alerts.
*
* This animates the dialog on screen. If you want to do something after the
* animation completes, use the callback parameter.
*
* Don't call this directly. Instead use client.showAlert(), showSuccess(),
* showError(), showQuery(), or invent your own.
*
* @param {string} dialogQuery A jquery path to the dialog box to be shown.
* The element identified by this query should be a direct child of
* the #modal-shade element.
* @param {function} callback The function to invoke when the dialog is shown.
*/
client.showModal_ =
function showModal_(dialogQuery, callback) {
var container = $('#modal-shade');
var dialog = $(dialogQuery, container)[0];
if (!dialog)
throw new Error('Unknown modal dialog: ' + dialogQuery);
container.children().each(function(index, element) {
$(element).css('display', (element == dialog ? 'inherit' : 'none'));
});
container.css('display', 'inherit');
setTimeout(function () {
container.css('top', '0%');
var firstInput = $('input', dialog)[0];
if (firstInput)
firstInput.focus();
if (typeof callback == 'function')
callback();
}, 1);
}
/**
* Called when a user clicks the "Details" link in a modal alert.
*/
client.toggleDetails =
function toggleDetails() {
var details = $('textarea.dialog-details')[0];
if ($(details).css('display') == 'none') {
$('a.dialog-details').text('Hide Details');
$('textarea.dialog-details').css('display', 'inherit' );
setTimeout(function () {
$('textarea.dialog-details').css('height', '7em');
}, 1);
} else {
$('a.dialog-details').text('Show Details');
$('textarea.dialog-details').css('height', '0em' );
setTimeout(function () {
$('textarea.dialog-details').css('display', 'none');
}, 250);
}
}
/**
* Hide any modal dialog box.
*
* This animates the dialog box off screen, rather than instantly hiding the
* dialog. If you want to do something after the dialog is hidden (like
* display another dialog box) then you should make use of the callback
* parameter.
*
* @param {function} callback The function to invoke once the dialog box is
* hidden.
*/
client.hideModal =
function hideModal(callback) {
client.modalContext = null;
var container = $('#modal-shade');
container.css('left', '-100%');
// There is a transition associated with the change in 'left', this timeout
// waits until that's done before resetting the dialog.
setTimeout(function () {
var transition = container.css('-webkit-transition');
// Clear the transition so we can move the dialog back to the top of
// the page without waiting for the transition.
container.css('-webkit-transition', '');
// Setting webkit-transition doesn't take effect in chrome until this
// call completes, so we need this 1ms timeout.
setTimeout(function () {
container.css({ display: 'none', top: '-100%', 'left': '0%',
'-webkit-transition': transition });
if (typeof callback == "function") {
// We reset the transition, so we need another 1ms time to make it
// take effect.
setTimeout(callback, 1);
}
}, 1);
}, 250);
};
/**
* Show a modal "Alert" style dialog box.
*
* Graphic is a yellow "!", box has a single "Ok" button.
*
* This function is also used as a base for the other alert-like dialogs,
* showSuccess(), showError(), and showQuery().
*
* @param {string} message The message to be displayed in the main part of
* the dialog box. This will be displayed in a large font, so should be
* kept relatively short.
* @param {string} title Optional. The title of the alert. Defaults to
* 'Alert'.
* @param {Object} options Optional. May contain the following properties:
* - okCallback {function} The function to call if the user clicks "Ok".
* - cancelCallback {function} The function to call if the user clicks
* "Cancel". (Only valid for client.showQuery).
* - details {string} A string containing a more detailed message. This
* will enable the "Details" link in the dialog, which will reveal
* the detail message in a read-only textarea.
*/
client.showAlert =
function showAlert(message, title, options) {
if (client.isModalVisible()) {
client.hideModal(function () { client.showAlert(message, title, options) });
return;
}
client.modalContext = {
okCallback: (options && options.okCallback || null),
cancelCallback: (options && options.cancelCallback || null),
};
options = options || {};
var alertDialog = $('#alert-dialog');
client.showModal_(alertDialog, options.showCallback);
$('.dialog-title', alertDialog).text(title || 'Alert');
$('.dialog-message', alertDialog).html(message || 'NO MESSAGE PROVIDED');
$('.dialog-ok', alertDialog).css('display', 'inherit');
$('.dialog-cancel', alertDialog).css('display', 'none');
$('.dialog-graphic', alertDialog).text('!').attr('status', '');
if (options.details) {
$('a.dialog-details', alertDialog).
css('display', 'inherit').
text('Show Details');
} else {
$('a.dialog-details', alertDialog).css('display', 'none');
}
$('textarea.dialog-details', alertDialog).
css({ display: 'none', height: '0em' }).
text(options.details);
}
/**
* Show a modal "Query" style dialog box.
*
* Graphic is a green "?", box has an "Ok" button and a "Cancel" button.
*/
client.showQuery =
function showQuery(message, title, options) {
if (client.isModalVisible()) {
client.hideModal(function () {
client.showQuery(message, title, options)
});
return;
}
client.showAlert(message, title, options);
$('#alert-dialog .dialog-graphic').
html('?').
attr('status', 'success');
$('#alert-dialog .dialog-cancel').css('display', 'inherit');
}
/**
* Show a modal "Success" style dialog box.
*
* Graphic is a green check mark, box has a single "Ok" button.
*/
client.showSuccess =
function showSuccess(message, title, options) {
if (client.isModalVisible()) {
client.hideModal(function () {
client.showSuccess(message, title, options)
});
return;
}
client.showAlert(message, title, options);
$('#alert-dialog .dialog-graphic').
html('&#x2714;'). // unicode "heavy check"
attr('status', 'success');
}
/**
* Show a modal "Error" style dialog box.
*
* Graphic is a red "X", box has a single "Ok" button.
*/
client.showError =
function showError(message, title, options) {
if (client.isModalVisible()) {
client.hideModal(function () {
client.showError(message, title, options)
});
return;
}
client.showAlert(message, title || 'Error', options);
$('#alert-dialog .dialog-graphic').
html('&#x2718;'). // unicode "heavy ballot x"
attr('status', 'error');
}
/**
* Show the details for a given token in a modal dialog box.
*
* Shows the token details in a modal Error, Success, or Alert box, depending
* on the state of the token.
*/
client.showTokenDetails =
function showTokenDetails(token, options) {
var options = options || {};
options.details = '';
if (token.log)
options.details += token.log + '\n';
options.details += 'current state: ' + token.state +
'\nslotId: ' + token.slotId + '\nflags: ';
for (var key in token.flags) {
if (token.flags[key])
options.details += '\n* ' + key;
}
if (token.state == 'stop:error') {
var msg = client.getLastLogLine(token.log);
if (!msg)
msg = 'There was an error initializing your token.';
client.showError(msg, 'Error', options);
return;
}
if (token.state == 'stop:ready') {
client.showSuccess('Token is initialized', 'Token Information', options);
return;
}
client.showAlert('Token is not initialized: ' + token.state,
'Token Information', options);
}
/**
* Shows the Token Progress dialog.
*/
client.showTokenProgress =
function showTokenProgress(token) {
$('#token-status').find('.status').attr('status', 'pending');
$('#token-desc').text(token.manufacturerID + ', ' + token.model);
$('#token-slot').text(token.slotId);
client.showModal_('#token-status');
}
/**
* Show the details for a given certificate in a modal dialog box.
*
* Shows the certificate details in a modal Error, Success, or Alert box,
* depending on the state of the certificate.
*/
client.showCertDetails =
function showCertDetails(cert) {
var options = { details: '' };
if (cert.log)
options.details += cert.log + '\n';
options.details += 'current state: ' + cert.state;
if (cert.state == 'stop:error') {
var msg = client.getLastLogLine(cert.log);
if (!msg)
msg = 'There was an error installing your certificate';
client.showError(msg, 'Error', options);
return;
}
if (cert.state == 'stop:ready') {
client.showSuccess('Certificate is installed', 'Certificate Information',
options);
return;
}
client.showAlert('Certificate is not installed',
'Certificate Information', options);
}
/**
* Shows the Certificate Progress dialog.
*/
client.showCertProgress =
function showCertProgress(cert) {
$('#cert-status').find('.status').attr('status', 'pending');
$('#cert-status-label').text(cert.label);
client.showModal_('#cert-status');
}
/**
* Show the modal dialog box used to collect user variables.
*
* @param {Object} cert The certificate object that should be associated with
* the modal dialog.
*/
client.showCertQuery =
function showCertQuery(cert) {
client.modalContext = { cert: cert };
$('#user-vars').empty();
var firstInput;
for (var key in cert.userVariables) {
var uservar = cert.userVariables[key];
var html = util.replaceVars(
'<li>%(label) <input name="%(varname)" id="uservar:%(varname)" ' +
'type="%(type)">',
{ label: uservar.label || key,
varname: key,
type: (uservar.type == 'password' ? 'password' : 'text')
});
$('#user-vars').append(html);
}
$('#cert-query-label').text(cert.label);
client.showModal_('#cert-query');
};
/**
* Called when the 'Submit' button is clicked on the cert dialog.
*/
client.onCertSubmit_ =
function onCertSubmit() {
var cert = client.modalContext.cert;
var variables = {};
var valid = true;
for (var key in cert.userVariables) {
var input = document.getElementById('uservar:' + key);
if (!input.value) {
input.setAttribute('class', 'invalid');
valid = false;
} else {
input.setAttribute('class', '');
variables[key] = input.value;
}
}
if (!valid)
return;
client.hideModal(function () { client.installCert(cert, variables) });
};
/**
* Called when the 'Cancel' button is clicked on the cert dialog.
*/
client.onCertCancel_ =
function onModalCancel() {
client.hideModal();
};
/**
* Clear the token list UI and repopulate it.
*
* @param {Array} tokens The list of known tokens.
*/
client.resetTokens =
function resetTokens(tokens) {
client.tokens = tokens;
$('#token-list').empty();
client.refreshTokens(tokens);
};
/**
* Synchronize the existing token list UI with the known list of tokens.
*
* @param {Array} tokens The list of known tokens.
*/
client.refreshTokens =
function refreshTokens(tokens) {
for (var i = 0; i < tokens.length; ++i) {
var rv = $('#token-' + tokens[i].slotId);
if (rv[0]) {
client.refreshToken(rv[0], tokens[i]);
} else {
client.renderToken(tokens[i]);
}
}
};
/**
* Create UI for a given token.
*
* @param {Object} token The target token.
*/
client.renderToken =
function renderToken(token) {
var li = document.createElement('li');
li.className = 'token';
li.setAttribute('id', 'token-' + token.slotId);
$(li).html(
'<table width="100%">' +
'<tr><td><span class="desc"></span> (<span class="label"></span>)</td>' +
'<td rowspan="2" width="1%"><button class="init-button">Initialize' +
'</button></td></tr><tr><td class="status"></td></tr></table>');
$(li).find('button').click(function () {
client.onTokenClick_(client.tokens[token.slotId]);
});
$(li).find('.status').click(function () {
client.showTokenDetails(client.tokens[token.slotId]);
});
client.refreshToken(li, token);
$('#token-list').append(li);
}
/**
* Refresh UI for a given token.
*
* @param {Object} token The target token.
*/
client.refreshToken =
function refreshToken(li, token) {
var status;
if (token.state == 'stop:ready') {
color = 'green';
status = 'Initialized.';
} else if (!token.flags.TOKEN_INITIALIZED) {
color = 'red';
status = 'Not initialized.';
} else if (token.flags.SO_PIN_TO_BE_CHANGED ||
token.flags.USER_PIN_TO_BE_CHANGED) {
color = 'red';
status = 'PINs not initialized.';
} else {
color = 'red';
status = 'Token error';
}
$('.desc', li).text(token.manufacturerID + ', ' + token.model);
$('.label', li).text(token.label || 'Unlabeled');
$('.status', li).attr('status', color);
$('.status', li).text(status);
if (client.cryptohome_init_pkcs11) {
// If automatic initialization is enabled, do not give the user
// the option to initialize.
$('.init-button', li).css('display', 'none');
} else {
$('button', li).text(color == 'red' ? 'Initialize' : 'Reinitialize');
}
}
/**
* Create the UI for a list of certificates.
*
* This will destroy any existing cert UI before proceeding.
*
* @param {Array} certs The list of known certificates.
*/
client.resetCertificates =
function resetCertificates(certs) {
client.certificates = {};
for (var key in certs) {
// The certificates coming from the server have keys that don't match
// their ids (key != cert.id), because the server is being careful not
// to let keys and default object properties collide. We don't care much
// about the collisions here, and having key == cert.id is useful to us.
var cert = certs[key];
client.certificates[cert.id] = cert;
}
var list = document.getElementById('cert-list');
while (list.firstChild)
list.removeChild(list.firstChild);
client.refreshCertificates(certs);
};
/**
* Refresh the UI for a list of certificates.
*
* Creates the UI if it doesn't exist.
*
* @param {Array} certs The list of known certificates.
*/
client.refreshCertificates =
function refreshCertificates(certs) {
for (var key in certs) {
var cert = certs[key];
var li = document.getElementById('cert:' + cert.id);
if (li)
client.refreshCertificate(li, cert);
else
client.renderCertificate(cert);
}
};
/**
* Refresh the UI for a given certificate.
*
* @param {DOMListItem} li The 'li' element containing the certificate UI.
* @param {Object} cert The object representing the current state of the
* certificate.
*/
client.refreshCertificate =
function refreshCertificate(li, cert) {
var ok = false;
var status;
if (cert.state == 'stop:ready') {
ok = true;
status = 'Installed.';
} else if (cert.state == 'stop:error') {
status = 'Error installing certificate.';
} else {
status = 'Not installed.';
}
$('.label', li).text(cert.label);
$('.status', li).text(status).
attr('status', ok ? 'green' : 'red');
$('button', li).text(ok ? 'Reinstall' : 'Install');
return;
};
/**
* Create the UI for a given certificate.
*
* This code intentionally takes the long way around, building each
* element by hand, rather than using innerHTML, in order to avoid some
* potential content injection expoits.
*
* @param {Object} cert The object representing the current state of the
* certificate.
*/
client.renderCertificate =
function renderCertificate(cert) {
// The placeholder text ('<label>', etc) gets replaced later by
// refreshCertificate().
var li = document.createElement('li');
li.setAttribute('class', 'cert');
li.setAttribute('id', 'cert:' + cert.id);
li.innerHTML = '<table width="100%">' +
'<tr><td class="label" width="100%"></td>' +
' <td rowspan="2" valign="center" align="right" width="1%"><button></td>' +
'</tr><tr><td class="status" width="100%"></td></tr>';
$('button', li).click(function() {
client.onCertInstall_(client.certificates[cert.id])
});
$(li).find('.status').click(function () {
client.showCertDetails(client.certificates[cert.id]);
});
$('#cert-list').append(li);
client.refreshCertificate(li, cert);
};
/**
* Invoke a remote policy callback function.
*
* When the callback completes, the oncomplete function will be called with
* a single parameter. The parameter will be an instance of
* client.CallbackSuccess or client.CallbackError, depending on the result
* of the callback. The 'data' property of that object will contain more
* details.
*
* @param {string} name The name of the callback to invoke.
* @param {Object} arg The argument object for the callback.
* @param {function(object)} oncomplete A function to invoke when the callback
* is complete.
*/
client.invokePolicyCallback =
function invokePolicyCallback(name, arg, oncomplete) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (this.readyState != 4)
return;
if (this.status == 200) {
console.log(this.responseText);
var retval = JSON.parse(this.responseText);
var param;
if (typeof retval == 'object') {
if (retval.status == 'success') {
param = new client.CallbackSuccess(name, arg, retval);
} else if (retval.status == 'error') {
param = new client.CallbackError(name, arg, retval);
}
}
if (!param) {
param = new client.CallbackError
(name, arg,
'Unexpected response to callback: ' + name + ': ' +
JSON.stringify(retval));
}
} else {
param = new client.CallbackError(
name, arg,
{ msg: 'Error invoking callback: ' + name + ': http status: ' +
this.status } );
}
param.xhrStatus = xhr.status;
if (typeof oncomplete == "function")
oncomplete(param);
};
xhr.open('POST', 'http://127.0.0.1:' + client.policyCallbackPort +
'/dispatch');
xhr.setRequestHeader('X-Entd-Request',
client.manifest.requestHeaderValue || 'magic');
xhr.setRequestHeader('X-Entd-Session-Id', client.session_id.session_id);
xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
xhr.send(JSON.stringify({'function': name, 'argument': arg}));
};
/**
* client.CallbackSuccess constructor.
*
* An instance of this class holds the return value of a successful policy
* callback. The data property of this object will contain the return
* value of the callback.
*
* @param {string} name The name of the callback that returned this data.
* @param {Object} arg The argument object originally passed to the callback.
* @param {Object} data The data returned by the callback.
*/
client.CallbackSuccess =
function CallbackSuccess(name, arg, data) {
this.init_(name, arg, data)
};
client.CallbackSuccess.prototype.init_ =
function init_(name, arg, rv) {
this.name_ = name;
this.arg_ = arg;
this.toString = function toString() {
return '[' + this.constructor.name + ': ' + JSON.stringify(this) + ']';
};
for (var key in rv)
this[key] = rv[key];
};
/**
* client.CallbackError constructor.
*
* An instance of this class holds the return value of an unsuccessful policy
* callback. The data property of this object will contain the return
* value of the callback.
*
* @param {string} name The name of the callback that returned this data.
* @param {Object} arg The argument object originally passed to the callback.
* @param {Object} data The data returned by the callback.
*/
client.CallbackError =
function CallbackError(name, arg, data) {
this.init_(name, arg, data);
};
client.CallbackError.prototype.init_ = client.CallbackSuccess.prototype.init_;