Viewing File: /usr/local/cpanel/base/frontend/jupiter/security/tls_wizard/services/CertificatesService.js
/*
* base/frontend/jupiter/security/tls_wizard/services/CertificatesService.js
* Copyright(c) 2020 cPanel, L.L.C.
* All rights reserved.
* copyright@cpanel.net http://cpanel.net
* This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global Promise: false */
/* jshint -W100 */
/* eslint-disable camelcase */
define(
[
"angular",
"lodash",
"cjt/util/locale",
"cjt/util/html",
"cjt/util/parse",
"cjt/io/api",
"cjt/io/uapi-request",
"app/views/Certificate",
"app/services/VirtualHost",
"cjt/io/uapi", // IMPORTANT: Load the driver so its ready
"cjt/services/alertService",
],
function(angular, _, LOCALE, cjt2Html, cjt2Parse, API, APIREQUEST) {
"use strict";
var ACTION_URL_LABELS = {
"evClickThroughStatus": LOCALE.maketext("Sign the Agreement"),
"ovCallbackStatus": LOCALE.maketext("Schedule a Call"),
DEFAULT: LOCALE.maketext("Complete this Now"),
};
var ACTION_URL_ICONS = {
"ovCallbackStatus": "fas fa-phone-square",
DEFAULT: "fas fa-external-link-alt",
};
var STATUS_DETAIL_STRINGS = {
"csrStatus": {
label: LOCALE.maketext("[output,abbr,CSR,Certificate Signing Request] Status:"),
inProgress: LOCALE.maketext("Validating the [output,abbr,CSR,Certificate Signing Request] status …"),
},
"dcvStatus": {
label: LOCALE.maketext("[output,abbr,DCV,Domain Control Validation] Status:"),
inProgress: LOCALE.maketext("Validating the [output,abbr,DCV,Domain Control Validation] status …"),
},
"evClickThroughStatus": {
label: LOCALE.maketext("[output,abbr,EV,Extended Validation] Click-Through Status:"),
inProgress: LOCALE.maketext("Validating the [output,abbr,EV,Extended Validation] click-through status …"),
},
"freeDVUPStatus": {
label: LOCALE.maketext("Free [output,abbr,DV,Domain Validated] Up Status:"),
inProgress: LOCALE.maketext("Validating the free [output,abbr,DV,Domain Validated] up status …"),
},
"organizationValidationStatus": {
label: LOCALE.maketext("[output,abbr,OV,Organization Validation] Status:"),
inProgress: LOCALE.maketext("Validating the [output,abbr,OV,Organization Validation] status …"),
},
"ovCallbackStatus": {
label: LOCALE.maketext("[output,abbr,OV,Organization Validation] Callback Status:"),
inProgress: LOCALE.maketext("Validating the [output,abbr,OV,Organization Validation] callback status …"),
},
"validationStatus": {
label: LOCALE.maketext("Validation Status:"),
inProgress: LOCALE.maketext("Checking the validation status …"),
},
};
function _isWildcard(domain) {
return /^\*/.test(domain);
}
// Curious that JS doesn’t expose sprintf(). Anyway.
// http://www.codigomanso.com/en/2010/07/simple-javascript-formatting-zero-padding/
function _sprintf02D(n) {
return ("0" + n).slice(-2);
}
var app;
try {
app = angular.module("App"); // For runtime
} catch (e) {
app = angular.module("App", ["cjt2.services.alert"]); // Fall-back for unit testing
}
function CertificatesServiceFactory(VirtualHost, Certificate, $q, $log, alertService) {
var CertificatesService = {};
var virtualHosts = [];
var allDomains = [];
var selectedDomains = [];
var products = [];
var orders = [];
var pendingCertificates = [];
var installedHosts = null;
var purchasingCerts = [];
var sslDomains = {};
var installedHostsMap = {};
var productsSearchOptions;
var wildcardMap = {};
// A lookup of “www.” domains. We don’t display these in the
// UI, but we want to know about them so we avoid trying to DCV them.
var wwwDomainsLookup = {};
var domainSearchOptions;
var currentDate = new Date();
var introductionDismissed = false;
function _apiError(whichAPI, errorMsgHTML) {
var error = LOCALE.maketext("The “[_1]” [asis,API] failed due to the following error: [_2]", _.escape(whichAPI), errorMsgHTML);
alertService.add({
type: "danger",
message: error,
group: "tlsWizard",
});
}
CertificatesService.add_new_certificate = function(cert) {
purchasingCerts.push(cert);
return purchasingCerts;
};
CertificatesService.get_purchasing_certs = function() {
return purchasingCerts;
};
CertificatesService.get_order_by_id = function(orderID) {
for (var i = 0; i < orders.length; i++) {
if (orders[i].order_id === orderID) {
return orders[i];
}
}
};
CertificatesService.add_order = function(order) {
var existingOrder = CertificatesService.get_order_by_id(order.order_id);
if (existingOrder) {
// update existing order
angular.extend(existingOrder, order);
} else {
// add orer
orders.push(order);
}
return orders;
};
CertificatesService.restore = function() {
if (CertificatesService.get_virtual_hosts().length) {
return false;
}
var storedSettings = _getStoredSettingsJSON();
if (!storedSettings) {
return false;
}
var storage = JSON.parse(storedSettings);
angular.forEach(storage.virtual_hosts, function(vhost) {
virtualHosts.push(new VirtualHost(vhost));
});
angular.forEach(storage.purchasing_certs, function(cert) {
CertificatesService.add_new_certificate(new Certificate(cert));
});
storage.orders = storage.orders ? storage.orders : [];
orders = storage.orders;
return virtualHosts.length === storage.virtual_hosts.length && orders.length === storage.orders.length;
};
CertificatesService.add_virtual_host = function(virtualHost, isSSL) {
var newVHost = new VirtualHost({
display_name: virtualHost,
is_ssl: isSSL,
});
var vhostID = virtualHosts.length;
virtualHosts.push(newVHost);
return vhostID;
};
CertificatesService.get_virtual_hosts = function() {
return virtualHosts;
};
CertificatesService.doesDomainMatchOneOf = function(domain, domains) {
if (domains === null || domain === null) {
return false;
}
return domains.some(function(domainOne) {
var domainTwo = domain;
if (domainOne === domainTwo) {
return true;
}
var possibleWildcard;
var domainToMatch;
if (_isWildcard(domainOne)) {
possibleWildcard = domainOne;
domainToMatch = domainTwo;
} else if (_isWildcard(domainTwo)) {
possibleWildcard = domainTwo;
domainToMatch = domainOne;
} else {
return false;
}
possibleWildcard = possibleWildcard.replace(/^\*\./, "");
domainToMatch = domainToMatch.replace(/^[^.]+\./, "");
if (possibleWildcard === domainToMatch) {
return true;
}
return false;
});
};
// for testing
CertificatesService._getWWWDomainsLookup = function() {
return wwwDomainsLookup;
};
CertificatesService.add_raw_domain = function(rawDomain) {
if (/^www\./.test(rawDomain.domain)) {
wwwDomainsLookup[ rawDomain.domain ] = true;
return;
}
rawDomain.virtual_host = rawDomain.vhost_name;
rawDomain.order_by_name = rawDomain.domain;
/* for consistency and ease of filtering */
rawDomain.is_wildcard = rawDomain.domain.indexOf("*.") === 0;
rawDomain.is_proxy = rawDomain.is_proxy && rawDomain.is_proxy.toString() === "1";
rawDomain.stripped_domain = rawDomain.domain;
CertificatesService.add_domain(rawDomain);
// Adding this check here, but should probably check to make sure these weren't manually created (in a later version)
var matchesAutoGenerated = rawDomain.domain.match(/^(mail|ipv6)\./);
if (!rawDomain.is_wildcard && !rawDomain.is_proxy && !matchesAutoGenerated) {
CertificatesService.add_domain(angular.extend({}, rawDomain, {
domain: "*." + rawDomain.domain,
is_wildcard: true,
}));
}
};
// for testing
CertificatesService._getWildcardMap = function() {
return wildcardMap;
};
CertificatesService.domain_covered_by_wildcard = function(domain) {
return wildcardMap[domain];
};
CertificatesService.compare_wildcard_domain = function(wildcardDomain, compareDomain) {
return wildcardMap[compareDomain] === wildcardDomain.domain;
};
/* map these for faster lookup */
CertificatesService.build_wildcard_map = function() {
wildcardMap = {};
var domains = CertificatesService.get_all_domains();
var re;
domains.forEach(function(domain) {
// only need to map wildcards
if (domain.is_wildcard === false) {
return false;
}
// The “stripped_domain” isn’t stripped in the case of
// wildcard domains that actually exist in Apache vhosts.
re = new RegExp("^[^\\.]+\\." + _.escapeRegExp(domain.stripped_domain.replace(/^\*\./, "")) + "$");
domains.forEach(function(matchDomain) {
if (domain.domain !== matchDomain.domain && re.test(matchDomain.domain)) {
wildcardMap[matchDomain.domain] = domain;
}
});
});
};
CertificatesService.get_domain_certificate_status = function(domain) {
var ihost = CertificatesService.get_domain_certificate(domain.domain);
if (ihost && ihost.certificate) {
var expirationDate = new Date(ihost.certificate.not_after * 1000);
var daysUntilExpiration = (expirationDate - currentDate) / 1000 / 60 / 60 / 24;
if (expirationDate < currentDate) {
return "expired";
} else if (daysUntilExpiration < 30 && daysUntilExpiration > 0) {
return "expiring_soon";
} else {
return "active";
}
}
return "unsecured";
};
CertificatesService._getSSLDomains = function() {
return sslDomains;
};
CertificatesService._getInstalledHostsMap = function() {
return installedHostsMap;
};
CertificatesService._getInstalledHosts = function() {
return installedHosts;
};
CertificatesService.add_domain = function(domainObject) {
var vhostID = CertificatesService.get_virtual_host_by_display_name(domainObject.virtual_host);
if (vhostID !== 0 && !vhostID) {
vhostID = CertificatesService.add_virtual_host(domainObject.virtual_host, 1);
}
virtualHosts[vhostID].is_ssl = 1;
/* prevent adding of duplicates */
if (CertificatesService.get_domain_by_domain(domainObject.domain)) {
return;
}
// assume installed hosts is there, we will ensure this later
sslDomains[domainObject.domain] = null;
// domain certificate finding
var ihost = installedHostsMap[domainObject.virtual_host];
if (ihost && ihost.certificate) {
// vhost has certificate, but does it cover this domain
angular.forEach(ihost.certificate.domains, function(domain) {
if (domainObject.domain === domain) {
sslDomains[domainObject.domain] = ihost;
return;
}
var wildcardDomain = domainObject.domain.replace(/^[^.]+\./, "*.");
if (wildcardDomain === domain) {
sslDomains[domainObject.domain] = ihost;
}
});
}
domainObject.type = domainObject.is_wildcard ? "wildcard_domain" : "main_domain";
domainObject.proxy_type = domainObject.is_proxy ? "proxy_domain" : "main_domain";
domainObject.certificate_status = CertificatesService.get_domain_certificate_status(domainObject);
return virtualHosts[vhostID].add_domain(domainObject);
};
// This function should potentially be renamed
// It actually just deselects all the domains in a specific VHost
CertificatesService.remove_virtual_host = function(displayName) {
var index = CertificatesService.get_virtual_host_by_display_name(displayName);
if (!_.isNil(index)) {
virtualHosts[index].remove_all_domains();
}
};
CertificatesService.get_virtual_host_by_display_name = function(displayName) {
for (var i = 0; i < virtualHosts.length; i++) {
if (virtualHosts[i].display_name === "*") {
/* There can be only one if we requested an all-vhosts install */
return 0;
} else if (virtualHosts[i].display_name === displayName) {
return i;
}
}
};
CertificatesService._runUAPI = function(apiCall) {
var deferred = $q.defer();
API.promise(apiCall.getRunArguments())
.done(function(response) {
response = response.parsedResponse;
if (response.status) {
deferred.resolve(response);
} else {
deferred.reject(response.error);
}
});
return deferred.promise;
};
function _dnsDcvPromise(dnsDcvDomainObjs) {
var apiCall = (new APIREQUEST.Class()).initialize(
"DCV",
"check_domains_via_dns",
{
domain: dnsDcvDomainObjs.map( function(d) {
return d.domain;
} ),
}
);
return CertificatesService._runUAPI(apiCall).then(
function(results) {
for (var d = 0; d < dnsDcvDomainObjs.length; d++) {
var domain = dnsDcvDomainObjs[d];
domain.resolving = false;
domain.dcvPassed.dns = cjt2Parse.parsePerlBoolean(results.data[d].succeeded);
if (domain.dcvPassed.dns) {
domain.resolved = 1;
} else {
// What to do here?? If HTTP passed but
// DNS fails, let’s assume that whatever
// DCV logic the CA does will fail.
domain.resolved = 0;
// TODO: Make it so we can inject
// the raw HTML.
if (domain.resolution_failure_reason) {
domain.resolution_failure_reason += " " + cjt2Html.decode( LOCALE.maketext("[asis,DNS]-based [output,abbr,DCV,Domain Control Validation] also failed.") );
} else {
domain.resolution_failure_reason = cjt2Html.decode( LOCALE.maketext("[asis,DNS]-based [output,abbr,DCV,Domain Control Validation] failed.") );
}
}
}
},
function(error) {
_apiError("DCV::check_domains_via_dns", error);
}
);
}
CertificatesService.set_confirmed_status_for_ssl_certificates = function(provider, order) {
var deferred = $q.defer();
var apiCall = new APIREQUEST.Class();
var orderItemIDs = [];
angular.forEach(order.certificates, function(item) {
orderItemIDs.push(item.order_item_id);
});
apiCall.initialize("Market", "set_status_of_pending_queue_items");
apiCall.addArgument("provider", provider);
apiCall.addArgument("status", "confirmed");
apiCall.addArgument("order_item_id", orderItemIDs);
API.promise(apiCall.getRunArguments())
.done(function(response) {
response = response.parsedResponse;
// This specific case is unusual because we want to
// give the error handler the entire object so that it
// can check for “data” in the response. See the
// documentation for Market::set_status_of_pending_queue_items.
var method = response.status ? "resolve" : "reject";
deferred[method](response);
});
return deferred.promise;
};
// Might return a promise, or it might return a boolean,
// which indicates that there’s no work to be done.
// (Could ideally do this with Promise.resolve()?)
CertificatesService.fetch_domains = function() {
var ret = CertificatesService.fetch_installed_hosts();
if (_.isFunction(ret.then) !== false) {
return ret.then(function() {
return CertificatesService.fetch_domains();
});
}
if (CPANEL.PAGE.domains) {
angular.forEach(CPANEL.PAGE.domains, function(domain) {
CertificatesService.add_raw_domain(domain);
});
if (CertificatesService.get_all_domains().length) {
return true;
}
}
var deferred = $q.defer();
var apiCall = new APIREQUEST.Class();
apiCall.initialize("WebVhosts", "list_ssl_capable_domains");
API.promise(apiCall.getRunArguments())
.done(function(response) {
response = response.parsedResponse;
if (response.status) {
deferred.resolve(response);
} else {
deferred.reject(response.error);
}
});
deferred.promise.then(function(result) {
angular.forEach(result.data, function(domain) {
CertificatesService.add_raw_domain(domain);
});
}, function(error) {
_apiError("WebVHosts::list_ssl_capable_domains", error);
});
return deferred.promise;
};
CertificatesService.get_store_login_url = function(provider, escapedURL) {
var deferred = $q.defer();
var apiCall = new APIREQUEST.Class();
apiCall.initialize("Market", "get_login_url");
apiCall.addArgument("provider", provider);
apiCall.addArgument("url_after_login", escapedURL);
API.promise(apiCall.getRunArguments())
.done(function(response) {
response = response.parsedResponse;
if (response.status) {
deferred.resolve(response);
} else {
deferred.reject(response.error);
}
});
return deferred.promise;
};
function _getStoredSettingsJSON() {
return localStorage.getItem("tls_wizard_data");
}
CertificatesService.store_settings = function(extras) {
var storableSettings = CertificatesService.get_storable_settings(extras);
localStorage.setItem("tls_wizard_data", storableSettings);
var retrievedData = _getStoredSettingsJSON();
return retrievedData === storableSettings;
};
CertificatesService.save = CertificatesService.store_settings;
// Returns at least an empty object.
CertificatesService.get_stored_extra_settings = function() {
var settings = _getStoredSettingsJSON();
if (settings) {
settings = JSON.parse(settings).extras;
}
return settings || {};
};
CertificatesService.clear_stored_settings = function() {
return localStorage.removeItem("tls_wizard_data");
};
CertificatesService.get_storable_settings = function(extras) {
// Preserve the “extras”, which contains things like
// identity verification for OV and EV certs.
//
var storage = _getStoredSettingsJSON();
storage = storage ? JSON.parse(storage) : {};
if (!storage.extras) {
storage.extras = {};
}
if (extras) {
_.assign(storage.extras, extras);
}
// Clobber everything else.
_.assign(storage, {
orders: orders,
// Used in the “Advanced” screen
// NB: Each one has a .toJSON() method defined.
virtual_hosts: virtualHosts,
// Used in the “Simple” screen
// NB: Each one has a .toJSON() method defined.
purchasing_certs: CertificatesService.get_purchasing_certs(),
});
return JSON.stringify(storage);
};
CertificatesService.get_all_domains = function() {
allDomains = [];
angular.forEach(virtualHosts, function(vhost) {
allDomains = allDomains.concat(vhost.get_domains());
});
return allDomains;
};
CertificatesService.get_all_selected_domains = function() {
selectedDomains = [];
angular.forEach(virtualHosts, function(vhost) {
selectedDomains = selectedDomains.concat(vhost.get_selected_domains());
});
return selectedDomains;
};
CertificatesService.get_products = function() {
return products;
};
CertificatesService.fetch_products = function() {
if (CertificatesService.get_products().length) {
return true;
}
if (CPANEL.PAGE.products) {
angular.forEach(CPANEL.PAGE.products, function(product) {
CertificatesService.add_raw_product(product);
});
if (CertificatesService.get_products().length) {
return true;
}
}
products = [];
var deferred = $q.defer();
var apiCall = new APIREQUEST.Class();
apiCall.initialize("Market", "get_all_products");
apiCall.addFilter("enabled", "eq", "1");
apiCall.addFilter("product_group", "eq", "ssl_certificate");
apiCall.addSorting("recommended", "dsc", "numeric");
apiCall.addSorting("x_price_per_domain", "asc", "numeric");
API.promise(apiCall.getRunArguments())
.done(function(response) {
response = response.parsedResponse;
if (response.status) {
deferred.resolve(response);
} else {
deferred.reject(response.error);
}
});
deferred.promise.then(function(results) {
angular.forEach(results.data, function(product) {
// typecasts
product.product_id += "";
["x_warn_after", "x_price_per_domain", "x_max_http_redirects"].forEach(function(attr) {
if (product[attr]) {
product[attr] = cjt2Parse.parseNumber(product[attr]);
}
});
CertificatesService.add_raw_product(product);
});
}, function(error) {
_apiError("Market::get_all_products", error);
});
return deferred.promise;
};
CertificatesService._make_certificate_term_label = function(termUnit, termValue) {
var unitStrings = {
"year": LOCALE.maketext("[quant,_1,Year,Years]", termValue),
"month": LOCALE.maketext("[quant,_1,Month,Months]", termValue),
"day": LOCALE.maketext("[quant,_1,Day,Days]", termValue),
};
return unitStrings[termUnit] || termValue + " " + termUnit;
};
CertificatesService._make_validation_type_label = function(validationType) {
var validationTypeLabels = {
"dv": LOCALE.maketext("[output,abbr,DV,Domain Validated] Certificate"),
"ov": LOCALE.maketext("[output,abbr,OV,Organization Validated] Certificate"),
"ev": LOCALE.maketext("[output,abbr,EV,Extended Validation] Certificate"),
};
return validationTypeLabels[validationType] || validationType;
};
CertificatesService.add_raw_product = function(rawProduct) {
rawProduct.id = rawProduct.product_id;
rawProduct.provider = rawProduct.provider_name;
rawProduct.provider_display_name = rawProduct.provider_display_name || rawProduct.provider;
rawProduct.price = Number(rawProduct.x_price_per_domain);
rawProduct.wildcard_price = Number(rawProduct.x_price_per_wildcard_domain);
rawProduct.wildcard_parent_domain_included = rawProduct.x_wildcard_parent_domain_free && rawProduct.x_wildcard_parent_domain_free.toString() === "1";
rawProduct.icon_mime_type = rawProduct.icon_mime_type ? rawProduct.icon_mime_type : "image/png";
rawProduct.is_wildcard = !isNaN(rawProduct.wildcard_price) ? true : false;
rawProduct.x_certificate_term = rawProduct.x_certificate_term || [1, "year"];
rawProduct.x_certificate_term_display_name = CertificatesService._make_certificate_term_label(rawProduct.x_certificate_term[1], rawProduct.x_certificate_term[0]);
rawProduct.x_certificate_term_key = rawProduct.x_certificate_term.join("_");
rawProduct.x_validation_type_display_name = CertificatesService._make_validation_type_label(rawProduct.x_validation_type);
rawProduct.x_supports_dns_dcv = cjt2Parse.parsePerlBoolean(rawProduct.x_supports_dns_dcv);
rawProduct.validity_period = rawProduct.x_certificate_term;
products.push(rawProduct);
};
CertificatesService.get_domain_search_options = function() {
if (domainSearchOptions) {
return domainSearchOptions;
}
domainSearchOptions = {
domainType: {
label: LOCALE.maketext("Domain Types:"),
item_key: "type",
options: [{
"value": "main_domain",
"label": LOCALE.maketext("Non-Wildcard"),
"description": LOCALE.maketext("Only list Non-Wildcard domains."),
}, {
"value": "wildcard_domain",
"label": LOCALE.maketext("Wildcard"),
"description": LOCALE.maketext("Only list Wildcard domains."),
}],
},
proxyDomainType: {
label: LOCALE.maketext("Service Subdomain Types:"),
item_key: "proxy_type",
options: [{
"value": "proxy_domain",
"label": LOCALE.maketext("[asis,cPanel] Service Subdomains"),
"description": LOCALE.maketext("Only list Service Subdomains."),
}, {
"value": "main_domain",
"label": LOCALE.maketext("Other Domains"),
"description": LOCALE.maketext("Only list non-Service Subdomains."),
}],
},
sslType: {
label: LOCALE.maketext("[asis,SSL] Types:"),
item_key: "certificate_type",
options: [{
"value": "unsecured",
"label": LOCALE.maketext("Unsecured or Self-signed"),
"description": LOCALE.maketext("Only list unsecured or self-signed domains."),
}, {
"value": "dv",
"label": CertificatesService._make_validation_type_label("dv"),
"description": LOCALE.maketext("Only list domains with [asis,DV] Certificates."),
}, {
"value": "ov",
"label": CertificatesService._make_validation_type_label("ov"),
"description": LOCALE.maketext("Only list domains with [asis,OV] Certificates."),
}, {
"value": "ev",
"label": CertificatesService._make_validation_type_label("ev"),
"description": LOCALE.maketext("Only list domains with [asis,EV] Certificates."),
}],
},
sslStatus: {
label: LOCALE.maketext("[asis,SSL] Statuses:"),
item_key: "certificate_status",
options: [{
"value": "unsecured",
"label": LOCALE.maketext("Unsecured"),
"description": LOCALE.maketext("Only list unsecured domains."),
}, {
"value": "active",
"label": LOCALE.maketext("Active"),
"description": LOCALE.maketext("Only list domains with an active certificate."),
}, {
"value": "expired",
"label": LOCALE.maketext("Expired"),
"description": LOCALE.maketext("Only list domains whose certificate is expiring soon."),
}, {
"value": "expiring_soon",
"label": LOCALE.maketext("Expiring Soon"),
"description": LOCALE.maketext("Only list domains with certificates that expire soon."),
}],
},
};
return CertificatesService.get_domain_search_options();
};
CertificatesService.get_product_search_options = function() {
if (productsSearchOptions) {
return productsSearchOptions;
}
productsSearchOptions = {
validationType: {
label: LOCALE.maketext("[asis,SSL] Validation Types"),
item_key: "x_validation_type",
options: [],
},
sslProvider: {
label: LOCALE.maketext("[asis,SSL] Providers"),
item_key: "provider",
options: [],
},
certTerms: {
label: LOCALE.maketext("Certificate Terms"),
item_key: "x_certificate_term_key",
options: [],
},
};
var products = CertificatesService.get_products();
var certTerms = {},
providers = {},
validationTypes = {};
angular.forEach(products, function(product) {
certTerms[product.x_certificate_term_key] = {
"value": product.x_certificate_term_key,
"label": product.x_certificate_term_display_name,
"description": LOCALE.maketext("Only list products with a term of ([_1]).", product.x_certificate_term_display_name),
};
providers[product.provider] = {
"value": product.provider,
"label": product.provider_display_name,
"description": LOCALE.maketext("Only list products from the “[_1]” provider.", product.provider_display_name),
};
validationTypes[product.x_validation_type] = {
"value": product.x_validation_type,
"label": product.x_validation_type_display_name,
"description": LOCALE.maketext("Only list products that use the “[_1]” validation type.", product.x_validation_type_display_name),
};
});
angular.forEach(certTerms, function(item) {
productsSearchOptions.certTerms.options.push(item);
});
angular.forEach(providers, function(item) {
productsSearchOptions.sslProvider.options.push(item);
});
angular.forEach(validationTypes, function(item) {
productsSearchOptions.validationType.options.push(item);
});
for (var key in productsSearchOptions) {
if (productsSearchOptions.hasOwnProperty(key)) {
if (productsSearchOptions[key].options.length <= 1) {
delete productsSearchOptions[key];
}
}
}
return CertificatesService.get_product_search_options();
};
CertificatesService.get_product_by_id = function(providerName, productID) {
for (var i = 0; i < products.length; i++) {
if (products[i].id === productID && products[i].provider === providerName) {
return products[i];
}
}
return;
};
var _ensureDomainCanPassDCV = function(domains, dcvConstraints) {
// A lookup of objects for domains that will be DCVed
// in this function. (The list will exclude, e.g., domains
// that are already DCVed.) Do not confuse with
// dnsDcvDomainObjs, which is specific to DNS DCV.
var allDcvDomainObjs = {};
var httpDcvDomainNames = [];
// A lookup of objects for domains that will be DCVed
// via DNS in this function. Do not confuse with
// allDcvDomainObjs, which includes domains that will not
// undergo DNS DCV (e.g., because they passed HTTP DCV).
var dnsDcvDomainObjs = {};
angular.forEach(domains, function(domain) {
if (domain.resolved === -1) {
allDcvDomainObjs[domain.domain] = domain;
if (_isWildcard(domain.domain)) {
dnsDcvDomainObjs[domain.domain] = domain;
} else {
httpDcvDomainNames.push(domain.domain);
}
domain.dcvPassed = {};
domain.resolving = true;
}
});
if (Object.keys(allDcvDomainObjs).length === 0) {
return;
}
var httpPromise;
if (httpDcvDomainNames.length) {
var productForbidsRedirects = function(p) {
// Compare against 0 to accommodate providers that
// don’t define this particular product attribute.
return 0 === p.x_max_http_redirects;
};
var apiCall = (new APIREQUEST.Class()).initialize(
"DCV",
"check_domains_via_http", {
domain: httpDcvDomainNames,
dcv_file_allowed_characters: JSON.stringify(dcvConstraints.dcv_file_allowed_characters),
dcv_file_random_character_count: dcvConstraints.dcv_file_random_character_count,
dcv_file_extension: dcvConstraints.dcv_file_extension,
dcv_file_relative_path: dcvConstraints.dcv_file_relative_path,
dcv_user_agent_string: dcvConstraints.dcv_user_agent_string,
}
);
// TODO: Currently we always fall back to DNS DCV
// if there are any HTTP redirects, even if the number
// of redirects is within every product’s redirection
// limit. Ideally we shouldn’t fall back to DNS DCV
// in that case; as a practical matter, though, as of
// 2021 cPStore remains the only provider we have ever
// used, and cPStore (i.e., Sectigo) forbids all HTTP
// redirects, so our current implementation happens to
// be correct for now (and the foreseeable future).
//
var prodsThatForbidRedirects = products.filter(productForbidsRedirects);
httpPromise = CertificatesService._runUAPI(apiCall).then(
function(results) {
for (var d = 0; d < httpDcvDomainNames.length; d++) {
var domainName = httpDcvDomainNames[d];
var domain = allDcvDomainObjs[domainName];
domain.resolution_failure_reason = results.data[d].failure_reason;
domain.redirects_count = cjt2Parse.parseNumber(results.data[d].redirects_count);
// Success with redirects likely means that even
// rebuilding .htaccess didn’t fix the issue,
// so the customer will need to investigate manually.
if (domain.redirects_count && !domain.resolution_failure_reason) {
if (prodsThatForbidRedirects.length) {
var message = LOCALE.maketext("“[_1]”’s [output,abbr,DCV,Domain Control Validation] check completed correctly, but the check required an [asis,HTTP] redirection. The system tried to exclude such redirections from this domain by editing the website document root’s “[_2]” file, but the redirection persists. You should investigate further.", _.escape(domain.domain), ".htaccess");
alertService.add({
type: "danger",
message: message,
group: "tlsWizard",
});
}
}
domain.dcvPassed.http = !domain.resolution_failure_reason;
// Send this batch of domains to DNS DCV if the
// current domain’s DCV failed or redirected.
if (!domain.dcvPassed.http || domain.redirects_count) {
dnsDcvDomainObjs[domain.domain] = domain;
} else {
domain.resolved = domain.dcvPassed.http ? 1 : 0;
}
}
},
function(error) {
_apiError("DCV::check_domains_via_http", error);
}
);
} else {
httpPromise = Promise.resolve();
}
return httpPromise.then(
function() {
var domainObjs = Object.values(dnsDcvDomainObjs);
if (domainObjs.length) {
return _dnsDcvPromise(domainObjs);
}
}
).finally( function() {
Object.values(allDcvDomainObjs).forEach( function(domainObj) {
domainObj.resolving = false;
} );
} );
};
CertificatesService.get_default_provider_name = function() {
var product;
var products = CertificatesService.get_products();
/* if it's set, use that */
var cpStoreProducts = products.filter(function(product) {
if (product.provider_name === "cPStore") {
return true;
}
return false;
});
if (cpStoreProducts.length) {
/* if cPStore exists, use that */
product = cpStoreProducts[0];
} else {
/* otherwise use first */
product = products[0];
}
return product.provider_name;
};
CertificatesService.get_provider_specific_dcv_constraints = function(providerName) {
var apiCall = (new APIREQUEST.Class()).initialize(
"Market",
"get_provider_specific_dcv_constraints", {
provider: providerName,
}
);
return CertificatesService._runUAPI(apiCall);
};
CertificatesService.ensure_domains_can_pass_dcv = function(domains, providerName) {
return CertificatesService.get_provider_specific_dcv_constraints(providerName).then(function(results) {
return _ensureDomainCanPassDCV(domains, results.data);
}, function(error) {
_apiError("Market::get_provider_specific_dcv_constraints", error);
});
};
CertificatesService.verify_login_token = function(provider, loginToken, urlAfterLogin) {
var deferred = $q.defer();
var apiCall = new APIREQUEST.Class();
apiCall.initialize("Market", "validate_login_token");
apiCall.addArgument("login_token", loginToken);
apiCall.addArgument("url_after_login", urlAfterLogin);
apiCall.addArgument("provider", provider);
API.promise(apiCall.getRunArguments())
.done(function(response) {
response = response.parsedResponse;
if (response.status) {
deferred.resolve(response);
} else {
deferred.reject(response.error);
}
});
return deferred.promise;
};
CertificatesService.set_url_after_checkout = function(provider, accessToken, orderID, urlAfterCheckout) {
var deferred = $q.defer();
var apiCall = new APIREQUEST.Class();
apiCall.initialize("Market", "set_url_after_checkout");
apiCall.addArgument("provider", provider);
apiCall.addArgument("access_token", accessToken);
apiCall.addArgument("order_id", orderID);
apiCall.addArgument("url_after_checkout", urlAfterCheckout);
API.promise(apiCall.getRunArguments())
.done(function(response) {
response = response.parsedResponse;
// This specific case is unusual because we want to
// give the error handler the entire object so that it
// can check for “data” in the response. See the
// documentation for Market::set_url_after_checkout.
var method = response.status ? "resolve" : "reject";
deferred[method](response);
});
return deferred.promise;
};
// Returns a YYYY-MM-DD string
//
// AngularJS sets all date models as Date objects,
// so we convert those to YYYY-MM-DD for the order.
// It’s a bit hairy because we can’t use
// .toISOString() since that date will be UTC, while
// the numbers we want are the ones the user gave.
function _dateToYYYYMMDD(theDate) {
return [
theDate.getFullYear(),
_sprintf02D(1 + theDate.getMonth()),
_sprintf02D(theDate.getDate()),
].join("-");
}
var _requestCertificates = function(provider, accessToken, certificates, urlAfterCheckout) {
var deferred = $q.defer();
var apiCall = new APIREQUEST.Class();
apiCall.initialize("Market", "request_ssl_certificates");
apiCall.addArgument("provider", provider);
apiCall.addArgument("access_token", accessToken);
apiCall.addArgument("url_after_checkout", urlAfterCheckout);
var jsonCertificates = certificates.map(function(cert) {
var newCertificate = {
product_id: cert.get_product().id,
subject_names: cert.get_subject_names(),
vhost_names: cert.get_virtual_hosts(),
price: cert.get_price(),
validity_period: cert.get_validity_period(),
};
if (cert.get_product().x_identity_verification) {
var identityVerification = cert.get_identity_verification();
newCertificate.identity_verification = {};
cert.get_product().x_identity_verification.forEach(function(idv) {
var k = idv.name;
// If the form didn’t give us any data for it,
// then don’t submit it.
if (!identityVerification[k]) {
return;
}
// “date” items come from AngularJS as Date objects,
// but they come from JSON as ISO 8601 strings.
if (idv.type === "date") {
var dateObject;
try {
dateObject = new Date(identityVerification[k]);
} catch (e) {
$log.warn("new Date() failed; ignoring", identityVerification[k], e);
}
if (dateObject) {
newCertificate.identity_verification[k] = _dateToYYYYMMDD(dateObject);
}
} else {
newCertificate.identity_verification[k] = identityVerification[k];
}
});
}
// A lookup map of the wildcard subject names.
var wildcardDomainMap = {};
newCertificate.subject_names.forEach(function(subject_name) {
var domain = subject_name.name;
if (domain.indexOf("*.") === 0) {
wildcardDomainMap[domain] = true;
}
});
// An array of objects that describe subject name
// entries to add for www. subdomains.
var validWWWDomains = [];
newCertificate.subject_names.forEach(function(subject_name) {
var domain = subject_name.name;
// Don’t add www. if we already have the wildcard
// for the domain. For example, if the cert will
// secure foo.com and *.foo.com, there’s no need
// for www.foo.com, so we leave it off.
var addWwwYn = !wildcardDomainMap["*." + domain];
// Only add www. if that domain actually exists.
addWwwYn = addWwwYn && wwwDomainsLookup["www." + domain];
if ( addWwwYn ) {
validWWWDomains.push( {
type: "dNSName",
name: "www." + domain,
dcv_method: subject_name.dcv_method,
});
}
});
newCertificate.subject_names = newCertificate.subject_names.concat(validWWWDomains);
return JSON.stringify(newCertificate);
});
apiCall.addArgument("certificate", jsonCertificates);
API.promise(apiCall.getRunArguments())
.done(function(response) {
response = response.parsedResponse;
if (response.status) {
deferred.resolve(response);
} else {
deferred.reject(response.error);
}
});
deferred.promise.catch(CertificatesService.reset.bind(CertificatesService));
return deferred.promise;
};
CertificatesService.request_certificates = function(provider, accessToken, certificates, urlAfterCheckout) {
// This now requires that wwwDomainsLookup be populated.
// We ensure that by calling fetch_domains().
var domains_fetch = CertificatesService.fetch_domains();
var callback = function() {
return _requestCertificates(provider, accessToken, certificates, urlAfterCheckout);
};
if (domains_fetch.then) {
return domains_fetch.then(callback);
}
return callback();
};
CertificatesService.get_pending_certificates = function() {
return pendingCertificates;
};
var _assignPendingCertificates = function(newPending) {
pendingCertificates = newPending;
pendingCertificates.forEach(function(pcert) {
// Typecasts
pcert.order_id += "";
pcert.order_item_id += "";
pcert.product_id += "";
});
};
CertificatesService.fetch_pending_certificates = function() {
if (CPANEL.PAGE.pending_certificates) {
_assignPendingCertificates(CPANEL.PAGE.pending_certificates);
/* if exists on page load use it, but if view switching, we want to reload, so clear this variable */
CPANEL.PAGE.pending_certificates = null;
if (pendingCertificates.length) {
return true;
}
}
var deferred = $q.defer();
var apiCall = new APIREQUEST.Class();
apiCall.initialize("Market", "get_pending_ssl_certificates");
API.promise(apiCall.getRunArguments())
.done(function(response) {
response = response.parsedResponse;
if (response.status) {
deferred.resolve(response);
} else {
deferred.reject(response.error);
}
});
deferred.promise.then(function(result) {
_assignPendingCertificates(result.data);
}, function(error) {
_apiError("Market::pending_certificates", error);
});
return deferred.promise;
};
CertificatesService.add_raw_installed_host = function(ihost) {
if (!installedHosts) {
installedHosts = [];
}
ihost.certificate.is_self_signed = parseInt(ihost.certificate.is_self_signed, 10) === 1;
installedHosts.push(ihost);
installedHostsMap[ihost.servername] = ihost;
};
CertificatesService.get_domain_certificate = function(domain) {
return sslDomains[domain];
};
CertificatesService.get_domain_by_domain = function(domain) {
var domains = CertificatesService.get_all_domains();
for (var i = 0; i < domains.length; i++) {
if (domains[i].domain === domain) {
return domains[i];
}
}
return;
};
CertificatesService.get_virtual_host_certificate = function(virtualHost) {
if (!installedHosts) {
return;
}
for (var i = 0; i < installedHosts.length; i++) {
if (installedHosts[i].servername === virtualHost.display_name) {
return installedHosts[i];
}
}
return installedHosts[0] ? installedHosts[0] : undefined;
};
CertificatesService.fetch_installed_hosts = function() {
if (installedHosts) {
return true;
}
if (CPANEL.PAGE.installed_hosts) {
if (!CPANEL.PAGE.installed_hosts.length) {
return true; /* Defined, but no installed hosts */
}
installedHosts = [];
installedHostsMap = {};
sslDomains = {};
angular.forEach(CPANEL.PAGE.installed_hosts, function(ihost) {
CertificatesService.add_raw_installed_host(ihost);
});
if (installedHosts.length) {
return true;
}
}
var deferred = $q.defer();
var apiCall = new APIREQUEST.Class();
apiCall.initialize("SSL", "installed_hosts");
API.promise(apiCall.getRunArguments())
.done(function(response) {
response = response.parsedResponse;
if (response.status) {
deferred.resolve(response);
} else {
deferred.reject(response.error);
}
});
deferred.promise.then(function(result) {
installedHosts = [];
installedHostsMap = {};
sslDomains = {};
angular.forEach(result.data, function(ihost) {
CertificatesService.add_raw_installed_host(ihost);
});
}, function(error) {
_apiError("SSL::installed_hosts", error);
});
return deferred.promise;
};
var _makeBatch = function(calls) {
var apiCall = new APIREQUEST.Class();
apiCall.initialize("Batch", "strict");
apiCall.addArgument("command", calls.map(JSON.stringify, JSON));
return apiCall;
};
CertificatesService.install_certificate = function(cert, vhostNames) {
var apiCall = _makeBatch(vhostNames.map(function(vh) {
return [
"SSL",
"install_ssl", {
cert: cert,
domain: vh,
},
];
}));
return CertificatesService._runUAPI(apiCall);
};
CertificatesService.get_ssl_certificate_if_available = function(provider, orderItemID) {
var apiCall = new APIREQUEST.Class();
apiCall.initialize("Market", "get_ssl_certificate_if_available");
apiCall.addArgument("provider", provider);
apiCall.addArgument("order_item_id", orderItemID);
return CertificatesService._runUAPI(apiCall);
};
CertificatesService.get_installed_ssl_for_domain = function(domain) {
var apiCall = new APIREQUEST.Class();
apiCall.initialize("SSL", "installed_host");
apiCall.addArgument("domain", domain);
return CertificatesService._runUAPI(apiCall);
};
CertificatesService.cancel_pending_ssl_certificate_and_poll = function(provider, orderItemID) {
var apiCall = _makeBatch([
[
"Market",
"cancel_pending_ssl_certificate", {
provider: provider,
order_item_id: orderItemID,
},
],
[
"Market",
"get_ssl_certificate_if_available", {
provider: provider,
order_item_id: orderItemID,
},
],
]);
return CertificatesService._runUAPI(apiCall);
};
CertificatesService.cancel_pending_ssl_certificates = function(provider, orderItemIDs) {
var apiCall = _makeBatch(orderItemIDs.map(function(oiid) {
return [
"Market",
"cancel_pending_ssl_certificate", {
provider: provider,
order_item_id: oiid,
},
];
}));
return CertificatesService._runUAPI(apiCall);
};
CertificatesService.cancel_certificate = function(virtualHost, provider, orderItemID) {
CertificatesService.cancel_pending_ssl_certificate(provider, orderItemID).then(function() {
angular.forEach(virtualHost.get_selected_domains(), function(domain) {
domain.selected = false;
});
});
};
CertificatesService.process_ssl_pending_queue = function() {
var deferred = $q.defer();
var apiCall = new APIREQUEST.Class();
apiCall.initialize("Market", "process_ssl_pending_queue");
API.promise(apiCall.getRunArguments())
.done(function(response) {
response = response.parsedResponse;
if (response.status) {
deferred.resolve(response);
} else {
deferred.reject(response.error);
}
});
return deferred.promise;
};
CertificatesService.hard_reset = function() {
CertificatesService.reset();
CPANEL.PAGE.domains = null;
};
CertificatesService.reset = function() {
virtualHosts = [];
allDomains = [];
products = [];
installedHosts = null;
purchasingCerts = [];
sslDomains = {};
orders = [];
wildcardMap = {};
};
CertificatesService.reset_purchasing_certificates = function() {
purchasingCerts = [];
};
CertificatesService.dismiss_introduction = function() {
introductionDismissed = true;
};
CertificatesService.show_introduction_block = function() {
return !introductionDismissed && !alertService.getAlerts().length;
};
CertificatesService.parseCertificateDomainDetails = function(rawDomainDetails) {
var domainDetails = {};
angular.forEach(rawDomainDetails, function(value) {
domainDetails[value.domain] = value.status;
});
return domainDetails;
};
CertificatesService.parseCertificateStatusDetails = function(rawStatusDetails, rawActionUrls) {
var statusDetails = [];
if (!rawStatusDetails) {
return statusDetails;
}
rawActionUrls = rawActionUrls ? rawActionUrls : {};
angular.forEach(rawStatusDetails, function(detail, key) {
var detailString = STATUS_DETAIL_STRINGS[key];
if (!detailString) {
detailString = {
label: key,
inProgress: "",
};
}
if (detail === "not-applicable" || key === "certificateStatus" || key === "csrStatus") {
return;
}
if (detail) {
var status;
if (detail === "not-completed") {
status = detailString.inProgress;
} else if (detail === "completed") {
status = LOCALE.maketext("Complete.");
} else {
status = detail;
}
var detailItem = {
label: detailString.label,
status: status,
rawLabel: key,
rawStatus: detail,
};
if (rawActionUrls[key]) {
detailItem.actionLabel = ACTION_URL_LABELS[key] || ACTION_URL_LABELS.DEFAULT;
detailItem.actionURL = rawActionUrls[key];
detailItem.actionIcon = ACTION_URL_ICONS[key] || ACTION_URL_ICONS.DEFAULT;
}
statusDetails.push(detailItem);
}
});
return statusDetails;
};
CertificatesService.getCertificateStatusDetails = function(provider, orderItemID) {
var apiCall = new APIREQUEST.Class();
apiCall.initialize("Market", "get_certificate_status_details", {
"provider": provider,
"order_item_id": orderItemID,
});
return CertificatesService._runUAPI(apiCall).then(function(result) {
return {
statusDetails: CertificatesService.parseCertificateStatusDetails(result.data.status_details, result.data.action_urls),
domainDetails: CertificatesService.parseCertificateDomainDetails(result.data.domain_details),
};
});
};
return CertificatesService;
}
return app.factory("CertificatesService", ["VirtualHost", "Certificate", "$q", "$log", "alertService", CertificatesServiceFactory]);
});
Back to Directory
File Manager