CLOUDSTACK-6695: Added support to the UI for uploading a chain of certificates

In the "SSL Certificate" dialog we added:
- new field for the root certificate;
- a button to add intermediate certificates if necessary; when this is pressed, a new field, called "Intermediate certificate 1" is added; pressed again, "Intermediate certificate 2" field is added, and so on.

We upload the certificates in order: first the root certificate (with id=1), then the intermediate certificates (with id=2,3,..) and finally the server certificate.
When uploading a certificate, we wait for the upload to be completed successfully and only then we proceed to uploading the next one. If one fails, we report failure and don't continue with the remaining.

Signed-off-by: Mihaela Stoica <mihaela.stoica@citrix.com>
This commit is contained in:
Mihaela Stoica 2014-08-05 21:59:15 +01:00 committed by Jessica Wang
parent ffe0f2f60f
commit 57f611df16
4 changed files with 168 additions and 51 deletions

View File

@ -313,6 +313,7 @@ label.add.firewall=Add firewall rule
label.add.guest.network=Add guest network
label.add.host=Add Host
label.add.ingress.rule=Add Ingress Rule
label.add.intermediate.certificate=Add intermediate certificate
label.add.ip.range=Add IP Range
label.add.load.balancer=Add Load Balancer
label.add.more=Add More
@ -431,7 +432,7 @@ label.cancel=Cancel
label.capacity=Capacity
label.capacity.bytes=Capacity Bytes
label.capacity.iops=Capacity IOPS
label.certificate=Certificate
label.certificate=Server certificate
label.change.service.offering=Change service offering
label.change.value=Change value
label.character=Character
@ -678,6 +679,7 @@ label.instance.limits=Instance Limits
label.instance.name=Instance Name
label.instance=Instance
label.instances=Instances
label.intermediate.certificate=Intermediate certificate {0}
label.internal.dns.1=Internal DNS 1
label.internal.dns.2=Internal DNS 2
label.internal.name=Internal name
@ -1020,6 +1022,7 @@ label.retry.interval=Retry Interval
label.review=Review
label.revoke.project.invite=Revoke invitation
label.role=Role
label.root.certificate=Root certificate
label.root.disk.controller=Root disk controller
label.root.disk.offering=Root Disk Offering
label.round.robin=Round-robin
@ -1820,7 +1823,7 @@ message.tooltip.reserved.system.netmask=The network prefix that defines the pod
message.tooltip.zone.name=A name for the zone.
message.update.os.preference=Please choose a OS preference for this host. All virtual instances with similar preferences will be first allocated to this host before choosing another.
message.update.resource.count=Please confirm that you want to update resource counts for this account.
message.update.ssl=Please submit a new X.509 compliant SSL certificate to be updated to each console proxy and secondary storage virtual instance\:
message.update.ssl=Please submit a new X.509 compliant SSL certificate chain to be updated to each console proxy and secondary storage virtual instance\:
message.validate.instance.name=Instance name can not be longer than 63 characters. Only ASCII letters a~z, A~Z, digits 0~9, hyphen are allowed. Must start with a letter and end with a letter or a digit.
message.virtual.network.desc=A dedicated virtualized network for your account. The broadcast domain is contained within a VLAN and all public network access is routed out by a virtual router.
message.vm.create.template.confirm=Create Template will reboot the VM automatically.

View File

