📁
SKYSHELL MANAGER
PHP v8.2.31
Create
Create
Path:
root
/
home
/
thevaxnx
/
nativize.com
/
staging
/
wp-includes
/
js
/
tinymce
/
themes
/
Name
Size
Perm
Actions
📁
inlite
-
0755
🗑️
🏷️
🔒
📁
modern
-
0755
🗑️
🏷️
🔒
📄
wp-links-opml.php
6.83 KB
0444
🗑️
🏷️
⬇️
✏️
🔒
Edit: sslinstall.js
// Copyright 2024 WebPros International, LLC // All rights reserved. // copyright@cpanel.net http://cpanel.net // This code is subject to the cPanel license. Unauthorized copying is prohibited. /* global LOCALE:false */ (function(window) { "use strict"; // ----------------------- // Shortcuts // ----------------------- var YAHOO = window.YAHOO; var CPANEL = window.CPANEL; var DOM = YAHOO.util.Dom; var EVENT = YAHOO.util.Event; var LANG = YAHOO.lang; var REGION = YAHOO.util.Region; var document = window.document; var Handlebars = window.Handlebars; var PAGE = window.PAGE; var nvdata = PAGE && PAGE.nvdata || {}; var certificates_table_sort = nvdata.certificates_table_sort || { key: "domains" }; // ----------------------- // Constants // ----------------------- var IP_COMBOBOX_CONFIG = { useShadow: false, expander: "ipexpander", applyLocalFilter: true, queryMatchCase: false, typeAhead: true, autoHighlight: false, }; var MAX_COLUMN_WIDTH = { user: 90, apache: 130, }; var STAR_DOT_REGEXP = /^[*][.]/; var SORRY_COMMA_REGEXP = /sorry,/i; // ----------------------- // State Variables // ----------------------- var FQDN_TO_CREATED_DOMAIN = {}; var errorNotice; var validators = {}; var formProgressOverlay; var sslResultsPanel; var hiddenWhiteSpaceListTemplate; var pageHasDomainSelector = false; // True in cPanel, false in WHM. var lastCaughtFocus = null; // The variable that catchNextFocus() sets. var pageProgressPanel; var certificatesDataTable = null; // This is the certificate DataTable shared by changeUserComplete() and searchOk() var current_user = null; var current_browse_source = nvdata.browse_source || "user"; var dialogEventsSetup = false; var installMessageMaker; var ipSelectorItemTemplate; var wildcard_subdomain_warning; /** * Request a list of the certificates. * @public * @method browsessl * @param {object} clicked_el The element from which to expand the progress panel * @static */ function browsessl(clicked_el) { clearErrorNotice(); pageProgressPanel = new CPANEL.ajax.Progress_Panel( null, { zIndex: 2000, // to be above CJT validation message overlays effect: CPANEL.ajax.FADE_MODAL, } ); if (clicked_el) { pageProgressPanel.show_from_source(clicked_el); } else { pageProgressPanel.show(); } pageProgressPanel.after_hideEvent.subscribe( pageProgressPanel.destroy, pageProgressPanel, true ); var api_call; if (CPANEL.is_whm()) { if ( current_browse_source === "user" ) { // Select the last selected user again // so we retain context. api_call = { func: "listcrts", data: { user: current_user }, }; } else { api_call = { func: "fetch_ssl_vhosts", }; } } else { api_call = { version: 3, module: "SSL", func: "list_certs", api_data: { filter: [ [ "domain_is_configured", "eq", 1 ] ], }, }; } api_call.callback = { success: searchOk, failure: searchFailure, }; CPANEL.api(api_call); } /** * Transform a a domain list into a list of simple structures * that inform the template whether to make the domain a link. */ var installSuccessDomainListXform = function(d) { return { domain: d, makeLink: !/^[*]/.test(d) }; }; /** * Sends the form data off to do the SSL install for Apache. * * @method sendApacheInstall * @param {String|Object} form The form, or its ID. */ function sendApacheInstall(form) { form = DOM.get(form); var formdata = CPANEL.dom.get_data_from_form(form); // No need to catch exceptions here since validation has already run. var certParse = CPANEL.ssl.parseCertificateText(formdata.crt); var progress = new CPANEL.ajax.Progress_Panel( null, { show_status: true, status_html: LOCALE.maketext("Installing …"), } ); progress.show_from_source(form); progress.after_hideEvent.subscribe( progress.destroy, progress, true ); /** * What to do with an "OK" click when the page is to reload. */ var reload_button_handler = function(e) { var this_button = this.getButtons()[0]; this_button.disabled = true; if (document.activeElement === this_button) { this_button.blur(); } // Strip out a query from the URL, and reload. window.location.href = window.location.pathname; }; /** * What to do with an "OK" click when the page doesn't reload. */ var non_reload_button_handler = function(e) { this.cancel(); }; /** * What to call when the install API call returns successfully. */ var onsuccess = function(o) { var ip = o.cpanel_data.ip || o.argument && o.argument.ip; var tData = {}; if ( o.cpanel_data.working_domains ) { tData.workingDomains = o.cpanel_data.working_domains.map(installSuccessDomainListXform); tData.workingDomainsMessage = LOCALE.maketext("The SSL website is now active and accessible via HTTPS on [numerate,_1,this domain,these domains]:", tData.workingDomains.length); } if ( o.cpanel_data.warning_domains && o.cpanel_data.warning_domains.length ) { tData.warningDomains = o.cpanel_data.warning_domains.map(installSuccessDomainListXform); tData.warningDomainsMessage = LOCALE.maketext("The SSL website is also accessible via [numerate,_1,this domain,these domains], but the certificate does not support [numerate,_1,it,them]. Web browsers will show a warning when accessing [numerate,_1,this domain,these domains] via HTTPS:", tData.warningDomains.length); } if ( o.cpanel_data.extra_certificate_domains && o.cpanel_data.extra_certificate_domains.length ) { tData.extraCertificateDomains = o.cpanel_data.extra_certificate_domains.map( function(d) { return { domain: d }; } ); tData.extraCertificateDomainsMessage = LOCALE.maketext("The SSL certificate also supports [numerate,_1,this domain,these domains], but [numerate,_1,this domain does,these domains do] not refer to the SSL website mentioned above:", tData.extraCertificateDomains.length); } var okHandler, messageTemplate; switch ( o.cpanel_data.action ) { case "install": tData.statusMessageHTML = LOCALE.maketext("You have successfully configured SSL."); okHandler = reload_button_handler; tData.needReload = true; break; case "update": tData.statusMessageHTML = LOCALE.maketext("You have successfully updated the SSL website’s certificate."); okHandler = reload_button_handler; tData.needReload = true; break; case "none": tData.statusMessageHTML = LOCALE.maketext("This SSL certificate was already installed."); okHandler = non_reload_button_handler; } if (!messageTemplate) { messageTemplate = DOM.get("installSuccessTemplate").text; } if ( !installMessageMaker ) { installMessageMaker = Handlebars.compile( messageTemplate ); } var message = installMessageMaker( tData ); var dialog = new CPANEL.ajax.Common_Dialog( null, { effect: CPANEL.ajax.FADE_MODAL, buttons: [ { text: LOCALE.maketext("OK"), isDefault: true, handler: okHandler, }, ], } ); var header_text; if (o.cpanel_data.action === "update") { header_text = LOCALE.maketext("SSL Certificate Successfully Updated"); } else { header_text = LOCALE.maketext("SSL Host Successfully Installed"); } dialog.setHeader( CPANEL.widgets.Dialog.applyDialogHeader(header_text) ); dialog.beforeShowEvent.subscribe( function() { dialog.form.innerHTML = message; dialog.center(); } ); progress.fade_to(dialog); }; var apicall; if ( CPANEL.is_cpanel() ) { apicall = { version: 3, module: "SSL", func: "install_ssl", data: { domain: formdata.domain, cert: formdata.crt, key: formdata.key, cabundle: formdata.cabundle, }, }; } else { apicall = { func: "installssl", data: { domain: formdata.domain, ip: formdata.ip, crt: formdata.crt, key: formdata.key, cab: formdata.cabundle, }, }; } apicall.callback = CPANEL.ajax.build_page_callback( onsuccess, { on_error: function() { progress.hide(); }, } ); apicall.callback.argument = { ip: formdata.ip, domain: formdata.domain }; CPANEL.api(apicall); } /** * Fetches the selected certificate from the dialog box. * @method handleBeforeSubmit * @param {String} type The CustomEvent type * @param {Object[]} args The CustomEvent arguments * @param {Object} obj The scope object * @static */ function handleBeforeSubmit(type, args, obj) { var selected = certificatesDataTable.getSelectedRows()[0]; var record = certificatesDataTable.getRecord(selected); if ( current_browse_source === "user" ) { fetchByCertId( record.getData("id"), current_user ); } else { _populateFormByVhost( record.getData("servername") ); } } /** * Fire off an async call that should eventually populate the form with * the indicated SSL vhost's SSL components. * * @method _populateFormByVhost * @private * @param servername {String} The servername of the vhost whose information to load. */ function _populateFormByVhost( servername ) { CPANEL.sharedjs.sslinstall.showFormProgressOverlay(); CPANEL.api( { func: "fetch_vhost_ssl_components", api_data: { filter: [ ["servername", "eq", servername] ], }, callback: CPANEL.ajax.build_page_callback( populate_form_with_ssl_components, { on_error: CPANEL.sharedjs.sslinstall.hideFormProgressOverlay, } ), } ); } /** * Schlep the results of an AJAX call into the form. * This assumes that the passed-in object is for an API call that returns: * [ * { certificate:"..", key:"..", cabundle:".." } * ] * * @method populate_form_with_ssl_components * @param o {Object} An API response object, as described. */ var populate_form_with_ssl_components = function(o) { var payload = o.cpanel_data[0]; var mainform = DOM.get("mainform"); mainform.crt.value = payload.certificate; mainform.key.value = payload.key; mainform.cabundle.value = payload.cabundle || ""; try { _populate_domain_from_parsed_cert(CPANEL.ssl.parseCertificateText( payload.certificate )); } catch (e) {} CPANEL.sharedjs.sslinstall.hideFormProgressOverlay(); CPANEL.sharedjs.sslinstall.runValidation(); CPANEL.sharedjs.sslinstall.updateUI(); }; /** * Formatter that injects hidden whitespace after a period * in a string * @param {Object} elCell DOM object for the cell. * @param {Object} oRecord Record for this row. * @param {Object} oColumn Column definition for this cell. * @param {Object} oData Data for the specific cell. */ var injectHiddenWhiteSpace = function(elCell, oRecord, oColumn, oData) { if (LANG.isValue(oData)) { elCell.innerHTML = oData.html_encode().replace(/\./g, ".<wbr><a class=\"wbr\"></a>"); elCell.title = oData; } }; /** * Formatter that injects hidden whitespace after a period * in a string * @param {Object} elCell DOM object for the cell. * @param {Object} oRecord Record for this row. * @param {Object} oColumn Column definition for this cell. * @param {Object} oData Data for the specific cell. */ var injectHiddenWhiteSpaceList = function(elCell, oRecord, oColumn, oData) { if (oData && oData.length) { var array = oData; var data = []; for (var i = 0, l = array.length; i < l; i++) { var tmp = array[i].html_encode(); var item = { title: tmp, value: tmp.replace(/\./g, ".<wbr><a class=\"wbr\"></a>"), }; data.push(item); } // Process the template elCell.innerHTML = hiddenWhiteSpaceListTemplate( { data: data }); } }; /** * Formatter that correctly formats date to be * international compliant in the users selected language. * @param {Object} elCell DOM object for the cell. * @param {Object} oRecord Record for this row. * @param {Object} oColumn Column definition for this cell. * @param {Object} oData Data for the specific cell. */ var formatLocaleDate = function(elCell, oRecord, oColumn, oData) { if (LANG.isValue(oData)) { elCell.innerHTML = LOCALE.datetime( oData, "date_format_short" ); elCell.title = LOCALE.datetime( oData, "datetime_format_long" ); } }; /** * Formatter that shows either the issuer or Self-Signed. * @param {Object} elCell DOM object for the cell. * @param {Object} oRecord Record for this row. * @param {Object} oColumn Column definition for this cell. * @param {Object} oData Data for the specific cell. */ var formatIssuer = function(elCell, oRecord, oColumn, oData) { if (oRecord && oRecord.getData("is_self_signed")) { elCell.innerHTML = LOCALE.maketext("Self-Signed"); } else { injectHiddenWhiteSpace.apply(this, arguments); } }; /** * Formats INPUT TYPE=RADIO elements. * * @method formatRadio * @param el {HTMLElement} The element to format with markup. * @param oRecord {YAHOO.widget.Record} Record instance. * @param oColumn {YAHOO.widget.Column} Column instance. * @param oData {Object} (Optional) Data value for the cell. * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. * @static */ var formatRadio = function(el, oRecord, oColumn, oData) { var value = oData + ""; el.innerHTML = "<input type=\"radio\"" + " name=\"selected-cert\"" + " class=\"" + YAHOO.widget.DataTable.CLASS_RADIO + "\"" + " value=\"" + value.html_encode() + "\" />"; }; /** * Converts data to type Date from a linux timestamp. * @method parseLinuxTimeStamp * @param oData {Date | String | Number} Data to convert. * @return {Date} A Date instance. */ var parseLinuxTimeStamp = function(oData) { var date = null; // Convert to date if (LANG.isValue(oData) && !(oData instanceof Date)) { date = new Date(parseInt(oData * 1000, 10)); } else { return oData; } // Validate if (date instanceof Date) { return date; } else { YAHOO.log("Could not convert data " + LANG.dump(oData) + " to type Date", "warn", this.toString()); return null; } }; /** * Converts data to type Boolean from perl boolean. * @method parsePerlBoolean * @param oData {Date | String | Number} Data to convert. * @return {Boolean} A Boolean instance. */ var parsePerlBoolean = function(oData) { var value = null; // Convert to boolean if (LANG.isValue(oData) && (typeof oData !== "boolean")) { if (oData && (parseInt(oData, 10) === 1)) { value = true; } else { value = false; } } else { return oData; } // Validate if (typeof value === "boolean") { return value; } else { YAHOO.log("Could not convert data " + LANG.dump(oData) + " to type Date", "warn", this.toString()); return null; } }; /** * Utility method for sorting DataTable by the issuer. * This sorts self-signed certs "greater". * @method sortIssuer * @param a {Record} The first Record to compare. * @param b {Record} The second Record to compare. * @param desc {Boolean} Whether the sort is a descending sort or not. * @return {Number} -1 if a is first, 1 if a is second, 0 if the same */ var sortIssuer = function(a, b, desc) { var a_is_self_signed = a.getData("is_self_signed"); var b_is_self_signed = b.getData("is_self_signed"); if (a_is_self_signed) { if (b_is_self_signed) { return 0; } return desc ? -1 : 1; } else if (b_is_self_signed) { return desc ? 1 : -1; } return YAHOO.util.Sort.compare( a.getData("issuer.organizationName"), b.getData("issuer.organizationName"), desc ); }; /** * Utility method for sorting DataTable by the domains lists. * @method sortDomains * @param a {Record} The first Record to compare. * @param b {Record} The second Record to compare. * @param desc {Boolean} Whether the sort is a descending sort or not. * @return {Number} -1 if a is first, 1 if a is second, 0 if the same */ var sortDomains = function(a, b, desc) { return YAHOO.util.Sort.compare( a.getData("domains").join("\n"), b.getData("domains").join("\n"), desc ); }; // Setup the schema definitions for the data source var certResponseSchema = { fields: [ "id", "domain", "issuer.organizationName", { key: "not_after", parser: parseLinuxTimeStamp }, "friendly_name", // for browsing user "servername", // for browsing apache "domains", { key: "is_self_signed", parser: parsePerlBoolean }, ], }; var certColumnDefs = [ { key: "id", label: "", formatter: formatRadio, sortable: false, abbr: LOCALE.maketext("Select a certificate below:"), }, { key: "domains", maxAutoWidth: MAX_COLUMN_WIDTH.user, label: LOCALE.maketext("Domains"), formatter: injectHiddenWhiteSpaceList, sortable: true, sortOptions: { sortFunction: sortDomains }, abbr: LOCALE.maketext("Domain names on the certificate."), }, { key: "issuer.organizationName", maxAutoWidth: MAX_COLUMN_WIDTH.user, label: LOCALE.maketext("Issuer"), formatter: formatIssuer, sortable: true, sortOptions: { sortFunction: sortIssuer }, abbr: LOCALE.maketext("Issuer organization name."), }, { key: "not_after", label: LOCALE.maketext("Expiration"), formatter: formatLocaleDate, sortable: true, abbr: LOCALE.maketext("The certificate’s expiration date"), }, { key: "friendly_name", maxAutoWidth: MAX_COLUMN_WIDTH.user, label: LOCALE.maketext("Description"), formatter: injectHiddenWhiteSpace, sortable: true, abbr: LOCALE.maketext("A user-defined description for the certificate."), }, ]; /** * Handle things that always happen when the change-user AJAX call returns, * regardless of success or failure. * @method _changeUserOnReturn * @param o {Object} CPANEL.api response object * @static */ function _changeUserOnReturn(o) { var users = DOM.get("users"); if ( o.argument && (o.argument.oldActiveElement === users) && !lastCaughtFocus ) { users.focus(); } } /** * Handle the case where the user context change triggered * listcrts request succeeds. * @method changeUserComplete * @param o {Object} CPANEL.api response object * @static */ function changeUserComplete(o) { _changeUserOnReturn(o); _set_column_widths_for_browse_source("user"); certificatesDataTable.showColumn("friendly_name"); _update_cert_browser_table_with_new_certs(o.cpanel_data); // Update the current user current_user = o.argument.user; } function _set_column_widths_for_browse_source(browse_source) { var colset = certificatesDataTable.getColumnSet(); for (var c = 0; c < colset.flat.length; c++) { colset.flat[c].maxAutoWidth = MAX_COLUMN_WIDTH[browse_source]; } } // WHM "fetch_ssl_vhosts" returns data in a format that doesn't quite jive // with what the cert browser here expects, so this function massages that // data to play nicely with the cert browser table. function _format_fetch_ssl_vhosts_for_cert_browser(vhosts) { var with_cert = vhosts.filter(function(v) { return v.crt; }); // Grab "servername" from the "crt" hash. var crts = with_cert.map( function(v) { v.crt.servername = v.servername; return v.crt; } ); // Make sure we show each cert only once. var certs_already_seen = {}; crts = crts.filter( function(c) { if ( certs_already_seen[c.id] ) { return false; } certs_already_seen[c.id] = true; return true; } ); return crts; } function loadApacheCertsComplete(o) { certificatesDataTable.hideColumn("friendly_name"); _set_column_widths_for_browse_source("apache"); var crts = _format_fetch_ssl_vhosts_for_cert_browser( o.cpanel_data ); _update_cert_browser_table_with_new_certs(crts); } function _update_cert_browser_table_with_new_certs(crts) { _enable_cert_browser_controls(); // Hide the table message certificatesDataTable.hideTableMessage(); var certDataSource = new YAHOO.util.LocalDataSource(crts); certDataSource.responseType = YAHOO.util.DataSource.TYPE_JSARRAY; certDataSource.responseSchema = certResponseSchema; certificatesDataTable.load( { datasource: certDataSource } ); var state = certificatesDataTable.getState(); if ( state.sortedBy ) { certificatesDataTable.sortColumn( certificatesDataTable.getColumn( state.sortedBy.key ), state.sortedBy.dir ); } certificatesDataTable.selectRow(0); } /** * Handle the case where loading new certs fails during the API call. * This works for both user and Apache cert browsing. * * @method chageUserFailed * @param {Object} o Error object. * @static */ function loadNewCertsFailed(o) { _changeUserOnReturn(o); var postRenderOnFail = function() { certificatesDataTable.unsubscribe("postRenderEvent", postRenderOnFail); // Show an error certificatesDataTable.showTableMessage( LOCALE.maketext("The certificate list could not be retrieved because of an error: [_1]", o.cpanel_error.html_encode()), YAHOO.widget.DataTable.CLASS_ERROR); }; certificatesDataTable.subscribe("postRenderEvent", postRenderOnFail); // Remove all the rows var length = certificatesDataTable.getRecordSet().getLength(); if (length > 0) { certificatesDataTable.deleteRows(0, length); // postRenderOnFail is triggered after the delete completes. } else { // No records to delete, so call synchronously postRenderOnFail(); } } /** * Set a body listener that catches the next focus event. * When it catches one, it sets the "lastCaughtFocus" variable. * This is useful for re-focusing an element that you've disabled * for an AJAX call, but only doing so if the user hasn’t focused * something else in the meantime. * * TODO: Make this a class that tracks lastCaughtFocus internally. * * @method catchNextFocus * @static */ function catchNextFocus() { lastCaughtFocus = null; var listener = function(e) { EVENT.removeListener( this, "focusin", listener ); lastCaughtFocus = e; }; EVENT.on( document.body, "focusin", listener ); } /** * Convenience wrapper around YAHOO.util.Event.preventDefault() */ var preventDefault = function(e) { EVENT.preventDefault(e); }; /** * Handler for when the browse source changes. * * NOTE: Context object MUST be the clicked radio button. * * @method _onBrowseSourceChange * @private * @param e {Object} The event object from a YUI 2 DOM listener. */ var _onBrowseSourceChange = function(e) { var clicked_el = this; current_browse_source = clicked_el.value; if ( current_browse_source === "user" ) { // Read this manually because get_data_from_form will reject values from // disabled form controls. var select = DOM.get("users"); _update_cert_table_for_user( select[select.selectedIndex].value ); } else { _update_cert_table_for_apache(); } }; /** * Handler for when the user in the cert browser popup changes. * * NOTE: Context object MUST be the user <select>. * * @method _onUserChange * @private * @param e {Object} The event object from a YUI 2 DOM listener. */ var _onUserChange = function(e) { if (this.selectedIndex === -1) { return; } var new_user = this.options[this.selectedIndex].value; if ( new_user !== current_user ) { _update_cert_table_for_user(new_user); } }; /** * Fire off an AJAX call that eventually will update the cert browser table. * This is for when we want to query Apache. * * @method _update_cert_table_for_apache * @private */ var _update_cert_table_for_apache = function() { var what_to_do = function() { // Show the loading message certificatesDataTable.showTableMessage( LOCALE.maketext("Loading installed Apache certificates …"), YAHOO.widget.DataTable.CLASS_LOADING); CPANEL.api( { func: "fetch_ssl_vhosts", callback: { success: loadApacheCertsComplete, failure: loadNewCertsFailed, }, } ); }; _update_cert_table(what_to_do); }; /** * Query the DOM to return the cert browser controls. * * @method _get_cert_browser_controls * @private */ var _get_cert_browser_controls = function() { return CPANEL.Y.all("input[name=browse_source], select#users"); }; /** * Enable or disable the cert browser controls as needed. * This is smart enough not to enable the user drop-down when we're * browsing Apache. * * @method _get_cert_browser_controls * @private */ var _set_cert_browser_controls_disabled = function(to_disable) { _get_cert_browser_controls().forEach( function(el) { // Ensure that we don't enable the user selector when we're // browsing Apache's certificates. if ( to_disable || (current_browse_source === "user") || (el !== DOM.get("users")) ) { el.disabled = to_disable; } } ); }; /** * Enable the cert browser controls. * * @method _get_cert_browser_controls * @private */ var _enable_cert_browser_controls = function() { _set_cert_browser_controls_disabled(false); }; /** * Disable the cert browser controls. * * @method _get_cert_browser_controls * @private */ var _disable_cert_browser_controls = function() { _set_cert_browser_controls_disabled(true); }; /** * Fire off an AJAX call that eventually will update the cert browser table. * This is code common to both Apache and user-sslstorage browsing. * * @method _update_cert_table_for_apache * @private */ var _update_cert_table = function(what_to_do_after_clearing_table) { _disable_cert_browser_controls(); // For update_cert_table_for_user. Put it here in case some browser // fires "focus" when you disable the activeElement. catchNextFocus(); var postRender = function() { certificatesDataTable.unsubscribe("postRenderEvent", postRender); return what_to_do_after_clearing_table.apply(this, arguments); }; // Remove all the rows var length = certificatesDataTable.getRecordSet().getLength(); if (length > 0) { certificatesDataTable.subscribe("postRenderEvent", postRender); certificatesDataTable.deleteRows(0, length); // postRender is triggered after the delete completes. } else { // No records to delete, so call synchronously postRender(); } }; /** * Fire off an AJAX call that eventually will update the cert browser table. * This is for when we want to query a user's datastore. * * @method _update_cert_table_for_user * @private */ var _update_cert_table_for_user = function(new_user) { // If the users drop-down is focused, // then we want to restore that focused state when the // AJAX call returns, unless the user has focused // something else in the meantime. var oldActiveElement = document.activeElement; /** * What to do immediately after the cert datatable is done rendering. */ var postRender = function() { // Show the loading message certificatesDataTable.showTableMessage( LOCALE.maketext("Loading certificates for “[output,strong,_1]” …", new_user), YAHOO.widget.DataTable.CLASS_LOADING); CPANEL.api( { func: "listcrts", data: { user: new_user }, callback: { success: changeUserComplete, failure: loadNewCertsFailed, argument: { user: new_user, oldActiveElement: oldActiveElement, }, }, } ); }; _update_cert_table(postRender); }; /** * Handle the case where the listcrts request fails. * * @method searchFailure * @param o {Object} CPANEL.api response object * @static */ function searchFailure(o) { showErrorNotice( LOCALE.maketext("The certificate list could not be retrieved because of an error: [_1]", o.cpanel_error.html_encode())); } /** * Handle the case where the listcrts request succeeds. * @method searchOk * @param o {Object} CPANEL.api response object * @static */ function searchOk(o) { /** * The handler for when the SSL cert browser is shown. * This event needs to be setup every time the API returns since it * changes the table's data. */ var beforeShowResultsPanel = function() { sslResultsPanel.beforeShowEvent.unsubscribe(beforeShowResultsPanel); // Add to prevent the parent window from scrolling when // the popup is scrolling past its top or bottom. EVENT.addListener(document.body, "scroll", preventDefault); _enable_cert_browser_controls(); // Set the current user (in WHM) if (DOM.get("users")) { var users = DOM.get("users"); CPANEL.dom.set_form_el_value(users, current_user); } // For Apache certs, we process below var crts = o.cpanel_data; if ( CPANEL.is_cpanel() && PAGE && PAGE.data ) { var c; // silly jshint var installable_domains = PAGE.data.installable_domains; // TODO: Implement this using YUI 3's array extras? for (c = crts.length - 1; c >= 0; c--) { var l = installable_domains.length; var cert_matches_at_least_one_account_domain = false; for ( var i = 0; i < l; i++ ) { if ( CPANEL.ssl.doesDomainMatchOneOf( installable_domains[i], crts[c].domains ) ) { cert_matches_at_least_one_account_domain = true; break; } } if (!cert_matches_at_least_one_account_domain) { crts.splice(c, 1); } } } else if ( current_browse_source === "apache" ) { crts = _format_fetch_ssl_vhosts_for_cert_browser(crts); // We set maxAutoWidth to MAX_COLUMN_WIDTH.user in the column // definitions. If case the table's first view is with Apache // certs instead, switch maxAutoWidth here to what it needs to be. for (var cd = 0; cd < certColumnDefs.length; cd++) { certColumnDefs[cd].maxAutoWidth = MAX_COLUMN_WIDTH.apache; if (certColumnDefs[cd].key === "friendly_name") { certColumnDefs[cd].hidden = true; } } } // Setup the data-source for the data table var certDataSource = new YAHOO.util.LocalDataSource(crts); certDataSource.responseType = YAHOO.util.DataSource.TYPE_JSARRAY; certDataSource.responseSchema = certResponseSchema; // Setup the scrolling data table. var list = DOM.get("certlist"); if (list) { certificatesDataTable = new YAHOO.widget.ScrollingDataTable( "certlist", certColumnDefs, certDataSource, { height: "250px", className: "sortable", MSG_EMPTY: LOCALE.maketext("This account does not have any installable certificates."), } ); // Do the sort on the client since the API, // as of early 2013, doesn't know how to sort arrays. // NOTE: This doesn't work if you put it in the instantiation config // since that just tells DataTable how the data is *already* sorted. certificatesDataTable.sortColumn( certificatesDataTable.getColumn(certificates_table_sort.key), certificates_table_sort.dir || YAHOO.widget.DataTable.CLASS_ASC ); certificatesDataTable.set("selectionMode", "single"); certificatesDataTable.subscribe("theadCellClickEvent", function(e) { // This fixes an issue with selected and sorting. if (selectedRecord) { this.unselectAllRows(); actionSource = "sort"; this.selectRow(selectedRecord); } }); certificatesDataTable.subscribe( "columnSortEvent", function(oArgs) { var the_sort = this.get("sortedBy"); certificates_table_sort = { dir: the_sort.dir, key: the_sort.key, }; CPANEL.nvdata.save(); } ); // Capture the source for the select even, we are trying // to screen out the ones that are from mouse based actions. var actionSource = null; var selectedRow = null; var selectedRecord = null; certificatesDataTable.subscribe("radioClickEvent", function(e) { var radio = e.target; if (radio.checked) { this.unselectAllRows(); var row = this.getTrEl(radio); actionSource = "radio"; this.selectRow(row); } }); certificatesDataTable.subscribe("rowSelectEvent", function(e) { // Prevent text selection as recommended in datatable this.clearTextSelection(); // We only get here for up and down arrow keys var row = e.el; var targetRecord = this.getRecord(row); selectedRecord = targetRecord; // Select the radio button. CPANEL.Y(row).one("input.yui-dt-radio").checked = true; if (actionSource !== null) { selectedRow = row; // Not a keyboard event so reset it and return. actionSource = null; return; } // Scroll as needed var parentContainer = row.offsetParent.parentNode; var regionParent = REGION.getRegion(parentContainer); var regionRow = REGION.getRegion(row); var regionIntersect = regionRow.intersect(regionParent); if (regionIntersect === null || regionIntersect.getArea() === 0 || regionIntersect.height < regionRow.height) { // NOTE: There is still a little issue here, but this is // usable. If you scroll the selected item off the screen // then try to use the keyboard to scroll selection, it doesn't // correctly move the selected item into view. // regionIntersect.getArea() === 0 means no overlap // regionIntersect.height < regionRow.height mean partial overlap // Calculate the fudge for partial overlaps var deltaHeight = regionIntersect ? regionIntersect.height : 0; // Determine if we need to scroll to top or scroll to bottom // depending of in we are scrolling up or down. var nextRow = this.getNextTrEl(row); var scrollingUp = selectedRow === nextRow; if (scrollingUp) { parentContainer.scrollTop -= regionRow.height + deltaHeight; } else { parentContainer.scrollTop += regionRow.height + deltaHeight; } } // Update the state variable used to track the previous row. selectedRow = row; }); certificatesDataTable.subscribe("rowDblclickEvent", function(e) { // Prevent text selection as recommended in datatable this.clearTextSelection(); EVENT.preventDefault(e); var row = DOM.get(e.target); this.getRecord(row); sslResultsPanel.submit(); }); certificatesDataTable.subscribe("rowClickEvent", function(e) { EVENT.preventDefault(e); this.unselectAllRows(); var row = DOM.get(e.target); actionSource = "click"; this.selectRow(row); }); certificatesDataTable.subscribe("postRenderEvent", function(e) { sslResultsPanel.center(); }); certificatesDataTable.selectRow(0); } }; if (!dialogEventsSetup) { sslResultsPanel.beforeHideEvent.subscribe(function() { // Restore normal parent window scrolling behavior. EVENT.removeListener(document.body, "scroll", preventDefault); // Release the table for garbage collection since // we will just recreate it on each show. certificatesDataTable.destroy(); certificatesDataTable = null; }); dialogEventsSetup = true; } sslResultsPanel.beforeShowEvent.subscribe(beforeShowResultsPanel); // Show the popup pageProgressPanel.fade_to(sslResultsPanel); } /** * Clears the notice area. * @method clearErrorNotice * @static */ function clearErrorNotice() { if (errorNotice && errorNotice.cfg) { errorNotice.hide(); errorNotice.destroy(); errorNotice = null; } } /** * Shows an error message * @method showErrorNotice * @param text {String} Error message text. * @param extraText {String} Text to add to the collapsible section. * @param extraOpenLabel {String} Open collapsable section label. * @param extraCloseLabel {String} Close collapsable section label. * @static */ function showErrorNotice(text, extraText, extraOpenLabel, extraCloseLabel) { if (formProgressOverlay && formProgressOverlay.cfg) { formProgressOverlay.hide(); } if (pageProgressPanel && pageProgressPanel.cfg) { pageProgressPanel.hide(); } var BUTTON_PREFIX = "en_btn_"; var AREA_PREFIX = "en_area_"; var buttonId, areaId; var elID = DOM.generateId(); if (extraText) { extraOpenLabel = extraOpenLabel || LOCALE.maketext("Show"); extraCloseLabel = extraCloseLabel || LOCALE.maketext("Hide"); buttonId = BUTTON_PREFIX + elID; areaId = AREA_PREFIX + elID; text += YAHOO.lang.substitute(DOM.get("error-extra-block").text, { "btnId": buttonId, "areaId": areaId, "label": extraOpenLabel, "content": extraText.html_encode(), }); } errorNotice = new CPANEL.widgets.Dynamic_Page_Notice({ level: "error", content: text, visible: false, }); if (extraText) { // Setup the events just before we show the notice errorNotice.beforeShowEvent.subscribe(function(e) { var theButtonId = buttonId; var btn = DOM.get(theButtonId); // Setup the toggle event handler EVENT.on(btn, "click", function(e) { var el = DOM.get(areaId); if (DOM.getStyle(el, "display") === "none") { DOM.setStyle(el, "display", ""); this.innerHTML = extraCloseLabel; } else { DOM.setStyle(el, "display", "none"); this.innerHTML = extraOpenLabel; } errorNotice = null; }); }); } errorNotice.show(); errorNotice.hideEvent.subscribe( function() { CPANEL.align_panels_event.fire(); } ); CPANEL.align_panels_event.fire(); } /** * Set a Page_Progress_Overlay as an AJAX status indicator. * * @method showFormProgressOverlay * @static */ function showFormProgressOverlay(content_html) { if ( !formProgressOverlay || !formProgressOverlay.cfg ) { formProgressOverlay = new CPANEL.ajax.Page_Progress_Overlay( null, { zIndex: 2000, // to be above CJT validation message overlays covers: DOM.get("mainform"), show_status: !!content_html, status_html: content_html, } ); } else { formProgressOverlay.set_status_now(content_html); } formProgressOverlay.show(); } /** * Hide the Page_Progress_Overlay AJAX status indicator. * * @method hideFormProgressOverlay * @static */ function hideFormProgressOverlay(content_html) { formProgressOverlay.hide(); } /** * Validate the domain, allowing wildcards through. * * @method validateDomain * @return {Boolean} */ var validateDomain = function(el) { var val = ( typeof el === "object" ) ? el.value : el; val = val.trim(); return !val || CPANEL.validate.host(val.replace(/^\*\./, "")); }; /** * Validate that there is at least one service selected. * * @method _validate_services_selected * @private * @return {Boolean} */ function _validate_services_selected() { return !!CPANEL.dom.get_data_from_form("mainform").service; } /** * Validate the IP, allowing wildcards through. * NOTE: This only works for WHM. In cPanel, there is no need to check an IP. * * @method validateIP * @return {Boolean} */ var validateIP = function(el) { return !validateIP_fail_reason(el) ? true : false; }; /** * Give a reason, if any, why the IP is invalid. * Returns the empty string otherwise. * * @method validateIP_for_domain_fail_reason * @return {String} */ var validateIP_fail_reason = function(el) { var val = ( typeof el === "object" ) ? el.value : el; if (val.length) { if ( !CPANEL.validate.ip(val) ) { return LOCALE.maketext("Enter a valid IP address."); } var sslips = PAGE.properties && PAGE.properties.sslips; if (sslips && !sslips[val]) { return LOCALE.maketext("The IP address “[_1]” is not available, or you do not have permission to use it.", val); } } return ""; }; /** * Validate the certificate. * * @method validateCert * @return {Boolean} */ var validateCert = function(el) { var val = el.value.trim(); if (!val) { return true; // Another validator handles empty-string. } try { return !!CPANEL.ssl.parseCertificateText(val); } catch (e) { return false; } return true; }; /** * Validate the key. * * @method validateKey * @return {Boolean} */ var validateKey = function(el) { var val = el.value.trim(); if (!val) { return true; // Another validator handles empty-string. } var parse; try { parse = CPANEL.ssl.parseKeyText(val); } catch (e) {} return !!parse; }; /** * Validate the CA bundle. * * @method validateCABundle * @return {Boolean} */ var validateCABundle = function(el) { var val = el.value.trim(); if (!val) { return true; } try { return !!CPANEL.ssl.parseCABundleText(val); } catch (e) { return false; } return true; }; /** * Verify that one of these is true: * a) The certificate is invalid. * b) The domain is invalid. * c) The selected domain and certificate match. * * @method validateDomainCertificateMatch * @param {string} el The DOM element whose value is the certificate text. * @return {Boolean} */ var validateDomainCertificateMatch = function(el) { var cert = el.value.trim(); if (!cert) { return true; } var formData = CPANEL.dom.get_data_from_form("mainform"); var domain = formData.domain; if ( !validateDomain(domain) ) { return true; } var all_fqdns = [domain]; if (pageHasDomainSelector) { var alias_subdomains = PAGE.data.domain_aliases[domain] || []; all_fqdns = all_fqdns.concat( alias_subdomains.map( function(as) { return as + "." + domain; } ) ); } return all_fqdns.some( function(fqdn) { try { return CPANEL.ssl.validateCertificateForDomain( cert, fqdn ); } catch (e) { return true; } } ); }; /** * Verify that one of these is true: * a) The key is invalid. * b) The certificate is invalid. * c) The key and certificate match. * Return true for invalids to prevent validate.js from spooging the user * with extra warnings. * * @method validateKeyCert * @return {Boolean} */ var validateKeyCert = function(el) { var val = el.value.trim(); if (!val) { return true; } try { var key_parse = CPANEL.ssl.parseKeyText(val); var cert_text = el.form.crt.value.trim(); var cert_parse = CPANEL.ssl.parseCertificateText(cert_text); return cert_parse.modulus === key_parse.modulus; } catch (e) { return true; } }; /** * Verify that one of these is true: * a) The CA bundle is invalid. * b) The certificate is invalid. * c) The CA bundle and the certificate match. * Return true for invalids to prevent validate.js from spooging the user * with extra warnings. * * @method validateCABundleCert * @return {Boolean} */ var validateCABundleCert = function(el) { var cab = el.value.trim(); if ( !cab ) { return true; } var cert = el.form.crt.value.trim(); try { var cert_parse = CPANEL.ssl.parseCertificateText(cert); var cab_parse = CPANEL.ssl.parseCABundleText(cab); var cabLeafSubject = JSON.stringify(cab_parse.shift().subjectList); var certIssuer = JSON.stringify(cert_parse.issuerList); return cabLeafSubject === certIssuer; } catch (e) { return true; } }; /** * HTML-escape a string and wrap it in <code>. * * @method wrapCode * @param {string} str The string to process. * @return {string} The processed string. */ var wrapCode = function(str) { return "<code>" + str.html_encode() + "</code>"; }; /** * Same as updateUI, but builds in a very small delay for paste events. * * @param object A set of named parameters; can be: * active_element: The element that "caused" the UI update. * @method delayed_updateUI */ var delayed_updateUI = function(opts) { window.setTimeout( function() { updateUI(opts); }, 1 ); }; /** * Show parsed data and whatever warnings need be, * and update which of the "Fetch" buttons shows. * NOTE: This does NOT do validation; validate.js handles that separately. * * @param object A set of named parameters; can be: * active_element: The element that "caused" the UI update. (Not for "services" ui_mode!) * @method updateUI */ var updateUI = function(opts) { var certParse; var pageForm = DOM.get("mainform"); var formData = CPANEL.dom.get_data_from_form(pageForm); var formDomain = formData.domain; var formCrt = formData.crt; try { certParse = CPANEL.ssl.parseCertificateText(formCrt); } catch (e) {} var shown = CPANEL.widgets.ssl.showCertificateParse( formCrt.trim(), "cert_parse", {} ); DOM.setStyle( "cert_parse", "display", shown ? "" : "none" ); if ( DOM.get("fetch-cert") ) { DOM.get("fetch-cert").disabled = !certParse; } if ( DOM.get("fetch-domain") ) { DOM.get("fetch-domain").disabled = !validateDomain( formDomain ); } if (opts && opts.active_element) { var domainFieldHasText = (formDomain && formDomain.trim()) ? 1 : 0; var crtFieldHasText = (formCrt && formCrt.trim()) ? 1 : 0; if ( (opts.active_element === "domain" && domainFieldHasText) || ( !crtFieldHasText && domainFieldHasText ) ) { if ( DOM.getStyle("fetch-cert", "display") !== "none" ) { CPANEL.animate.fade_out("fetch-cert"); } if ( DOM.getStyle("fetch-domain", "display") === "none" ) { CPANEL.animate.fade_in("fetch-domain"); } } else if (certParse) { if ( (PAGE.ui_mode !== "services") && (DOM.getStyle("fetch-domain", "display") !== "none") ) { CPANEL.animate.fade_out("fetch-domain"); } if ( DOM.getStyle("fetch-cert", "display") === "none" ) { CPANEL.animate.fade_in("fetch-cert"); } } } CPANEL.align_panels_event.fire(); }; /** * Handle a failed fetch_for_install_form() callback. * * @method sslInfoFailure * @param {Object} o The CPANEL.api callback parameter. */ function sslInfoFailure(o) { showErrorNotice( LOCALE.maketext("The lookup failed because of an error: [_1]", o.cpanel_error.html_encode()) ); } /** * Paste in data from a fetch_for_install_form() callback. * * @method sslInfoOk * @param {Object} o The CPANEL.api callback parameter. */ function sslInfoOk(o) { formProgressOverlay.hide(); var result = o.cpanel_data[0] || o.cpanel_data; if (!result) { return; } var need = o.argument && o.argument.need; var exceptions = o.argument && o.argument.exceptions; var formParse = CPANEL.dom.get_data_from_form("mainform"); var setCert = !o.argument || (o.argument.known !== "crt"); var setIp = !formParse.ip || !validateIP(formParse.ip); // If we "need" the certificate, also paste in the key and CA bundle. // We might as well use the "domain" from this API response since then // we know that the IP and domain match. // All things being equal, we could easily grab a domain from the // certificate parse in JS, but that "tricks" the UI into // fetching on the domain, which clobbers the cert. ["key", "cab", "crt", "domain", "ip"].forEach( function(i) { var curResult = result[i]; var elId = "ssl" + i; if ( !exceptions || exceptions.indexOf(elId) === -1 ) { if (!curResult || SORRY_COMMA_REGEXP.test(curResult) || !DOM.get(elId) || (need && need !== i)) { return; } else if ( setIp && (i === "ip") && !result.user && PAGE.properties.sslips[curResult] ) { // always set ip if we have it CPANEL.dom.set_form_el_value(elId, curResult); } else if ( i === "domain" ) { setDomain(curResult); } else if ( ( ( i === "crt" ) && setCert ) || i === "key" || i === "cab" ) { CPANEL.dom.set_form_el_value(elId, curResult); } } } ); if ( !o.cpanel_data.crt && o.cpanel_messages[0] ) { var notice = new CPANEL.widgets.Dynamic_Page_Notice( { container: "autofill_message_container", visible: false, // capture the slide animation level: o.cpanel_messages[0].level, content: o.cpanel_messages[0].content.html_encode(), } ); var slide = notice.animated_show(); slide.onTween.subscribe( CPANEL.align_panels ); notice.hideEvent.subscribe( CPANEL.align_panels ); } updateUI(); runValidation(); } /** * Sets the domain selector, allowing for the www subdomain. * If there is nothing that matches, set the empty string. * * @param {String} domain The domain to attempt setting. * @method setDomain * @return {string} Which domain we set. */ function setDomain(domain) { var domainsToSet = [ domain, domain.replace(STAR_DOT_REGEXP, ""), "", ]; var curDomain; while ( domainsToSet[0] ) { curDomain = domainsToSet.shift(); if ( CPANEL.dom.set_form_el_value( "ssldomain", curDomain ) ) { return curDomain; } } } /** * Clear CJT validation. * If the form is emptied like we do when we click Update SSL * We need to clear all the validation messages * * @method clearValidation */ function clearValidation() { ["ip", "domain", "cert", "key", "cab"].forEach( function(validation_type) { if ( validators[validation_type] ) { validators[validation_type].clear_messages(); } } ); } /** * Run CJT validation. * NOTE: This does not call the function under CPANEL.validate.form_checkers * because that function is designed for when you submit the form, and it'll * make validation errors appear if (for example) the entire form is empty. * * @method runValidation */ function runValidation() { // === Do NOT do it this way .. see above. // CPANEL.validate.form_checkers.btnInstall( null, 1 ); var form_values = CPANEL.dom.get_data_from_form("mainform"); var validation_types = ["cert", "key"]; if (PAGE.ui_mode !== "services") { validation_types.push("domain"); } validation_types.forEach( function(validation_type) { validators[validation_type].verify(); } ); if (validators.ip && form_values.ip) { validators.ip.verify(); } // Validate the CAB separately because it alone can legitimately be empty. validators.cab.verify(); } /** * Fire off an AJAX lookup by a given certificate ID. * * @method fetchByCertId * * @param certId {string} - The id of the certificate to fetch from the server. * @param user {string} - The name of the user to retrieve the certificate from. * @return {boolean} * @static */ function fetchByCertId(certId, user) { showFormProgressOverlay(); var api_call; if (CPANEL.is_whm()) { api_call = { func: "fetchcrtinfo", data: { id: certId, user: user, }, }; } else { api_call = { version: 3, module: "SSL", func: "fetch_cert_info", data: { id: certId }, }; } // Ideally this would use build_page_callback; however, that function // doesn't (yet) play nicely with WHM API v1 batch calls. api_call.callback = { success: function(o) { formProgressOverlay.hide(); var certParse; if ( o.cpanel_data.certificate ) { DOM.get("sslcrt").value = o.cpanel_data.certificate; // NOTE: For better UCC support, we should show users a drop-down // or a combobox of domains and let them choose. if ( PAGE.ui_mode !== "services" ) { try { certParse = CPANEL.ssl.parseCertificateText( o.cpanel_data.certificate ); } catch (e) {} _populate_domain_from_parsed_cert(certParse); } } if ( o.cpanel_data.key && !SORRY_COMMA_REGEXP.test(o.cpanel_data.key) ) { DOM.get("sslkey").value = o.cpanel_data.key; } if ( certParse && certParse.isSelfSigned ) { DOM.get("sslcab").value = ""; } else if ( o.cpanel_data.cabundle && !SORRY_COMMA_REGEXP.test(o.cpanel_data.cabundle) ) { DOM.get("sslcab").value = o.cpanel_data.cabundle; } updateUI(); runValidation(); }, failure: function(o) { showErrorNotice( LOCALE.maketext("The certificate information could not be retrieved because of an error: [_1]", (o.cpanel_error || o.statusText).html_encode() ) ); }, }; CPANEL.api( api_call ); } /** * Fetch SSL items that match given criteria. * * @method fetch_for_install_form * @param {string} known What we are searching on: domain or sslcrt * @param {Array} exceptions What *not* to touch on the form. * @return {Boolean} */ function fetch_for_install_form(known, exceptions) { clearErrorNotice(); var formData = CPANEL.dom.get_data_from_form("mainform"); var domain = formData.domain; if (domain) { domain = domain.trim(); } var sslcrt = formData.crt; if (sslcrt) { sslcrt = sslcrt.trim(); } var callData = {}; if (known === "crt" && sslcrt && !SORRY_COMMA_REGEXP.test(sslcrt) ) { callData.crtdata = sslcrt; // WHM callData.certificate = sslcrt; // cpanel } else if (known === "domain" && domain) { callData.domain = domain; } else if (sslcrt && !SORRY_COMMA_REGEXP.test(sslcrt)) { callData.crtdata = sslcrt; // WHM callData.certificate = sslcrt; // cpanel known = "crt"; } else if (domain) { callData.domain = domain; known = "domain"; } else { return; } var api_req = { data: callData, callback: { success: sslInfoOk, failure: sslInfoFailure, argument: { known: known, exceptions: exceptions }, }, }; if (known === "domain") { // We only want one result. In the future // it might be nice to show the user all of // the results and have them pick, but for // now we just do the one. api_req.api_data = { paginate: { start: 0, size: 1 }, }; var all_domains = [ callData.domain ]; if ( callData.domain.substr(0, 1) !== "*" ) { var aliases; if (CPANEL.is_whm()) { aliases = PAGE.auto_domains; } else { aliases = PAGE.data.domain_aliases[ callData.domain ]; } aliases = aliases.map( function(ad) { return ad + "." + callData.domain; } ); all_domains.push.apply( all_domains, aliases ); } callData.domains = all_domains.join(","); delete callData.domain; } if ( CPANEL.is_whm() ) { api_req.func = (known === "domain") ? "fetch_ssl_certificates_for_fqdns" : "fetchsslinfo"; } else { api_req.version = 3; api_req.module = "SSL"; api_req.func = (known === "domain") ? "fetch_certificates_for_fqdns" : "fetch_key_and_cabundle_for_certificate"; } CPANEL.api( api_req ); if ( formProgressOverlay && formProgressOverlay.cfg ) { formProgressOverlay.destroy(); } showFormProgressOverlay(); } /** * Initialize the IP drop-down in WHM. * * @method _initializeIpSelector * @private */ var _initializeIpSelector = function() { var ips = PAGE.properties.sslips; var ip_options = PAGE.properties.ip_options; var cfg = YAHOO.lang.augmentObject( { maxResultsDisplayed: ip_options.length }, IP_COMBOBOX_CONFIG ); var ipcombo = new CPANEL.widgets.Combobox( DOM.get("sslip"), null, ip_options, cfg ); ipcombo.itemSelectEvent.subscribe(function(type, ACo) { var selected_ip = ACo[2][0]; if (!DOM.get("ssldomain").value && ips[selected_ip] && ips[selected_ip].sslhost) { if (ips[selected_ip].hasssl) { DOM.get("ssldomain").value = ips[selected_ip].sslhost; } else if (ips[selected_ip].iptype === "dedicated") { /* It might would be nice if we presented them with a popup of all the domains that the user owns and let them select which domain they would like to use */ } ssldomain_change_delayed(); } runValidation(); }); ipcombo.formatResult = function(oResultItem, sQuery) { var ip = oResultItem[0]; var ip_info; if (ips[ip].primary_ssl_servername) { if (ips[ip].primary_ssl_aliases && ips[ip].primary_ssl_aliases.length) { ip_info = LOCALE.maketext("SSL is installed; “[_1]” ([numerate,_2,alias,aliases] [list_and,_3]) is primary.", ips[ip].primary_ssl_servername, ips[ip].primary_ssl_aliases.length, ips[ip].primary_ssl_aliases.sort()); } else { ip_info = LOCALE.maketext("SSL is installed; “[_1]” is primary.", ips[ip].primary_ssl_servername); } } // TODO: Benchmark this with a large number of IPs. // It may need to be assembled without Handlebars for // the sake of users in slower environments. var sMarkup = ipSelectorItemTemplate( { ip: ip, hasssl: !!ips[ip].primary_ssl_servername, isShared: !!parseInt(ips[ip].is_shared_ip, 10), ipInfo_html: ip_info, } ); return sMarkup; }; }; /** * Validate the cert and update the UI. * * @param {object} opts An object to pass to updateUI(). */ var parse_and_do_cert_validate = function(opts) { updateUI( opts ); validators.cert.verify(); }; /** * Validate the cert and update the UI, but do this after a slight delay. * This is good for events like "onpaste" that don't immediately change * the input value (because they can still be canceled within the handler). * * @param {object} opts An object to pass to delayed_updateUI(). */ var delayed_parse_and_do_cert_validate = function(opts) { delayed_updateUI( opts ); window.setTimeout( validators.cert.verify.bind(validators.cert), 1 ); }; /** * Thin wrapper around delayed_parse_and_do_cert_validate() * that designates the active element as the domain input. */ var ssldomain_change_delayed = function() { delayed_parse_and_do_cert_validate( { active_element: "domain" } ); }; /** * Thin wrapper around parse_and_do_cert_validate() * that designates the active element as the domain input. */ var ssldomain_change = function() { parse_and_do_cert_validate( { active_element: "domain" } ); }; /** * Thin wrapper around the IP validation to delay it slightly. */ var delayed_ip_domain_validate = function() { window.setTimeout( function() { validators.ip.verify(); }, 1 ); }; /** * Thin wrapper around the key and CAB validation to delay it slightly. */ var delayed_key_cab_validate = function() { window.setTimeout( function() { validators.key.verify(); validators.cab.verify(); }, 1 ); }; /** * Thin wrapper around updateUI() to set the certificate input * as the active/changed element. */ var sslcrt_change = function() { updateUI( { active_element: "crt", }); }; /** * Thin wrapper around delayed_updateUI() to set the certificate input * as the active/changed element. */ var sslcrt_change_delayed = function() { delayed_updateUI( { active_element: "crt" } ); }; /** * Hide the warning, and don't spew if the warning isn't there. */ var _hide_wildcard_subdomain_warning = function() { if ( wildcard_subdomain_warning ) { wildcard_subdomain_warning.hide(); } }; /** * Maybe show the warning, maybe not, y'know? */ var _process_wildcard_subdomain_warning = function() { var formDomain = this.value.trim(); if (formDomain && /^\*\./.test(formDomain)) { var base_domain = formDomain.substr(2) || "domain.com"; var wildcard_domain = formDomain.substr(0, 2) + base_domain; var content = LOCALE.maketext("We recommend that users manage individual subdomains (e.g., “[_1]”, “[_2]”) instead of a single wildcard subdomain (e.g., “[_3]”).", "sample1." + base_domain, "sample2." + base_domain, wildcard_domain); if (!wildcard_subdomain_warning) { wildcard_subdomain_warning = new CPANEL.widgets.Page_Notice( { container: "wildcard_subdomain_warning", level: "warn", visible: false, /* To avoid animating. */ content: content, } ); } else { wildcard_subdomain_warning.cfg.setProperty("content", content); } wildcard_subdomain_warning.show(); } else { _hide_wildcard_subdomain_warning(); } }; /** * Initialize the page's "validators" hash * * @method _set_up_validators * @private */ var _set_up_validators = function() { if ( PAGE.ui_mode === "services" ) { validators.services = new CPANEL.validate.validator( LOCALE.maketext("Service") ); validators.services.add_for_submit( "service_to_install", _validate_services_selected, LOCALE.maketext("Choose a service.") ); validators.services.attach(); EVENT.on( DOM.get("mainform").service, "click", validators.services.verify.bind(validators.services) ); } else { validators.domain = new CPANEL.validate.validator( LOCALE.maketext("Domain") ); validators.domain.add_for_submit( "ssldomain", "min_length($input$,1)", LOCALE.maketext("Choose a domain.") ); validators.domain.add( "ssldomain", validateDomain, LOCALE.maketext("This is not a valid domain.") ); validators.domain.validateSuccess.subscribe(_process_wildcard_subdomain_warning, DOM.get("ssldomain"), true); validators.domain.validateFailure.subscribe(_hide_wildcard_subdomain_warning); validators.domain.attach(); if ( DOM.get("sslip") ) { validators.ip = new CPANEL.validate.validator( LOCALE.maketext("IP") ); validators.ip.add_for_submit( "sslip", validateIP, validateIP_fail_reason ); validators.ip.attach(); } } validators.cert = new CPANEL.validate.validator( LOCALE.maketext("Certificate") ); validators.cert.add_for_submit( "sslcrt", "min_length($input$.trim(),1)", LOCALE.maketext("Provide or retrieve a certificate.") ); validators.cert.add( "sslcrt", validateCert, LOCALE.maketext("The certificate is not valid.") ); if ( PAGE.ui_mode !== "services" ) { validators.cert.add( "sslcrt", validateDomainCertificateMatch, LOCALE.maketext("The certificate does not match your selected domain.") ); } validators.cert.attach(); validators.key = new CPANEL.validate.validator( LOCALE.maketext("Key") ); validators.key.add_for_submit( "sslkey", "min_length($input$.trim(),1)", LOCALE.maketext("Provide or retrieve a key.") ); validators.key.add( "sslkey", validateKey, LOCALE.maketext("The key is invalid.") ); validators.key.add( "sslkey", validateKeyCert, LOCALE.maketext("The key does not match the certificate.") ); validators.key.attach(); validators.cab = new CPANEL.validate.validator( LOCALE.maketext("Certificate Authority Bundle") ); validators.cab.add( "sslcab", validateCABundle, LOCALE.maketext("The CA bundle is invalid.") ); validators.cab.add( "sslcab", validateCABundleCert, LOCALE.maketext("The CA bundle does not match the certificate.") ); validators.cab.attach(); var submit_button = CPANEL.Y.one("#mainform input[type=submit], #mainform button[type=submit]"); CPANEL.validate.attach_to_form( submit_button.id, validators, { no_panel: true, success_callback: _send_install, } ); }; /** * Kick-start the install AJAX. * * @method _send_install * @private */ var _send_install = function() { var install_function = ( PAGE.ui_mode === "services" ) ? PAGE.sendInstall : sendApacheInstall; return install_function("mainform"); }; /** * Initialize the page's event listeners, including extra validation triggers. * This *MUST NOT* be called before _set_up_validators!! * * @method _set_up_listeners * @private */ var _set_up_listeners = function() { EVENT.on("fetch-cert", "click", function(e) { fetch_for_install_form("crt"); }); if ( DOM.get("fetch-domain") ) { EVENT.on("fetch-domain", "click", function(e) { fetch_for_install_form("domain"); }); } if ( PAGE.ui_mode !== "services" ) { if ( pageHasDomainSelector ) { EVENT.on("ssldomain", "change", ssldomain_change_delayed ); } else if (CPANEL.dom.has_oninput) { EVENT.on( "ssldomain", "input", ssldomain_change ); } else { EVENT.on( "ssldomain", "paste", ssldomain_change_delayed ); EVENT.on( "ssldomain", "keyup", ssldomain_change_delayed ); EVENT.on( "ssldomain", "change", ssldomain_change_delayed ); } } EVENT.on( "mainform", "reset", function() { delayed_updateUI(); window.setTimeout( runValidation, 1 ); } ); // Make key and CAB validation fire also if the cert changes. if (CPANEL.dom.has_oninput) { EVENT.on( "sslcrt", "input", validators.key.verify.bind(validators.key) ); EVENT.on( "sslcrt", "input", validators.cab.verify.bind(validators.cab) ); } else { EVENT.on( "sslcrt", "paste", delayed_key_cab_validate ); EVENT.on( "sslcrt", "keyup", delayed_key_cab_validate ); EVENT.on( "sslcrt", "change", delayed_key_cab_validate ); } if ( CPANEL.dom.has_oninput ) { EVENT.on( "sslcrt", "input", sslcrt_change ); } else { EVENT.on( "sslcrt", "paste", sslcrt_change_delayed ); EVENT.on( "sslcrt", "keyup", sslcrt_change ); EVENT.on( "sslcrt", "change", sslcrt_change ); } if ( DOM.get("sslip") ) { if (CPANEL.dom.has_oninput) { EVENT.on( "ssldomain", "input", validators.ip.verify.bind(validators.ip) ); } else { EVENT.on( "ssldomain", "paste", delayed_ip_domain_validate ); EVENT.on( "ssldomain", "keyup", delayed_ip_domain_validate ); EVENT.on( "ssldomain", "change", delayed_ip_domain_validate ); } } }; var make_ssl_browser_panel = function() { var new_panel = new YAHOO.widget.Dialog(DOM.generateId(), { fixedcenter: true, close: true, draggable: true, modal: true, postMethod: "none", buttons: [ { text: LOCALE.maketext("Use Certificate"), handler: function() { this.submit(); }, isDefault: true }, { text: LOCALE.maketext("Cancel"), classes: ["cancel"], handler: function() { this.cancel(); } }, ], visible: false, }); new_panel.setBody(""); DOM.addClass( new_panel.element, "ssl-results-panel" ); // Render the control into the element new_panel.render(document.body); return new_panel; }; /** * Initialize the page's event listeners, including extra validation triggers. * * @method _set_up_listeners * @private */ var _set_up_ssl_browser = function() { sslResultsPanel = make_ssl_browser_panel(); sslResultsPanel.beforeSubmitEvent.subscribe( handleBeforeSubmit, sslResultsPanel ); // Setup the current user on loading the page. if (PAGE && PAGE.properties) { current_user = PAGE.properties.selectedUser; } // Bug in YUI 2 Dialog: setBody has to happen before render. sslResultsPanel.setHeader(CPANEL.widgets.Dialog.applyDialogHeader(LOCALE.maketext("SSL Certificate List"))); var formTemplate = Handlebars.compile( DOM.get("browsessl_default_form").text ); var introBlurb; if ( CPANEL.is_cpanel() ) { introBlurb = LOCALE.maketext("Choose a certificate to install."); var limitationBlurb = LOCALE.maketext("Certificates that do not have a domain associated with your account are not listed here.") + " "; introBlurb += " " + limitationBlurb + " " + LOCALE.maketext("You can manage all of your saved certificates on the [output,url,_1,“Certificates” page].", "crts.html"); } else { introBlurb = LOCALE.maketext("Choose the account or Apache domain that contains the desired certificate to install. Then, select the certificate."); } sslResultsPanel.form.innerHTML = formTemplate( { introBlurb_html: introBlurb, } ); // NOTE: We do NOT call normalize_select_arrows() on the user drop-down // because of the disable/enable triggers on that element. // Users likely expect their platform's behavior here. // Fetch the template for the dialog if it exists var template = DOM.get("hiddenWhiteSpaceListTemplate"); if (template) { hiddenWhiteSpaceListTemplate = Handlebars.compile(template.text); } // This could also have gone into _set_up_listeners(). EVENT.on("sslbrowse", "click", function(e) { browsessl(this); }); EVENT.on( sslResultsPanel.form.users, "change", _onUserChange); // A radio button collection EVENT.on( sslResultsPanel.form.browse_source, "click", _onBrowseSourceChange ); }; function _populate_FQDN_TO_CREATED_DOMAIN() { for (var created in PAGE.data.domain_aliases) { FQDN_TO_CREATED_DOMAIN[created] = created; var aliases = PAGE.data.domain_aliases[created]; for (var a = 0; a < aliases.length; a++) { FQDN_TO_CREATED_DOMAIN[ aliases[a] + "." + created ] = created; } } } var _populate_domain_from_parsed_cert = function(certParse) { if (certParse) { var cert_domains = certParse.domains; // In WHM, the SSL domain is just a text input field, not a drop-down selector. if ( CPANEL.is_whm() ) { // Re-populate the field with the common name when a cert is selected. DOM.get("ssldomain").value = cert_domains[0]; } else { var form_data = CPANEL.dom.get_data_from_form("mainform"); var selected_domain = form_data.domain.trim(); var sel_dom_matches_cert = CPANEL.ssl.doesDomainMatchOneOf(selected_domain, cert_domains); if (!sel_dom_matches_cert) { var set_ok; // First, let’s see if we have any exact // matches for the subject.commonName. // This is good for wildcards but also // just in general. var created_domain = FQDN_TO_CREATED_DOMAIN[ cert_domains[0] ]; if (created_domain) { set_ok = CPANEL.dom.set_form_el_value( "ssldomain", created_domain ); } if (!set_ok) { MATCH_SEEK: for (var fqdn in FQDN_TO_CREATED_DOMAIN) { if (CPANEL.ssl.doesDomainMatchOneOf(fqdn, cert_domains)) { created_domain = FQDN_TO_CREATED_DOMAIN[fqdn]; if (CPANEL.dom.set_form_el_value( "ssldomain", created_domain )) { break; } } } } // "" //Reset domain <select> if this is a wildcard. } } } }; /** * Initialize the page * * @method initialize * @private */ var initialize = function() { if ( PAGE.ui_mode !== "services" ) { ( new CPANEL.widgets.Page_Notice( { visible: false, level: "info", // can also be "warn", "error", "success" content: DOM.get("ssl-install-require-template").text, } ) ).show(); (new CPANEL.widgets.Page_Notice( null, { visible: false, level: "info", content: LOCALE.maketext("To give website clients the best experience, ensure that each [asis,SSL] website’s certificate matches every domain on the website.") + "<br><br>" + LOCALE.maketext("When you install a valid certificate onto a website, the system also configures email, calendar, web disk, and [asis,cPanel]-related services to use that certificate for all of the website’s domains that match the certificate. Requests to these services from [asis,SNI]-enabled clients via the matching domains will receive the installed certificate.") + "<br><br>" + LOCALE.maketext("For more information, read our [output,url,_1,SSL Installation Workflow] documentation.", "https://go.cpanel.net/whmdocs66sslinstallworkflow"), } ) ).show(); } pageHasDomainSelector = !!CPANEL.Y.one("select#ssldomain"); if (pageHasDomainSelector) { CPANEL.dom.normalize_select_arrows("ssldomain"); } _set_up_validators(); _set_up_listeners(); _set_up_ssl_browser(); if ( DOM.get("sslip") ) { ipSelectorItemTemplate = Handlebars.compile( DOM.get("ipSelectorItemTemplate").text.trim() ); _initializeIpSelector(); } updateUI(); if ( DOM.get("sslcrt").value.trim() ) { if ( PAGE.ui_mode !== "services" ) { DOM.setStyle("fetch-cert", "display", ""); try { var certParse = CPANEL.ssl.parseCertificateText( DOM.get("sslcrt").value.trim() ); var certDomains = certParse.domains.slice(0); while ( certDomains[0] ) { if ( setDomain( certDomains.shift() ) ) { break; } } } catch (e) {} } runValidation(); } CPANEL.namespace( "CPANEL.sharedjs.sslinstall" ); YAHOO.lang.augmentObject( CPANEL.sharedjs.sslinstall, { domain_change_delayed: ssldomain_change_delayed, /* export this to the window for whm ip selector */ updateUI: updateUI, runValidation: runValidation, clearValidation: clearValidation, showFormProgressOverlay: showFormProgressOverlay, hideFormProgressOverlay: hideFormProgressOverlay, populate_form_with_ssl_components: populate_form_with_ssl_components, make_ssl_browser_panel: make_ssl_browser_panel, } ); if (pageHasDomainSelector) { _populate_FQDN_TO_CREATED_DOMAIN(); } }; CPANEL.nvdata.register( "certificates_table_sort", function() { return certificates_table_sort; } ); if ( CPANEL.is_whm() ) { CPANEL.nvdata.register( "browse_source", function() { return current_browse_source; } ); } window.fetch_for_install_form = fetch_for_install_form; // Register startup events. YAHOO.util.Event.onDOMReady(initialize); })(window);
Save