| // 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. |
| |
| /** |
| * @fileoverview This file contains implementation for the main.html page. |
| */ |
| |
| /** |
| * Namespace for generic UI manipulation. |
| */ |
| var ui = {}; |
| |
| /** |
| * Namespace for WiFi dialog. |
| */ |
| var wiFiDialog = {}; |
| |
| /** |
| * Namespace for VPN dialog. |
| */ |
| var vpnDialog = {}; |
| |
| /** |
| * Namespace for certificate dialog. |
| */ |
| var certDialog = {}; |
| |
| /** |
| * Namespace for load dialog. |
| */ |
| var loadDialog = {}; |
| |
| /** |
| * Namespace for save dialog. |
| */ |
| var saveDialog = {}; |
| |
| /** |
| * Namespace for ONC manipulation. |
| */ |
| var onc = {}; |
| |
| /** |
| * Namespace for all other functions in main.js. |
| */ |
| var main = { |
| /** |
| * Current content of onc. We expect this to be validated when it |
| * is updated and to thus always be valid. This means at load time |
| * and at "save changes" time in the modal dialogs, it will be |
| * validated. And it also means that at save time the value can be |
| * written directly out without any further validation. |
| */ |
| 'oncCurrent': { |
| 'NetworkConfigurations': [], |
| 'Certificates': [] |
| } |
| }; |
| |
| /** |
| * Dictionary from identifier to dialog objects. |
| */ |
| main.dialogs = { |
| 'cert': certDialog, |
| 'load': loadDialog, |
| 'save': saveDialog, |
| 'vpn': vpnDialog, |
| 'wifi': wiFiDialog, |
| }; |
| |
| /** |
| * Opens the dialog identified by the given ID prefix. |
| * @param {String} idPrefix HTML ID prefix of menu item and dialog |
| * (ie. 'wifi'). |
| */ |
| ui.openDialog = function(idPrefix) { |
| // Close anything else that is already open - though none should be. |
| ui.dismissDialog(); |
| var dialogDoms = $('#' + idPrefix + '-dialog'); |
| $('#overlay')[0].style.display = '-webkit-box'; |
| $('#overlay').keydown(function(event) { |
| if (event.which == 27) |
| ui.dismissDialog(); |
| if (event.which == 13) { |
| // Try to apply. |
| $('#apply-button', '#' + idPrefix + '-dialog').click(); |
| } |
| }); |
| dialogDoms.show(); |
| $('#cancel-button', dialogDoms).click(ui.onDialogCancelPress); |
| $('#apply-header', dialogDoms).html(''); |
| $('#apply-errors', dialogDoms).html(''); |
| $('#apply-warnings', dialogDoms).html(''); |
| main.dialogs[idPrefix].init(); |
| }; |
| |
| /** |
| * Return the currently selected option's i18n tag. |
| * @param {String} selector jquery selector for the option. |
| * @returns {String} i18n tag of current selected or '' if none selected. |
| */ |
| ui.getSelectedI18n = function(selector) { |
| var dom = $(selector)[0]; |
| var selectedIndex = dom.selectedIndex; |
| if (selectedIndex < 0) |
| return ''; |
| return dom.options[selectedIndex].getAttribute('i18n'); |
| }; |
| |
| /** |
| * Sets the current option based on i18n tag value. If no tag exists or |
| * none matches, make no changes. |
| * @param {String} selector jquery selector for the option. |
| * @param {String} i18n Value to set |
| */ |
| ui.setSelectedI18n = function(selector, i18n) { |
| var dom = $(selector)[0]; |
| var toSelect = -1; |
| for (var i = 0; i < dom.options.length; ++i) { |
| if (dom.options[i].getAttribute('i18n') == i18n) { |
| toSelect = i; |
| break; |
| } |
| } |
| if (toSelect >= 0) |
| dom.selectedIndex = toSelect; |
| }; |
| |
| /** |
| * Dismiss/hide the active modal dialog. |
| */ |
| ui.dismissDialog = function() { |
| $('#overlay').hide(); |
| for (var dialog in main.dialogs) |
| $('#' + dialog + '-dialog').hide(); |
| ui.updateSummary(); |
| }; |
| |
| /** |
| * Handle cancel button press on any dialog. |
| */ |
| ui.onDialogCancelPress = function() { |
| ui.dismissDialog(); |
| }; |
| |
| /** |
| * Handle edit button press in the list of networks and certs. |
| * @param {String} dialogId id for dialog to open |
| * @param {Integer} index offset inside arrays of main.oncCurrent |
| */ |
| ui.onEditPress = function(dialogId, index) { |
| var dialog = main.dialogs[dialogId]; |
| ui.openDialog(dialogId); |
| var result; |
| var oncData; |
| if (dialogId == 'cert') { |
| oncData = main.oncCurrent.Certificates[index]; |
| result = onc.validateCertificate(index, main.oncCurrent); |
| } else { |
| oncData = main.oncCurrent.NetworkConfigurations[index]; |
| result = onc.validateNetwork(index, main.oncCurrent); |
| } |
| ui.showMessages(result, '#' + dialogId + '-dialog'); |
| dialog.setToUi(oncData); |
| dialog.setUiVisibility(); |
| }; |
| |
| /** |
| * Handle delete button press in the list of networks and certs. |
| * @param {Object} oncData ONC blob for the entity to delete. |
| */ |
| ui.onDeletePress = function(oncData) { |
| // Currently we always trust the user. If the user wants to delete |
| // a certificate that is referenced by a network, allow it. Upon |
| // updating the summary they should see that the network referencing |
| // it now has an error. |
| // TODO: Use the "Remove" tag when the entity was loaded (vs created |
| // this session). |
| onc.deleteEntity(oncData.GUID, main.oncCurrent); |
| ui.updateSummary(); |
| }; |
| |
| /** |
| * Reset the given file picker. The file picker must be its parent's |
| * last child. |
| * @param {String} domId Query string to find picker. |
| */ |
| ui.resetFilePicker = function(domId) { |
| var pickerClone = $(domId).clone(); |
| var parent = $(domId).parent()[0]; |
| parent.removeChild($(domId)[0]); |
| parent.appendChild(pickerClone[0]); |
| } |
| |
| /** |
| * Called to hide/show fields according to settings. |
| */ |
| wiFiDialog.setUiVisibility = function() { |
| wiFiDialog.setWifiSecurityVisible(); |
| wiFiDialog.setEapVisible(); |
| wiFiDialog.setCredentialsVisible(); |
| }; |
| |
| /** |
| * Based on current Wi-Fi security setting, set visible configuration. |
| */ |
| wiFiDialog.setWifiSecurityVisible = function() { |
| var security = $('#security').val(); |
| if (security == 'WEP-PSK' || security == 'WPA-PSK') { |
| $('#passphrase-div').show(); |
| } else { |
| $('#passphrase-div').hide(); |
| } |
| if (security == 'WPA-EAP') { |
| $('#8021x-div').show(); |
| } else { |
| $('#8021x-div').hide(); |
| } |
| }; |
| |
| /** |
| * Based on current EAP settings, return if password is required for |
| * connection. |
| */ |
| wiFiDialog.wifiRequiresPassword = function() { |
| var security = $('#eap').val(); |
| return (security == 'EAP-TTLS' || security == 'PEAP' || security == 'LEAP'); |
| }; |
| |
| /** |
| * Based on current EAP settings, return if an inner authentication protocol |
| * method setting is required. |
| */ |
| wiFiDialog.wifiRequiresPhase2Method = function() { |
| var security = $('#eap').val(); |
| return security == 'EAP-TTLS' || security == 'PEAP'; |
| }; |
| |
| /** |
| * Based on current EAP settings, return if a server certificate check is |
| * required. |
| */ |
| wiFiDialog.wifiRequiresServerCertificate = function() { |
| var security = $('#eap').val(); |
| return (security == 'EAP-TTLS' || security == 'PEAP' || |
| security == 'EAP-TLS'); |
| }; |
| |
| /** |
| * Based on current EAP settings, return if a client certificate check is |
| * required. |
| */ |
| wiFiDialog.wifiRequiresClientCertficate = function() { |
| return $('#eap').val() == 'EAP-TLS'; |
| }; |
| |
| /** |
| * Based on current EAP setting, set visible configuration. |
| */ |
| wiFiDialog.setEapVisible = function() { |
| if (wiFiDialog.wifiRequiresPhase2Method()) { |
| $('#phase2-div').show(); |
| } else { |
| $('#phase2-div').hide(); |
| } |
| |
| if (wiFiDialog.wifiRequiresServerCertificate()) { |
| $('#eap-server-ca').show(); |
| } else { |
| $('#eap-server-ca').hide(); |
| } |
| |
| if (wiFiDialog.wifiRequiresClientCertficate()) { |
| $('#eap-client-cert').show(); |
| } else { |
| $('#eap-client-cert').hide(); |
| } |
| }; |
| |
| /** |
| * Based on current VPN setting, set visible configuration. |
| */ |
| vpnDialog.setUiVisibility = function() { |
| var setting = $('#vpn-type').val(); |
| if (setting == 'L2TP-IPsec-PSK') { |
| $('#vpn-psk-div').show(); |
| $('#vpn-cert-div').hide(); |
| } else if (setting == 'L2TP-IPsec-Cert' || setting == 'OpenVPN') { |
| $('#vpn-psk-div').hide(); |
| $('#vpn-cert-div').show(); |
| } |
| if ($('#vpn-save-credentials').is(':checked')) |
| $('#vpn-user-cred').show(); |
| else |
| $('#vpn-user-cred').hide(); |
| }; |
| |
| /** |
| * Set visible credentials based on kind of EAP chosen. |
| */ |
| wiFiDialog.setCredentialsVisible = function() { |
| if ($('#save-credentials').is(':checked')) |
| $('#phase2-auth-cred').show(); |
| else |
| $('#phase2-auth-cred').hide(); |
| if (wiFiDialog.wifiRequiresPassword()) |
| $('#div-password', '#phase2-auth-cred').show(); |
| else |
| $('#div-password', '#phase2-auth-cred').hide(); |
| }; |
| |
| /** |
| * Create GUID. |
| * @return {String} Returns a GUID string of format |
| * {XXXXXXXX-XXXX-XXXX-XXXXXXXXXXXXXXXX}. |
| */ |
| main.createGuid = function() { |
| var guidData = new Uint8Array(16); |
| crypto.getRandomValues(guidData); |
| var intervals = [ 4, 2, 2, 8 ]; |
| var guid = '{'; |
| var offset = 0; |
| for (var i = 0; i < intervals.length; ++i) { |
| if (i > 0) |
| guid += '-'; |
| for (var j = 0; j < intervals[i]; ++j) { |
| var hex = guidData[offset].toString(16); |
| if (hex.length == 1) |
| guid += '0'; |
| guid += hex; |
| ++offset; |
| } |
| } |
| guid += '}'; |
| return guid; |
| }; |
| |
| /** |
| * Convert a data string into hex representation. |
| * @param {String} str Data string |
| * @return {String} Hex representation of data string. |
| */ |
| main.toHex = function(str) { |
| var result = ''; |
| for (var i = 0; i < str.length; i++) { |
| var byte = str.charCodeAt(i).toString(16); |
| if (byte.length == 1) |
| byte = '0' + byte; |
| result += byte; |
| } |
| return result; |
| }; |
| |
| /** |
| * Validate the given string is a valid hex number. |
| * @param {String} str Hex string. |
| * @return {Boolean} Indicates if a valid hex string |
| */ |
| main.isAllHex = function(str) { |
| var allHex = true; |
| var validHexChars = ['a', 'b', 'c', 'd', 'e', 'f', |
| '0', '1', '2', '3', '4', '5', |
| '6', '7', '8', '9']; |
| var lowercaseString = str.toLowerCase(); |
| for (var i = 0; i < str.length; i++) { |
| if (validHexChars.indexOf(lowercaseString[i]) == -1) { |
| allHex = false; |
| break; |
| } |
| } |
| return allHex; |
| }; |
| |
| /** |
| * Validate and convert the WiFi configuration to ONC. |
| * @result {Object} Result array containing warnings, errors, and the |
| * ONC NetworkConfiguration object for WiFi. |
| */ |
| wiFiDialog.getFromUi = function() { |
| var network = {}; |
| if (wiFiDialog.oncBase) |
| network = wiFiDialog.oncBase; |
| network.GUID = $('#wifi-guid').val(); |
| network.Name = $('#ssid').val(); |
| network.Type = 'WiFi'; |
| onc.setUpAssocArray(network, 'WiFi'); |
| network.WiFi.AutoConnect = $('#auto-connect').is(':checked'); |
| network.WiFi.HiddenSSID = $('#hidden-ssid').is(':checked') != false; |
| network.WiFi.Security = $('#security').val(); |
| network.WiFi.SSID = $('#ssid').val(); |
| if ($('#wifi-proxy-url').val()) |
| network.ProxyURL = $('#wifi-proxy-url').val(); |
| else |
| delete network.ProxyUrl; |
| switch (network.WiFi.Security) { |
| case 'WEP-PSK': |
| case 'WPA-PSK': |
| network.WiFi.Passphrase = $('#passphrase').val(); |
| delete network.WiFi.EAP; |
| break; |
| case 'WPA-EAP': |
| onc.setUpAssocArray(network.WiFi, 'EAP'); |
| network.WiFi.EAP.Outer = $('#eap').val(); |
| network.WiFi.EAP.UseSystemCAs = $('#wifi-server-ca').val() != 'ignore'; |
| if ($('#save-credentials').is(':checked')) { |
| network.WiFi.EAP.SaveCredentials = true; |
| // Don't bother getting the username/password if save |
| // credentials is off. That would be an inconsistent state. |
| network.WiFi.EAP.Identity = $('#wifi-identity').val(); |
| network.WiFi.EAP.Password = $('#wifi-password').val(); |
| } |
| if (wiFiDialog.wifiRequiresServerCertificate()) { |
| if ($('#wifi-server-ca').val() != 'default') { |
| network.WiFi.EAP.ServerCARef = $('#wifi-server-ca').val(); |
| } |
| } |
| if (wiFiDialog.wifiRequiresClientCertficate()) { |
| network.WiFi.EAP.ClientCertType = 'Pattern'; |
| onc.setUpAssocArray(network.WiFi.EAP, 'ClientCertPattern'); |
| if ($('#wifi-client-ca').val() != 'empty') { |
| network.WiFi.EAP.ClientCertPattern.IssuerCARef = |
| $('#wifi-client-ca').val(); |
| } |
| network.WiFi.EAP.ClientCertPattern.EnrollmentUri = |
| $('#wifi-enrollment-uri').val(); |
| } |
| delete network.WiFi.Passphrase; |
| break; |
| } |
| |
| if (network.WiFi.Security == 'WEP-PSK') { |
| var asciiLengths = [5, 13, 16, 29]; |
| if (asciiLengths.indexOf(network.WiFi.Passphrase.length) != -1) { |
| // Store the WEP passphrase as hex as required by ONC. |
| network.WiFi.Passphrase = '0x' + main.toHex(network.WiFi.Passphrase); |
| } else { |
| var hexNumber = network.WiFi.Passphrase; |
| if (hexNumber.substr(0, 2) == '0x') |
| hexNumber = hexNumber.substr(2); |
| network.WiFi.Passphrase = '0x' + hexNumber.toLowerCase(); |
| } |
| } |
| return network; |
| }; |
| |
| vpnDialog.getUserCredentialsFromUi = function(container) { |
| var saveCredentials = $('#vpn-save-credentials').is(':checked'); |
| container.SaveCredentials = saveCredentials; |
| if (saveCredentials) { |
| container.Username = $('#vpn-username').val(); |
| container.Password = $('#vpn-password').val(); |
| } else { |
| delete container.Username; |
| delete container.Password; |
| } |
| } |
| |
| vpnDialog.getCertsFromUi = function(container) { |
| var serverCa = $('#vpn-server-ca').val(); |
| if (serverCa != 'empty') { |
| container.ServerCARef = serverCa; |
| } |
| container.ClientCertType = 'Pattern'; |
| var clientCa = $('#vpn-client-ca').val(); |
| onc.setUpAssocArray(container, 'ClientCertPattern'); |
| if (clientCa != 'empty') { |
| container.ClientCertPattern.IssuerCARef = clientCa; |
| } |
| if ($('#vpn-enrollment-uri').val()) { |
| container.ClientCertPattern.EnrollmentUri = $('#vpn-enrollment-uri').val(); |
| } else { |
| delete container.ClientCertPattern.EnrollmentUri; |
| } |
| } |
| |
| /** |
| * Validate and convert the VPN configuration to ONC. |
| * @result {Object} ONC NetworkConfiguration object for VPN. |
| */ |
| vpnDialog.getFromUi = function() { |
| var network = {}; |
| if (vpnDialog.oncBase) |
| network = vpnDialog.oncBase; |
| network.GUID = $('#vpn-guid').val(); |
| network.Name = $('#vpn-name').val(); |
| network.Type = 'VPN'; |
| onc.setUpAssocArray(network, 'VPN'); |
| network.VPN.Host = $('#vpn-host').val(); |
| var vpnType = $('#vpn-type').val(); |
| if (vpnType == 'L2TP-IPsec-PSK' || vpnType == 'L2TP-IPsec-Cert') { |
| network.VPN.Type = 'L2TP-IPsec'; |
| onc.setUpAssocArray(network.VPN, 'IPsec'); |
| network.VPN.IPsec.IKEVersion = 1; |
| onc.setUpAssocArray(network.VPN, 'L2TP'); |
| vpnDialog.getUserCredentialsFromUi(network.VPN.L2TP); |
| } else { |
| network.VPN.Type = 'OpenVPN'; |
| onc.setUpAssocArray(network.VPN, 'OpenVPN'); |
| vpnDialog.getUserCredentialsFromUi(network.VPN.OpenVPN); |
| } |
| if ($('#vpn-proxy-url').val()) |
| network.ProxyURL = $('#vpn-proxy-url').val(); |
| else |
| delete network.ProxyURL; |
| if (vpnType == 'L2TP-IPsec-Cert') { |
| container.AuthenticationType = 'Cert'; |
| vpnDialog.getCertsFromUi(network.VPN.IPsec); |
| delete network.VPN.IPsec.PSK; |
| } else if (vpnType == 'L2TP-IPsec-PSK') { |
| network.VPN.IPsec.AuthenticationType = 'PSK'; |
| if ($('#vpn-psk').val()) |
| network.VPN.IPsec.PSK = $('#vpn-psk').val(); |
| else |
| delete network.VPN.IPsec.PSK; |
| delete network.VPN.IPsec.ServerCARef; |
| delete network.VPN.IPsec.ClientCertPattern; |
| } else if ($('#vpn-type').val() == 'OpenVPN') { |
| vpnDialog.getCertsFromUi(network.VPN.OpenVPN); |
| } |
| return network; |
| }; |
| |
| /** |
| * Convert an array of numbers to the equivalent Uint8Array. |
| * @param {Array.<Number>} array Array of numbers to convert. |
| * @return {Uint8Array} Equivalent Uint8Array. |
| */ |
| main.arrayToUint8Array = function(array) { |
| var uint8array = new Uint8Array(array.length); |
| for (var i = 0; i < array.length; ++i) { |
| uint8array[i] = array[i]; |
| } |
| return uint8array; |
| }; |
| |
| /** |
| * Converts the given message list into localized HTML to display. |
| * @param {Array.<Array.<String>>} messageList array of messages to |
| * be converted. |
| */ |
| ui.convertMessagesToHtml = function(messageList) { |
| if (!messageList.length) |
| return ''; |
| messages = []; |
| for (var i = 0; i < messageList.length; i++) { |
| var messageFormat = messageList[i]; |
| var message; |
| if (messageFormat.length > 1) { |
| message = chrome.i18n.getMessage(messageFormat[0], |
| messageFormat.slice(1)); |
| } else { |
| message = chrome.i18n.getMessage(messageFormat[0]); |
| } |
| if (!message) { |
| message = 'NO TRANSLATION FOR: ' + messageFormat[0]; |
| } |
| messages.push(message); |
| } |
| return '<p>' + messages.join('</p><p>') + '<\p>'; |
| }; |
| |
| /** |
| * Update the save button to the current configuration. |
| */ |
| saveDialog.updateSaveLink = function() { |
| var saveLink = $('#save-link'); |
| var saveLinkDiv = $('#save-link-div'); |
| saveLinkDiv.hide(); // In case something fails. |
| var oncString = JSON.stringify(main.oncCurrent, null, 2); |
| var configArray = oncString.split('').map(function(c) { |
| return c.charCodeAt(0); |
| }); |
| var base64 = Base64.encode(main.arrayToUint8Array(configArray)); |
| saveLinkDiv.show(); |
| saveLink.attr('href', 'data:application/octet-stream;base64,' + base64); |
| }; |
| |
| /** |
| * Create an onc blob based on the input onc blob, updating with the |
| * given entity. The entities within the blob will be references to |
| * (not copies of) the original blob and entity. |
| * @param {Object} entity ONC entity to use as a replacement |
| * @param {Object} type Either NetworkConfigurations or Certificates. |
| * @param {Object} oncData ONC to update |
| * @returns {Boolean} Indicates if successful. |
| */ |
| onc.createUpdate = function(entity, type, oncData) { |
| if (oncData == null) |
| oncData = main.oncCurrent; |
| var resultOnc = { |
| 'NetworkConfigurations': [], |
| 'Certificates': [] |
| }; |
| resultOnc.NetworkConfigurations = oncData.NetworkConfigurations.slice(0); |
| resultOnc.Certificates = oncData.Certificates.slice(0); |
| if (!('GUID' in entity)) { |
| // Issue warning? |
| return resultOnc; |
| } |
| function replaceGuid(array) { |
| for (var i = 0; i < array.length; ++i) { |
| if (array[i].GUID == entity.GUID) { |
| array[i] = entity; |
| return; |
| } |
| } |
| array.push(entity); |
| } |
| if (type == 'NetworkConfigurations') |
| replaceGuid(resultOnc.NetworkConfigurations); |
| if (type == 'Certificates') |
| replaceGuid(resultOnc.Certificates); |
| return resultOnc; |
| }; |
| |
| /** |
| * Set up an associative array (JSON object) in the given ONC object. If there is |
| * already one, leave it alone. |
| * @param {Object} outer parent ONC object |
| * @param {String} newAssocArray String name of associative array. |
| */ |
| onc.setUpAssocArray = function(outer, newAssocArray) { |
| if (!(newAssocArray in outer) || !(outer[newAssocArray] instanceof Object)) { |
| outer[newAssocArray] = {}; |
| } |
| } |
| |
| /** |
| * Set up an associative array (JSON object) in the given ONC object. If there is |
| * already one, leave it alone. |
| * @param {Object} outer parent ONC object |
| * @param {String} newAssocArray String name of associative array. |
| */ |
| onc.setUpArray = function(outer, newArray) { |
| if (newArray in outer || !(outer[newArray] instanceof Array)) |
| outer[newArray] = []; |
| } |
| |
| /** |
| * Update array in place so that it has name in it iff value is non-zero. |
| * @param {Array} array input/output array. |
| * @param {Scalar} name name to find in array. |
| * @param {Integer} value value to use to determine if name should appear. |
| */ |
| onc.setBitArray = function(array, name, value) { |
| if (value) { |
| if (array.indexOf(name) < 0) |
| array.push(name); |
| } else { |
| var index = array.indexOf(name); |
| if (index >= 0) |
| array.splice(index, 1); |
| } |
| } |
| |
| /** |
| * Depending on the results, apply the changes or show warnings. |
| * @param {Object} result Result of compiling the UI to ONC. |
| * @param {Object} oncTest ONC resulting from applying change.. |
| * @param {Object} dialog DOM node of dialog. |
| */ |
| ui.showMessagesAndApply = function(result, oncTest, dialog) { |
| ui.showMessages(result, dialog); |
| // Require the user to fix errors. |
| if (result.errors.length) |
| return; |
| main.oncCurrent = oncTest; |
| ui.dismissDialog(); |
| }; |
| |
| /** |
| * Handle apply button press on the WiFi modal dialog. Responsible for |
| * showing errors, blocking save, and dismissing modal dialog. |
| */ |
| wiFiDialog.onApplyPress = function() { |
| var newWiFi = wiFiDialog.getFromUi(); |
| var oncTest = onc.createUpdate(newWiFi, 'NetworkConfigurations'); |
| var result = onc.validateNetwork(onc.findNetwork(newWiFi.GUID, oncTest), |
| oncTest); |
| ui.showMessagesAndApply(result, oncTest, $('#wifi-dialog')[0]); |
| }; |
| |
| /** |
| * Handle save button press on the VPN modal dialog. Responsible for |
| * showing errors, blocking save, and dismissing modal dialog. |
| */ |
| vpnDialog.onApplyPress = function() { |
| var newVpn = vpnDialog.getFromUi(); |
| var oncTest = onc.createUpdate(newVpn, 'NetworkConfigurations'); |
| var result = onc.validateNetwork(onc.findNetwork(newVpn.GUID, oncTest), |
| oncTest); |
| ui.showMessagesAndApply(result, oncTest, $('#vpn-dialog')[0]); |
| }; |
| |
| /** |
| * Called to initialize the save dialog. |
| */ |
| saveDialog.init = function() { |
| saveDialog.updateSaveLink(); |
| $('#cancel-button', '#save-dialog').focus(); |
| }; |
| |
| /** |
| * Called to hide/unhide UI fields. |
| */ |
| saveDialog.setUiVisibility = function() { |
| }; |
| |
| /** |
| * Update a certificate dropdown with the currently loaded certificates. |
| * Currently only Certificate Authority certs are listed. |
| * @param {Object} optionList DOM option list to update. |
| * @param {Boolean} clearFirst Clear the option list before adding. |
| */ |
| ui.updateCertificateDropdown = function(optionList, clearFirst) { |
| if (clearFirst) optionList.options.length = 0; |
| optionList.disabled = false; |
| for (var i = 0; i < main.oncCurrent.Certificates.length; ++i) { |
| var oncCert = main.oncCurrent.Certificates[i]; |
| if (oncCert.Type != 'Authority') |
| continue; |
| var certDescription = ui.formatCertificate(oncCert, 1); |
| optionList.options.add(new Option(certDescription, oncCert.GUID)); |
| } |
| if (optionList.options.length == 0) { |
| optionList.options.add(new Option(chrome.i18n.getMessage |
| ('certificateEmpty'), 'empty')); |
| optionList.disabled = true; |
| } |
| }; |
| |
| /** |
| * Find a cert in the cert list by GUID. |
| * @returns {Integer} index into certificate array or -1 if not found. |
| */ |
| onc.findCert = function(guid, oncData) { |
| for (var i = 0; i < oncData.Certificates.length; ++i) { |
| if (oncData.Certificates[i].GUID == guid) |
| return i; |
| } |
| return -1; |
| }; |
| |
| /** |
| * Find a network in the network list by GUID. |
| * @returns {Integer} index into certificate array or -1 if not found. |
| */ |
| onc.findNetwork = function(guid, oncData) { |
| for (var i = 0; i < oncData.NetworkConfigurations.length; ++i) { |
| if (oncData.NetworkConfigurations[i].GUID == guid) |
| return i; |
| } |
| return -1; |
| }; |
| |
| /** |
| * Deletes an entity by GUID. |
| * @params {String} guid GUID of entity (cert or network) to delete. |
| * @params {Object} oncData ONC blob to update. |
| * @returns {Boolean} Indicates if successful. |
| */ |
| onc.deleteEntity = function(guid, oncData) { |
| var index = onc.findCert(guid, oncData); |
| var array = null; |
| var isCert = false; |
| if (index >= 0) { |
| array = oncData.Certificates; |
| isCert = true; |
| } else { |
| index = onc.findNetwork(guid, oncData); |
| if (index >= 0) |
| array = oncData.NetworkConfigurations; |
| } |
| if (!array) |
| return false; |
| array = array.slice(0, index).concat(array.slice(index + 1)); |
| if (isCert) |
| oncData.Certificates = array; |
| else |
| oncData.NetworkConfigurations = array; |
| return true; |
| }; |
| |
| /** |
| * Takes an X509 PEM-formatted certificate and interprets its contents. |
| * @param {String} x509 base64 X509 certificate |
| * @returns {Object} Result of asn1.interpretCert on certificate. |
| */ |
| main.interpretCertFromX509Pem = function(x509Pem) { |
| var der = Base64.unarmor(x509Pem); |
| var asn1Data = asn1.parseAsn1(der); |
| return asn1.interpretCert(asn1Data); |
| }; |
| |
| /** |
| * Update the certificate summary based on the currently loaded cert. |
| */ |
| certDialog.updateBox = function() { |
| if (!certDialog.certData || !('X509' in certDialog.certData)) { |
| $('#cert-instructions').show(); |
| $('#cert-summary').hide(); |
| return; |
| } |
| $('#cert-instructions').hide(); |
| $('#cert-summary').show(); |
| var cert = main.interpretCertFromX509Pem(certDialog.certData.X509); |
| function updateEntity(table, entity) { |
| var fields = $('.cert-fill', table); |
| for (var i = 0; i < fields.length; ++i) { |
| if (fields[i].id in entity) |
| fields[i].innerText = entity[fields[i].id]; |
| else |
| fields[i].innerText = ''; |
| } |
| } |
| updateEntity($('#subject', '#cert-summary'), cert.subject); |
| updateEntity($('#issuer', '#cert-summary'), cert.issuer); |
| }; |
| |
| /** |
| * Handle a drag and drop file list. We validate the certificate |
| * file, show a summary of the certificate, and store the |
| * contents for later in certData. |
| * @param {Array.<Object>} files drag and drop file list. |
| */ |
| certDialog.handleCertFileList = function(files) { |
| certDialog.loadedCert = null; |
| for (var i = 0; i < files.length; ++i) { |
| var file = files[i]; |
| var reader = new FileReader(); |
| reader.onload = function(theFile) { |
| var derUint8; |
| if (file.name.match(/\.pem$/) || file.name.match(/\.crt$/)) { |
| var der = Base64.unarmor(this.result); |
| derUint8 = main.arrayToUint8Array(der); |
| } else if (file.name.match(/\.der/)) { |
| // TODO: This is currently broken |
| var der = this.result; |
| derUint8 = main.arrayToUint8Array(der); |
| } |
| certDialog.certData = {}; |
| if (derUint8) |
| certDialog.certData.X509 = Base64.encode(derUint8); |
| // Create a new ONC object. |
| var newCert = certDialog.getFromUi(); |
| var oncTest = onc.createUpdate(newCert, 'Certificates'); |
| var results = onc.validateCertificate( |
| onc.findCert(newCert.GUID, oncTest), oncTest); |
| ui.showMessages(results, '#cert-dialog'); |
| certDialog.updateBox(); |
| }; |
| reader.readAsBinaryString(file); |
| } |
| return false; |
| }; |
| |
| /** |
| * Set up the UI with the given certificate ONC. Error checking is |
| * not performed. |
| * @param {Object} oncCert Certificate |
| */ |
| certDialog.setToUi = function(oncCert) { |
| certDialog.oncBase = oncCert; |
| certDialog.certData.X509 = oncCert.X509; |
| if ('Trust' in oncCert) { |
| for (var i = 0; i < oncCert.Trust.length; ++i) { |
| if (oncCert.Trust[i] == 'Web') |
| $('#web-trust')[0].checked = true; |
| } |
| } |
| $('#cert-type').val(oncCert.Type); |
| certDialog.updateBox(); |
| }; |
| |
| /** |
| * Called to hide/unhide UI fields. |
| */ |
| certDialog.setUiVisibility = function() { |
| } |
| |
| /** |
| * Validate and convert the certificate configuration to ONC. |
| * @result {Object} ONC Certificate object. |
| */ |
| certDialog.getFromUi = function() { |
| // TODO: Handle or deprecate PKCS12 loading (in which case |
| // we load PKCS8). |
| var oncCert = {}; |
| if ('oncBase' in certDialog) |
| oncCert = certDialog.oncBase; |
| onc.setUpArray(oncCert, 'Trust'); |
| onc.setBitArray(oncCert.Trust, 'Web', $('#web-trust').is(':checked')); |
| oncCert.GUID = $('#cert-guid').val(); |
| oncCert.Type = $('#cert-type').val(); |
| if ('X509' in certDialog.certData) |
| oncCert.X509 = certDialog.certData.X509; |
| return oncCert; |
| }; |
| |
| /** |
| * Configure the given DOM id as a drag and drop target for certificates. |
| * @param {String} id DOM id. |
| */ |
| certDialog.configureDragDropTarget = function(id) { |
| function cancel(event) { |
| if (event.preventDefault) |
| event.preventDefault(); |
| return false; |
| } |
| var drop = $('#' + id)[0]; |
| drop.addEventListener('dragover', cancel, false); |
| drop.addEventListener('dragenter', cancel, false); |
| drop.addEventListener('drop', function(event) { |
| certDialog.handleCertFileList(event.dataTransfer.files); |
| }, false); |
| }; |
| |
| /** |
| * Configure the certificate file picker. |
| */ |
| certDialog.configureCertFilePicker = function() { |
| $('#cert-files').change(function(event) { |
| certDialog.handleCertFileList(event.target.files); |
| }); |
| }; |
| |
| /** |
| * Handles save of certificate. |
| */ |
| |
| certDialog.onApplyPress = function() { |
| var newCert = certDialog.getFromUi(); |
| var oncTest = onc.createUpdate(newCert, 'Certificates'); |
| var results = onc.validateCertificate( |
| onc.findCert(newCert.GUID, oncTest), oncTest); |
| ui.showMessagesAndApply(results, oncTest, $('#cert-dialog')[0]); |
| }; |
| |
| |
| /** |
| * Shows the error or warning mark as appropriate for the givn entity. |
| * @param {Object} result Validation result. |
| * @param {DOM} entityDom DOM of entity in the list. |
| */ |
| ui.setEntityStatus = function(result, entityDom) { |
| var whichMark = null; |
| if (result.errors.length) |
| whichMark = '#error-mark'; |
| else if (result.warnings.length) |
| whichMark = '#warning-mark'; |
| if (whichMark) { |
| $(whichMark, entityDom).show(); |
| // if you click on the warning/error, edit the entry. |
| $(whichMark, entityDom).click(function(event) { |
| $('#edit', this.parentNode).click(); |
| }); |
| } |
| }; |
| |
| /** |
| * Format a network configuration for display. |
| * @params {Object} oncNetwork ONC network configuration |
| */ |
| ui.formatNetwork = function(oncNetwork) { |
| return oncNetwork.Name + '<br>' + |
| oncNetwork.Type; |
| }; |
| |
| /** |
| * Handle edit press on a network in the summary list. |
| */ |
| ui.onNetworkEditPress = function() { |
| var parent = $(this).parent(); |
| var index = parent.data('index') |
| if (parent.data('onc').Type == 'WiFi') |
| ui.onEditPress('wifi', index); |
| else |
| ui.onEditPress('vpn', index); |
| }; |
| |
| /** |
| * Format a network configuration for display. |
| * @params {Object} oncNetwork ONC network configuration |
| * @params {Integer} maxLines Maximum lines available for description. |
| Defaults to no maximum.w |
| */ |
| ui.formatCertificate = function(oncCert, maxLines) { |
| var result = '' |
| var cert = main.interpretCertFromX509Pem(oncCert.X509); |
| if ('commonName' in cert.subject) { |
| result += cert.subject.commonName; |
| } |
| if (maxLines == undefined || maxLines > 1) { |
| result += '<br>\n'; |
| } |
| result += ' ['; |
| if ('commonName' in cert.issuer) { |
| result += cert.issuer.commonName; |
| } |
| result += ']'; |
| return result; |
| }; |
| |
| /** |
| * Handle edit press on a network in the summary list. |
| */ |
| ui.onCertificateEditPress = function() { |
| var parent = $(this).parent(); |
| var index = parent.data('index') |
| ui.onEditPress('cert', index); |
| }; |
| |
| /** |
| * Show the summary of the current ONC settings for a given kind of entity. |
| * @param {Array.<Object>} list List of entities (networks or certificates) |
| * @param {String} id query id of the list's top level DOM. |
| * @param {Function} validator Called to validate this entity. |
| * @param {Function} formatter Called to format entity to HTML. |
| * @param {Function} editCallback Called to handle edit press of this entity. |
| */ |
| ui.showSummaryList = function(list, id, validator, formatter, editCallback) { |
| for (var i = 0; i < list.length; ++i) { |
| var oncEntity = list[i]; |
| var result = validator(i, main.oncCurrent); |
| var newDom = $('#template', id).clone(); |
| newDom[0].id = ''; |
| $(id).append(newDom); |
| $('#left', newDom).html(formatter(oncEntity)); |
| ui.setEntityStatus(result, newDom); |
| |
| var editButton = $('#edit', newDom); |
| editButton.parent().data('onc', oncEntity); |
| editButton.parent().data('index', i); |
| editButton.click(editCallback); |
| // For opaque entities which we do not know how to edit, simply |
| // remove the edit button. User can still preserve such entities |
| // and delete them, but not modify them. |
| if (result.hasOpaqueEntity) |
| editButton[0].parentNode.removeChild(editButton[0]); |
| var deleteButton = $('#delete', newDom); |
| deleteButton.click(function() { |
| ui.onDeletePress($(this).parent().data('onc')); |
| }); |
| newDom.hover( |
| function() { |
| $('.action', this).show(); |
| }, |
| function() { |
| $('.action', this).hide(); |
| }); |
| newDom[0].style.display = '-webkit-box'; |
| } |
| }; |
| |
| /** |
| * Update the summary (right) pane. |
| */ |
| ui.updateSummary = function() { |
| var rightPane = $('#right-pane')[0]; |
| |
| // Remove all entities other than the template. |
| var entities = $('.entity'); |
| for (var i = 0; i < entities.length; ++i) { |
| if (entities[i].style.display != '') |
| entities[i].parentNode.removeChild(entities[i]); |
| } |
| |
| ui.showSummaryList(main.oncCurrent.NetworkConfigurations, |
| '#network-configurations', |
| onc.validateNetwork, |
| ui.formatNetwork, |
| ui.onNetworkEditPress); |
| |
| ui.showSummaryList(main.oncCurrent.Certificates, |
| '#certificates', |
| onc.validateCertificate, |
| ui.formatCertificate, |
| ui.onCertificateEditPress); |
| }; |
| |
| /** |
| * Called to initialize the WiFi dialog. |
| */ |
| wiFiDialog.init = function() { |
| $('#ssid').val(''); |
| $('#hidden-ssid')[0].checked = false; |
| $('#auto-connect')[0].checked = false; |
| ui.setSelectedI18n('#security', 'securityNone'); |
| $('#passphrase').val(''); |
| $('#wifi-proxy-url').val(''); |
| ui.setSelectedI18n('#eap', 'acronymPeap'); |
| ui.setSelectedI18n('#phase2', 'automatic'); |
| $('#save-credentials')[0].checked = false; |
| $('#wifi-identity').val(''); |
| $('#wifi-password').val(''); |
| $('#wifi-server-ca').val(''); |
| $('#wifi-client-ca').val(''); |
| $('#wifi-guid').val(main.createGuid()); |
| var serverCaDom = $('#wifi-server-ca')[0]; |
| serverCaDom.options.length = 0; |
| serverCaDom.options.add(new Option(chrome.i18n.getMessage |
| ('useAnyDefaultCA'), 'default')); |
| ui.updateCertificateDropdown(serverCaDom, false); |
| serverCaDom.options.add(new Option(chrome.i18n.getMessage |
| ('doNotCheckCA'), 'ignore')); |
| ui.updateCertificateDropdown($('#wifi-client-ca')[0], true); |
| $('#apply-button', '#wifi-dialog').click(wiFiDialog.onApplyPress); |
| $('#security').change(function() { |
| wiFiDialog.setUiVisibility(); |
| }); |
| $('#eap').change(wiFiDialog.setUiVisibility); |
| $('#save-credentials').click(function() { |
| wiFiDialog.setUiVisibility(); |
| }); |
| wiFiDialog.setUiVisibility(); |
| $('#ssid').focus(); |
| }; |
| |
| /** |
| * Called to initialize the VPN dialog. |
| */ |
| vpnDialog.init = function() { |
| $('#vpn-name').val(''); |
| $('#vpn-host').val(''); |
| $('#vpn-psk').val(''); |
| $('#vpn-save-credentials')[0].checked = false; |
| $('#vpn-username').val(''); |
| $('#vpn-password').val(''); |
| $('#vpn-enrollment-uri').val(''); |
| $('#vpn-guid').val(main.createGuid()); |
| ui.updateCertificateDropdown($('#vpn-server-ca')[0], true); |
| ui.updateCertificateDropdown($('#vpn-client-ca')[0], true); |
| $('#apply-button', '#vpn-dialog').click(vpnDialog.onApplyPress); |
| $('#vpn-type').change(vpnDialog.setUiVisibility); |
| $('#vpn-save-credentials').change(vpnDialog.setUiVisibility); |
| vpnDialog.setUiVisibility(); |
| $('#vpn-name').focus(); |
| }; |
| |
| loadDialog.init = function() { |
| $('#apply-button', '#load-dialog').click(loadDialog.onApplyPress); |
| // Reset the file picker. |
| ui.resetFilePicker('#load-file'); |
| loadDialog.configureLoadFilePicker(); |
| $('#load-file').focus(); |
| }; |
| |
| /** |
| * Called to initialize the cert dialog. |
| */ |
| certDialog.init = function() { |
| certDialog.certData = {}; |
| $('#cert-guid').val(main.createGuid()); |
| $('#web-trust')[0].checked = false; |
| ui.setSelectedI18n('#cert-type', 'certificateTypeAuthority'); |
| $('#apply-button', '#cert-dialog').click(certDialog.onApplyPress); |
| ui.resetFilePicker('#cert-files'); |
| certDialog.configureDragDropTarget('cert-summary'); |
| certDialog.configureCertFilePicker(); |
| $('#cert-files').focus(); |
| certDialog.updateBox(); |
| }; |
| |
| loadDialog.setUiVisibility = function() { |
| return; |
| }; |
| |
| /** |
| * Set up the UI with given WiFi ONC configuration. |
| * @param {Object} netconfig WiFi ONC object |
| **/ |
| wiFiDialog.setToUi = function(netConfig) { |
| // Preserve any existing settings. |
| wiFiDialog.oncBase = netConfig; |
| $('#wifi-guid').val(netConfig.GUID); |
| wifiConfig = netConfig.WiFi; |
| $('#ssid').val(wifiConfig.SSID); |
| if ('AutoConnect' in wifiConfig) |
| $('#auto-connect')[0].checked = wifiConfig.AutoConnect != false; |
| if ('HiddenSSID' in wifiConfig) |
| $('#hidden-ssid')[0].checked = wifiConfig.HiddenSSID != false; |
| if ('Passphrase' in wifiConfig) { |
| // Strip off any '0x' from hex passphrases. We'll correctly |
| // interpret it as a hex passphrase when we save and add the |
| // '0x' back on. |
| if (wifiConfig.Security == 'WEP-PSK' && |
| wifiConfig.Passphrase.substr(0,2) == '0x') |
| wifiConfig.Passphrase = wifiConfig.Passphrase.substr(2); |
| $('#passphrase').val(wifiConfig.Passphrase); |
| } |
| $('#security').val(wifiConfig.Security); |
| if ('EAP' in wifiConfig && wifiConfig.Security == 'WPA-EAP') { |
| var eapConfig = netConfig.WiFi.EAP; |
| switch (eapConfig.Outer) { |
| case 'PEAP': |
| case 'EAP-TTLS': |
| case 'EAP-TLS': |
| case 'LEAP': |
| $('#eap').val(eapConfig.Outer); |
| break; |
| } |
| if ('Identity' in eapConfig) { |
| $('#wifi-identity').val(eapConfig.Identity); |
| } |
| if ('Password' in eapConfig) { |
| $('#wifi-password').val(eapConfig.Password); |
| } |
| if ('SaveCredentials' in eapConfig && eapConfig.SaveCredentials) |
| $('#save-credentials')[0].checked = true; |
| if (onc.findCert(eapConfig.ServerCARef, main.oncCurrent) >= 0) |
| $('#wifi-server-ca').val(eapConfig.ServerCARef); |
| else if (!('UseSystemCAs' in eapConfig) || eapConfig.UseSystemCAs) { |
| $('#wifi-server-ca').val('default'); |
| } else { |
| $('#wifi-server-ca').val('ignore'); |
| } |
| if ('ClientCertPattern' in eapConfig) { |
| // TODO: handle more complex client cert patterns. |
| var certPattern = eapConfig.ClientCertPattern; |
| if ('IssuerCARef' in certPattern) |
| $('#wifi-client-ca').val(certPattern.IssuerCARef); |
| if ('EnrollmentUri' in certPattern) |
| $('#wifi-enrollment-uri').val(certPattern.EnrollmentUri); |
| } |
| if ('ProxyURL' in netConfig) |
| $('#wifi-proxy-url').val(netConfig.ProxyURL); |
| } |
| }; |
| |
| /** |
| * Set up the UI with an object from ONC that has certificate information. |
| * @param {Object} netconfig VPN ONC object |
| **/ |
| vpnDialog.setCertToUi = function(vpnConfig) { |
| if (onc.findCert(vpnConfig.ServerCARef, main.oncCurrent) >= 0) |
| $('#vpn-server-ca').val(vpnConfig.ServerCARef); |
| if (vpnConfig.ClientCertType == 'Pattern') { |
| if (onc.findCert(vpnConfig.ClientCertPattern.IssuerCARef, |
| main.oncCurrent) >= 0) { |
| $('#vpn-client-ca').val(vpnConfig.ClientCertPattern.IssuerCARef); |
| } |
| if ('EnrollmentUri' in vpnConfig.ClientCertPattern) |
| $('#vpn-enrollment-uri').val(vpnConfig.ClientCertPattern.EnrollmentUri); |
| } |
| } |
| |
| /** |
| * Set up the UI with an object from ONC that has user credentials. |
| * @param {Object} netconfig VPN ONC object |
| **/ |
| vpnDialog.setUserCredentialsToUi = function(vpnConfig) { |
| if ('Username' in vpnConfig) |
| $('#vpn-username').val(vpnConfig.Username); |
| if ('Password' in vpnConfig) |
| $('#vpn-password').val(vpnConfig.Password); |
| if ('SaveCredentials' in vpnConfig && vpnConfig.SaveCredentials) |
| $('#vpn-save-credentials')[0].checked = true; |
| } |
| |
| /** |
| * Set up the UI with given VPN ONC configuration. |
| * @param {Object} netconfig VPN ONC object |
| **/ |
| vpnDialog.setToUi = function(netConfig) { |
| vpnDialog.oncBase = netConfig; |
| $('#vpn-name').val(netConfig.Name); |
| $('#vpn-guid').val(netConfig.GUID); |
| if ('ProxyURL' in netConfig) |
| $('#vpn-proxy-url').val(netConfig.ProxyURL); |
| var vpnConfig = netConfig.VPN; |
| $('#vpn-type').val(vpnConfig.Type); |
| $('#vpn-host').val(vpnConfig.Host); |
| if (vpnConfig.Type == 'L2TP-IPsec') { |
| var ipsecConfig = vpnConfig.IPsec; |
| if (ipsecConfig.AuthenticationType == 'PSK') { |
| $('#vpn-type').val('L2TP-IPsec-PSK'); |
| if ('PSK' in ipsecConfig) |
| $('#vpn-psk').val(ipsecConfig.PSK); |
| } else if (ipsecConfig.AuthenticationType == 'Cert') { |
| $('#vpn-type').val('L2TP-IPsec-Cert'); |
| vpnDialog.setCertToUi(ipsecConfig); |
| } |
| vpnDialog.setUserCredentialsToUi(vpnConfig.L2TP); |
| } else { |
| $('#vpn-type').val('OpenVPN'); |
| vpnDialog.setCertToUi(vpnConfig.OpenVPN); |
| vpnDialog.setUserCredentialsToUi(vpnConfig.OpenVPN); |
| } |
| }; |
| |
| /** |
| * Validate an ONC Client Certificate. This handles both the client |
| * certificate reference and certificate pattern cases. |
| * @param {Object} network NetworkConfiguration ONC object. |
| * @param {Object} result Result object indicating errors and warnings. |
| */ |
| onc.validateClientCert = function(outer, index, oncData, result) { |
| var network = oncData.NetworkConfigurations[index]; |
| if (!('ClientCertType' in outer)) { |
| result.errors.push(['errorLoadRequiredObjectMissing', 'ClientCertType']); |
| return result; |
| } |
| |
| if (outer.ClientCertType == 'Ref') { |
| if (!('ClientCertRef' in outer)) { |
| result.errors.push(['errorLoadRequiredObjectMissing', 'ClientCertRef']); |
| return result; |
| } |
| if (onc.findCert(outer.ClientCertRef, oncData) < 0) { |
| result.errors.push(['errorBadCertReference', |
| network.Name, 'ClientCertRef', outer.ClientCertRef]); |
| } |
| } else if (outer.ClientCertType == 'Pattern') { |
| if (!('ClientCertPattern' in outer)) { |
| result.errors.push(['errorLoadRequiredObjectMissing', |
| 'ClientCertPattern']); |
| return result; |
| } |
| var pattern = outer.ClientCertPattern; |
| if (!('IssuerCARef' in pattern) && |
| !('Subject' in pattern)) { |
| result.errors.push(['errorMissingClientCA', network.Name]); |
| return result; |
| } |
| if ('IssuerCARef' in pattern && |
| onc.findCert(pattern.IssuerCARef, oncData) < 0) { |
| result.errors.push(['errorBadCertReference', |
| network.Name, 'IssuerCARef', pattern.IssuerCARef]); |
| } |
| } else { |
| result.errors.push(['errorLoadRequiredObjectMissing', 'ClientCertType']); |
| } |
| return result; |
| }; |
| |
| onc.validate = function(oncData, result) { |
| if (!result) |
| result = { 'errors': [], 'warnings': [], 'hasOpaqueEntity': false }; |
| var hasOne; |
| for (var field in oncData) { |
| if (field == 'NetworkConfigurations' || |
| field == 'Certificates') |
| hasOne = true; |
| else |
| result.warnings.push(['warningUnrecognizedTopLevelField', field]); |
| } |
| if (!hasOne) { |
| result.errors.push(['errorEmptyOnc']); |
| } |
| if (!('NetworkConfigurations' in oncData)) |
| oncData.NetworkConfigurations = []; |
| for (var i = 0; i < oncData.NetworkConfigurations.length; ++i) |
| onc.validateNetwork(i, oncData, result); |
| if (!('Certificates' in oncData)) |
| oncData.Certificates = []; |
| for (var i = 0; i < oncData.Certificates.length; ++i) |
| onc.validateCertificate(i, oncData, result); |
| return result; |
| }; |
| |
| /** |
| * Load the given ONC configuration and show any errors in errorDom. |
| * @param {String} config ONC formatted string. |
| */ |
| loadDialog.loadConfig = function(config) { |
| var result = { 'errors': [], 'warnings': [], 'hasOpaqueEntity': false }; |
| try { |
| config = JSON.parse(config); |
| } catch(e) { |
| result.errors.push(['errorDuringLoad', e.toString()]); |
| } |
| if (!result.errors.length && typeof(config) != 'object') { |
| result.errors.push(['errorDuringLoad', e.toString()]); |
| } |
| if (!result.errors.length) { |
| result = onc.validate(config, result); |
| } |
| |
| // Display the errors we found (if any) |
| if (result.errors.length == 0) { |
| $('#apply-header', '#load-dialog').html(chrome.i18n.getMessage |
| ('loadSucceeded')); |
| } else { |
| // Clear everything out if we had errors. |
| $('#load-file-form')[0].reset(); |
| } |
| ui.showMessages(result, '#load-dialog'); |
| |
| loadDialog.oncToLoad = config; |
| loadDialog.oncToLoadResult = result; |
| }; |
| |
| // TODO(kmixter): Remove these two levels of functions once we get the LGTM (just |
| // here to help gerrit with diffing code). |
| (function() { |
| /** |
| * Validate the given certificate ONC data. |
| * @param {String} index index into Certificates array in ONC. |
| * @param {Object} oncData ONC data |
| * @param {Object} result Validation results data that will be update. |
| */ |
| onc.validateCertificate = function(index, oncData, result) { |
| if (!result) |
| result = { 'errors': [], 'warnings': [], 'hasOpaqueEntity': false }; |
| var certificate = oncData.Certificates[index]; |
| if (!('GUID' in certificate)) { |
| result.errors.push(['errorCertificateMissingGUID']); |
| return result; |
| } |
| if (!('Type' in certificate)) { |
| result.errors.push(['errorLoadRequiredObjectMissing', 'Type']); |
| return result; |
| } |
| if ('X509' in certificate) { |
| var der = Base64.unarmor(certificate.X509); |
| var asn1Data = asn1.parseAsn1(der); |
| if (!asn1Data) { |
| result.errors.push(['errorX509CertificateIsInvalid', certificate.GUID]); |
| return result; |
| } |
| var cert = asn1.interpretCert(asn1Data); |
| if (!asn1Data) { |
| result.errors.push(['errorX509CertificateIsInvalid', certificate.GUID]); |
| } |
| } else if ('PKCS12' in certificate) { |
| // TODO: support or deprecate PKCS12. |
| result.errors.push(['errorPKCS12CertificatesNotYetSupported', |
| certificate.GUID]); |
| result.hasOpaqueEntity = true; |
| } else { |
| result.errors.push(['errorCertificateMissingPayloadField', |
| certificate.GUID]); |
| result.hasOpaqueEntity = true; |
| } |
| return result; |
| }; |
| })(); |
| |
| // TODO(kmixter): Remove these two levels of functions once we get the LGTM (just |
| // here to help gerrit with diffing code). |
| (function() { |
| (function() { |
| /** |
| * Validate the given NetworkConfiguration ONC object. |
| * @param {Integer} index Index into NetworkConfigurations. |
| * @param {Object} oncData Complete ONC |
| * @param {Object} result Incoming results of validation, updated. |
| * @returns {Object} Results object |
| */ |
| onc.validateNetwork = function(index, oncData, result) { |
| var netConfig = oncData.NetworkConfigurations[index]; |
| if (!result) |
| result = { 'errors': [], 'warnings': [], 'hasOpaqueEntity': false }; |
| var requiredNetworkConfigurationKeys = [ 'GUID', 'Type' ]; |
| for (var j = 0; j < requiredNetworkConfigurationKeys.length; ++j) { |
| var key = requiredNetworkConfigurationKeys[j]; |
| if (!(key in netConfig)) { |
| result.errors.push(['errorLoadRequiredObjectMissing', key]); |
| return result; |
| } |
| } |
| if (!('Name' in netConfig) || netConfig.Name == '') { |
| result.errors.push(['errorMissingNetworkName', netConfig.Type]); |
| return result; |
| } |
| if (netConfig.Type == 'WiFi') { |
| onc.validateWiFiNetwork(index, oncData, result); |
| } else if (netConfig.Type == 'VPN') { |
| onc.validateVpnNetwork(index, oncData, result); |
| } else { |
| result.warnings.push(['errorLoadUnknownNetworkConfigType', |
| netConfig.Type]); |
| result.hasOpaqueEntity = true; |
| } |
| return result; |
| }; |
| |
| (function() { |
| /** |
| * Validate a WiFi NetworkConfiguration ONC object. |
| * @param {Integer} index Index into NetworkConfiguration ONC object. |
| * @param {Object} oncData ONC configuration |
| * @param {Object} result Result object indicating errors and warnings. |
| * @returns {Object} Result of validation. |
| */ |
| onc.validateWiFiNetwork = function(index, oncData, result) { |
| var netConfig = oncData.NetworkConfigurations[index]; |
| if (!('WiFi' in netConfig)) { |
| result.errors.push(['errorLoadRequiredObjectMissing', 'WiFi']); |
| return result; |
| } |
| var wifiConfig = netConfig.WiFi; |
| if (!('SSID' in wifiConfig) || wifiConfig.SSID == '') { |
| result.errors.push(['errorWiFiNetworkMissingSSID', 'SSID']); |
| return result; |
| } |
| if (!('Security' in wifiConfig)) { |
| result.errors.push(['errorLoadRequiredObjectMissing', 'Security']); |
| return result; |
| } |
| switch (wifiConfig.Security) { |
| case 'None': |
| break; |
| case 'WEP-PSK': |
| result.warnings.push(['warningWEPInherentlyUnsafe', netConfig.Name]); |
| if ('Passphrase' in netConfig.WiFi) { |
| // 5/13/16/29 characters are needed for 64/128/152/256-bit WEP ascii keys |
| // 10/26/32/58 characters are needed for 64/128/152/256-bit WEP hex keys |
| // Note that the actual bits supplied here are only |
| // 40/104/128/232 bits, respectively, but WEP adds some |
| // randomness to make up the rest of the bits. |
| var hexLengths = [10, 26, 32, 58]; |
| var passphrase = netConfig.WiFi.Passphrase; |
| if (passphrase.substr(0, 2) != '0x' || |
| hexLengths.indexOf(passphrase.length - 2) == -1) |
| result.errors.push(['errorWEPKeyInvalidLength', |
| netConfig.Name]); |
| } else { |
| result.error.push(['errorPassphraseMissing', |
| netConfig.Name]); |
| } |
| break; |
| case 'WPA-PSK': |
| if ('Passphrase' in netConfig.WiFi) { |
| if (netConfig.WiFi.Passphrase.length < 8) { |
| result.warnings.push(['warningShortWPAPassphraseUnsafe', |
| netConfig.WiFi.Security, |
| netConfig.Name, |
| netConfig.WiFi.Passphrase.length]); |
| } |
| } else { |
| result.errors.push(['errorPassphraseMissing', |
| netConfig.Name]); |
| } |
| break; |
| case 'WPA-EAP': |
| if (!('EAP' in netConfig.WiFi)) { |
| result.errors.push(['errorLoadRequiredObjectMissing', 'EAP']); |
| break; |
| } |
| var eapConfig = netConfig.WiFi.EAP; |
| if (wiFiDialog.wifiRequiresServerCertificate()) { |
| if ('ServerCARef' in eapConfig && |
| onc.findCert(eapConfig.ServerCARef, oncData) < 0) { |
| result.errors.push(['errorBadCertReference', |
| netConfig.Name, 'ServerCARef', eapConfig.ServerCARef]); |
| } |
| } |
| if (wiFiDialog.wifiRequiresClientCertficate()) { |
| onc.validateClientCert(eapConfig, index, oncData, result); |
| } |
| if (wiFiDialog.wifiRequiresPassword()) { |
| if (!('Password' in eapConfig)) |
| result.warnings.push(['warningEAPPasswordEmpty', |
| netConfig.Name]); |
| } |
| if (!('Outer' in eapConfig)) { |
| result.errors.push(['errorLoadRequiredObjectMissing', |
| 'WiFi.EAP.Outer']); |
| return result; |
| } |
| switch (eapConfig.Outer) { |
| case 'PEAP': |
| case 'EAP-TTLS': |
| case 'EAP-TLS': |
| case 'LEAP': |
| break; |
| default: |
| result.errors.push(['errorLoadUnhandledEapType', |
| eapConfig.Outer, netConfig.Name]); |
| return result; |
| } |
| if (!('Identity' in eapConfig) || eapConfig.Identity == '') { |
| result.warnings.push(['warningIdentityMissing', netConfig.Name]); |
| } |
| break; |
| default: |
| result.errors.push(['errorLoadUnhandledSecurityType', |
| wifiConfig.Security, netConfig.Name]); |
| } |
| return result; |
| }; |
| |
| /** |
| * Validate a VPN NetworkConfiguration ONC object's user credentials. |
| * @param (Object) container ONC container for user credentials. |
| * @param {Integer} index Index into NetworkConfiguration ONC object. |
| * @param {Object} oncData ONC configuration |
| * @param {Object} result Result object indicating errors and warnings. |
| * @returns {Object} Result of validation. |
| */ |
| onc.validateVpnUserCredentials = function(container, index, |
| oncData, result) { |
| var netConfig = oncData.NetworkConfigurations[index]; |
| if (!('Username' in container) || container.Username == '') { |
| result.warnings.push(['warningIdentityMissing', netConfig.Name]); |
| } |
| if (!('Password' in container) || container.Password == '') { |
| result.warnings.push(['warningPasswordMissing', netConfig.Name]); |
| } |
| } |
| |
| /** |
| * Validate a VPN NetworkConfiguration ONC object's certificates. |
| * @param (Object) container ONC container for certificates. |
| * @param {Integer} index Index into NetworkConfiguration ONC object. |
| * @param {Object} oncData ONC configuration |
| * @param {Object} result Result object indicating errors and warnings. |
| * @returns {Object} Result of validation. |
| */ |
| onc.validateVpnCerts = function(container, index, oncData, result) { |
| var netConfig = oncData.NetworkConfigurations[index]; |
| if (!('ServerCARef' in container)) |
| result.errors.push(['errorMissingVPNServerCA', netConfig.Name]); |
| else if (onc.findCert(container.ServerCARef, oncData) < 0) { |
| result.errors.push(['errorBadCertReference', |
| netConfig.Name, 'ServerCARef', |
| container.ServerCARef]); |
| } |
| onc.validateClientCert(container, index, oncData, result); |
| } |
| |
| /** |
| * Validate a VPN NetworkConfiguration ONC object. |
| * @param {Integer} index Index into NetworkConfiguration ONC object. |
| * @param {Object} oncData ONC configuration |
| * @param {Object} result Result object indicating errors and warnings. |
| * @returns {Object} Result of validation. |
| */ |
| onc.validateVpnNetwork = function(index, oncData, result) { |
| var netConfig = oncData.NetworkConfigurations[index]; |
| if (!('VPN' in netConfig)) { |
| result.errors.push(['errorLoadRequiredObjectMissing', 'VPN']); |
| return result; |
| } |
| var vpnConfig = netConfig.VPN; |
| if (!('Type' in vpnConfig)) { |
| result.errors.push(['errorLoadRequiredObjectMissing', 'VPN.Type']); |
| } |
| if (!('Host' in vpnConfig) || vpnConfig.Host == '') { |
| result.errors.push(['errorLoadRequiredObjectMissing', 'VPN.Host']); |
| } |
| if (vpnConfig.Type == 'L2TP-IPsec') { |
| if (!('L2TP' in vpnConfig)) { |
| result.errors.push(['errorLoadRequiredObjectMissing', |
| 'VPN.L2TP']); |
| return result; |
| } |
| onc.validateVpnUserCredentials(vpnConfig.L2TP, index, |
| oncData, result) |
| if (!('IPsec' in vpnConfig)) { |
| result.errors.push(['errorLoadRequiredObjectMissing', |
| 'VPN.IPsec']); |
| return result; |
| } |
| var ipsecConfig = vpnConfig.IPsec; |
| if (!('IKEVersion' in ipsecConfig)) { |
| result.errors.push(['errorLoadRequiredObjectMissing', |
| 'VPN.IPsec.IKEVersion']); |
| return result; |
| } |
| if (!('AuthenticationType' in ipsecConfig)) { |
| result.errors.push(['errorLoadRequiredObjectMissing', |
| 'VPN.IPsec.AuthenticationType']); |
| return result; |
| } |
| if (ipsecConfig.AuthenticationType != 'PSK' && |
| ipsecConfig.AuthenticationType != 'Cert') { |
| result.errors.push(['errorUnsupportedValue', |
| 'VPN.IPsec.AuthenticationType', |
| ipsecConfig.AuthenticationType]); |
| } |
| if (ipsecConfig.AuthenticationType == 'PSK') { |
| if (!('PSK' in ipsecConfig)) |
| result.warnings.push(['warningPreSharedKeyMissing', netConfig.Name]); |
| } else { |
| onc.validateVpnCerts(ipsecConfig, index, oncData, result); |
| } |
| } else if (vpnConfig.Type == 'OpenVPN') { |
| onc.validateVpnUserCredentials(vpnConfig.OpenVPN, index, |
| oncData, result) |
| onc.validateVpnCerts(vpnConfig.OpenVPN, index, oncData, result); |
| } else { |
| result.warnings.push(['errorUnsupportedVPNType', netConfig.Name, |
| vpnConfig.Type]); |
| result.hasOpaqueEntity = true; |
| return result; |
| } |
| return result; |
| }; |
| })(); |
| })(); |
| })(); |
| |
| /** |
| * Show the results in the dialog. |
| * @param {Object} result Result of compiling the UI to ONC. |
| * @param {Object} dialog DOM node of dialog. |
| */ |
| ui.showMessages = function(result, dialog) { |
| $('#apply-errors', dialog).html(ui.convertMessagesToHtml |
| (result.errors)); |
| $('#apply-warnings', dialog).html(ui.convertMessagesToHtml |
| (result.warnings)); |
| }; |
| |
| /** |
| * Handle ONC file load event. |
| * @param {Array.<Object>} files Array of files in file upload format. |
| * @return {Boolean} Indicates the event needs to be passed to other objects. |
| */ |
| loadDialog.handleLoadFile = function(files) { |
| $('#apply-errors').html(''); |
| for (var i = 0; i < files.length; ++i) { |
| var file = files[i]; |
| var reader = new FileReader(); |
| reader.onload = function(theFile) { |
| loadDialog.loadConfig(this.result); |
| }; |
| reader.readAsBinaryString(file); |
| } |
| return false; |
| }; |
| |
| /** |
| * Configures the ONC load file picker. |
| */ |
| loadDialog.configureLoadFilePicker = function() { |
| $('#load-file').change(function(event) { |
| loadDialog.handleLoadFile(event.target.files); |
| }); |
| }; |
| |
| /** |
| * Handles apply button press in load dialog. |
| */ |
| loadDialog.onApplyPress = function() { |
| // Ignore apply button if nothing was loaded or there are errors. |
| main.oncCurrent = loadDialog.oncToLoad; |
| ui.dismissDialog(); |
| }; |
| |
| /** |
| * Handles loading the body of the extension. Called from onload |
| * event handler. |
| */ |
| ui.translateText = function() { |
| var i18nNodes = document.querySelectorAll('[i18n]'); |
| for (var i = 0; i < i18nNodes.length; ++i) { |
| var i18nId = i18nNodes[i].getAttribute('i18n'); |
| var translation = chrome.i18n.getMessage(i18nId); |
| if (translation == '') { |
| translation = 'NO TRANSLATION FOR: ' + i18nId; |
| } |
| i18nNodes[i].textContent = translation; |
| } |
| }; |
| |
| $(document).ready(function() { |
| ui.translateText(); |
| $('div.selectable').click(function() { |
| var id = $(this)[0].id; |
| ui.openDialog(id.split('-')[0]); |
| }); |
| // Allow clicking anywhere on the text next to a checkbox to toggle |
| // the checkbox. |
| $('.checkable').mousedown(function(event) { |
| if (event.target.type != 'checkbox') |
| $(':checkbox', this).trigger('click'); |
| }); |
| }); |