@ -4053,7 +4053,7 @@ Dialogs*/
display: inline-block;
}
.ui-dialog div.form-container div.value input {
.ui-dialog div.form-container div.value input, textarea {
width: 98%;
font-size: 14px;
padding: 4px;
@ -12984,3 +12984,21 @@ div.gpugroups div.list-view {
padding: 12px 12px 5px;
}
.ui-dialog .ui-button.add {
background: linear-gradient(to bottom, #f7f7f7 1%, #eaeaea 100%) repeat scroll 0px 0px transparent;
font-size: 12px;
height: 12px;
width: auto;
margin: 0px 0px 12px;
padding: 5px 7px 5px 6px;
}
.ui-dialog .ui-button.add:hover {
background: none repeat scroll 0px 0px #e5e5e5;
box-shadow: 0px 0px 5px #c3c3c3 inset;
}
.ui-dialog .ui-button.add span {
background: url("../images/icons.png") no-repeat scroll -626px -209px transparent;
padding: 0 0 3px 18px;
}

View File

@ -1898,7 +1898,10 @@ dictionary = {
'message.confirm.enable.vpc.offering': '<fmt:message key="message.confirm.enable.vpc.offering" />',
'message.enabling.vpc.offering': '<fmt:message key="message.enabling.vpc.offering" />',
'message.confirm.remove.vpc.offering': '<fmt:message key="message.confirm.remove.vpc.offering" />',
'message.confirm.disable.vpc.offering': '<fmt:message key="message.confirm.disable.vpc.offering" />'
'message.confirm.disable.vpc.offering': '<fmt:message key="message.confirm.disable.vpc.offering" />',
'label.root.certificate': '<fmt:message key="label.root.certificate" />',
'label.intermediate.certificate': '<fmt:message key="label.intermediate.certificate" />',
'label.add.intermediate.certificate': '<fmt:message key="label.add.intermediate.certificate" />'
};
</script>

View File

@ -96,31 +96,111 @@
form: {
title: 'label.update.ssl',
desc: 'message.update.ssl',
preFilter: function (args) {
var $form = args.$form;
// insert the "Add intermediate certificate" button
var $addButton = $('<div>')
.addClass('add ui-button')
.append(
$('<span>').html(_l('label.add.intermediate.certificate'))
);
var $servercertificate = $form.find('.form-item[rel=certificate]');
$addButton.insertBefore($servercertificate);
var count = 0;
var $intermediatecertificate = $form.find('.form-item[rel=intermediatecertificate]');
$addButton.click(function() {
// clone the template intermediate certificate and make it visible
var $newcertificate = $intermediatecertificate.clone().attr('id','intermediate'+count);
$newcertificate.insertBefore($addButton);
$newcertificate.css('display', 'inline-block');
$newcertificate.addClass('sslcertificate');
count++;
// change label
var $label = $newcertificate.find('label');
$label.html($label.html().replace('{0}', count)); // 'Intermediate certificate ' + count + ':'
});
},
fields: {
rootcertificate: {
label: 'label.root.certificate',
isTextarea: true,
validation: { required: true }
},
intermediatecertificate: { // this is the template 'intermediate certificate', always hidden
label: 'label.intermediate.certificate',
isTextarea: true,
isHidden: true
},
certificate: {
label: 'label.certificate',
isTextarea: true
isTextarea: true,
validation: { required: true }
},
privatekey: {
label: 'label.privatekey',
isTextarea: true
isTextarea: true,
validation: { required: true }
},
domainsuffix: {
label: 'label.domain.suffix'
label: 'label.domain.suffix',
validation: { required: true }
}
}
},
after: function(args) {
var $loading = $('<div>').addClass('loading-overlay');
$('.system-dashboard-view:visible').prepend($loading);
// build a list with all certificates that need to be uploaded
var certificates = [];
certificates.push(args.data.rootcertificate);
if ($.isArray(args.data.intermediatecertificate))
{
$.merge(certificates, args.data.intermediatecertificate);
}
else
{
certificates.push(args.data.intermediatecertificate);
}
certificates.push(args.data.certificate);
// Recursively uploads certificates.
// When the upload succeeds, proceeds to uploading the next certificate.
// When the upload fails, stops and reports failure.
var uploadCertificate = function(index) {
if (index >= certificates.length)
{
return;
}
if ( !$.trim(certificates[index])) // skip empty certificate
{
uploadCertificate(index + 1);
return;
}
// build certificate data
var certificateData = {
id: index + 1, // id start from 1
certificate: certificates[index],
domainsuffix: args.data.domainsuffix
};
switch (index) {
case (0): //first certificate is the root certificate
certificateData['name'] = 'root';
break;
case (certificates.length - 1): // last certificate is the server certificate
certificateData['privatekey'] = args.data.privatekey;
break;
default: // intermediate certificates
certificateData['name'] = 'intermediate' + index;
}
$.ajax({
type: "POST",
url: createURL('uploadCustomCertificate'),
data: {
certificate: args.data.certificate,
privatekey: args.data.privatekey,
domainsuffix: args.data.domainsuffix
},
data: certificateData,
dataType: 'json',
success: function(json) {
var jid = json.uploadcustomcertificateresponse.jobid;
@ -135,16 +215,24 @@
} else {
clearInterval(uploadCustomCertificateIntervalID);
if (result.jobstatus == 1) {
if (index == certificates.length - 1) // last one, report success
{
cloudStack.dialog.notice({
message: 'Update SSL Certificate succeeded'
message: 'Update SSL Certificates succeeded'
});
$loading.remove();
}
else // upload next certificate
{
uploadCertificate(index + 1);
}
} else if (result.jobstatus == 2) {
cloudStack.dialog.notice({
message: 'Failed to update SSL Certificate. ' + _s(result.jobresult.errortext)
});
}
$loading.remove();
}
}
},
error: function(XMLHttpResponse) {
cloudStack.dialog.notice({
@ -162,6 +250,11 @@
$loading.remove();
}
});
return;
};
// start uploading the certificates
uploadCertificate(0);
},
context: {}
});