From 55a0baf1c32e1c472efe2ce81870e05abccb5a4a Mon Sep 17 00:00:00 2001 From: Pavel Vomacka Date: Tue, 26 Apr 2016 12:28:45 +0200 Subject: Add certificate widget The certificate widget is used for each certificate in certs_widget. It allows to view, get, download, revoke and restore certificate. https://fedorahosted.org/freeipa/ticket/5108 https://fedorahosted.org/freeipa/ticket/5381 Reviewed-By: Petr Vobornik --- install/ui/less/widgets.less | 18 ++ install/ui/src/freeipa/certificate.js | 305 +++++++++++++++++++++++++++++++--- install/ui/test/data/ipa_init.json | 6 + ipaserver/plugins/internal.py | 6 + 4 files changed, 311 insertions(+), 24 deletions(-) diff --git a/install/ui/less/widgets.less b/install/ui/less/widgets.less index 616db9bc5..c31b2caf7 100644 --- a/install/ui/less/widgets.less +++ b/install/ui/less/widgets.less @@ -134,13 +134,31 @@ // Certificate Widget .certificate-widget { + padding: 5px 5px 5px 5px; + border: 1px dashed #DDD; label { padding-right: 10px; } + .cert-value { + padding-left: 5px; + } .certificate { word-wrap: break-word; padding-bottom: 10px; } + .dropdown { + float:right; + } + ul.dropdown-menu { + min-width: 100px; + } + .watermark { + position: absolute; + bottom: 0; + right: 0; + opacity:0.5; + font-size: 150%; + } } // Working widget diff --git a/install/ui/src/freeipa/certificate.js b/install/ui/src/freeipa/certificate.js index 0aa270039..cba50171b 100755 --- a/install/ui/src/freeipa/certificate.js +++ b/install/ui/src/freeipa/certificate.js @@ -21,7 +21,9 @@ define([ 'dojo/_base/lang', + 'dojo/on', './builder', + './datetime', './metadata', './ipa', './jquery', @@ -31,10 +33,11 @@ define([ './rpc', './text', './widget', + './widgets/DropdownWidget', './dialog'], function( - lang, builder, metadata_provider, IPA, $, menu, - phases, reg, rpc, text, widget_mod) { + lang, on, builder, datetime, metadata_provider, IPA, $, menu, + phases, reg, rpc, text, widget_mod, DropdownWidget) { var exp = IPA.cert = {}; @@ -1107,40 +1110,294 @@ IPA.cert.certs_widget = function(spec) { return that; }; + +/** + * certificate widget + * + * @class + * @extends IPA.input_widget + */ +IPA.cert.cert_widget = function(spec) { + + spec = spec || {}; + + var that = IPA.input_widget(spec); + IPA.table_mixin().apply(that); + + that.certificate = null; + + that.create = function(container) { + + that.widget_create(container); + + that.container = container; + that.container.addClass('cert-container col-sm-12'); + + var spinner_spec = { + name: 'working-notification' + }; + + that.spinner = IPA.working_widget(spinner_spec); + that.spinner.create(that.container); + + that.cert_subject = $('
', { + style: 'font-weight: bold;', + text: '' + }).appendTo(that.container); + + that.table_layout = that.create_layout().appendTo(that.container); + + var tr = that.create_row().appendTo(that.table_layout); + that.create_header_cell('@i18n:objects.cert.serial_number', ':') + .appendTo(tr); + that.cert_sn = that.create_cell('', '', 'cert-value').appendTo(tr); + + tr = that.create_row().appendTo(that.table_layout); + that.create_header_cell('@i18n:objects.cert.issued_by', ':') + .appendTo(tr); + that.cert_issuer = that.create_cell('', '', 'cert-value').appendTo(tr); + + tr = that.create_row().appendTo(that.table_layout); + that.create_header_cell('@i18n:objects.cert.valid_from', ':') + .appendTo(tr); + that.cert_valid_from = that.create_cell('', '', 'cert-value') + .appendTo(tr); + + tr = that.create_row().appendTo(that.table_layout); + that.create_header_cell('@i18n:objects.cert.valid_to', ':') + .appendTo(tr); + that.cert_valid_to = that.create_cell('', '', 'cert-value') + .appendTo(tr); + + that.dropdown = builder.build(null, { + $ctor: DropdownWidget, + toggle_text: text.get('@i18n:actions.title'), + toggle_class: 'btn btn-default dropdown-toggle', + toggle_icon: 'caret', + right_aligned: true, + name: 'cert-actions', + 'class': 'dropdown cert-actions', + items: [ + { + name: 'view', + label: text.get('@i18n:buttons.view'), + handler: that.open_view_dialog + }, + { + name: 'get', + label: text.get('@i18n:buttons.get'), + handler: that.open_get_dialog + }, + { + name: 'download', + label: text.get('@i18n:buttons.download'), + handler: that.perform_download + }, + { + name: 'revoke', + label: text.get('@i18n:buttons.revoke'), + disabled: true, + handler: that.open_revoke_dialog + }, + { + name: 'remove_hold', + label: text.get('@i18n:buttons.remove_hold'), + disabled: true, + handler: that.perform_remove_hold + } + ] + }); + + on(that.dropdown, 'item-click', function(item) { + if (!item.disabled && item.handler) { + item.handler(); + } + }); + + that.container.append(that.dropdown.render()); + that.table_layout.appendTo(that.container); + + that.create_error_link(that.container); + }; + + that.get_custom_actions = function() { + return that.dropdown; + }; + + that.update_displayed_data = function() { + + that.revoke_note = $('
', { + text: text.get('@i18n:objects.cert.revoked_status'), + style: 'display: none', + 'class': 'watermark' + }).appendTo(that.container); + + var cert = that.certificate; + + if (cert) { + that.cert_subject.text(IPA.cert.parse_dn(cert.subject).cn); + that.cert_sn.text(cert.serial_number); + that.cert_issuer.text(IPA.cert.parse_dn(cert.issuer).cn); + that.cert_valid_from.text(cert.valid_not_before); + that.cert_valid_to.text(cert.valid_not_after); } - if (!l) { - that.content_el.append(that.create_status( - 'missing', - text.get('@i18n:objects.cert.missing'), - 'fa fa-warning')); + that.handle_revocation_reason(cert.revocation_reason); + }; + + that.toggle_revoked_note = function(show) { + if (show) { + that.revoke_note.css('display', 'block'); } + else { + that.revoke_note.css('display', 'none'); + } + }; - if (l && !that.certs_visible) { + that.handle_revocation_reason = function(reason) { + // Skip certificates which are not issued by ipa's CA + if (that.certificate.revoked === undefined) return; - var msg = text.get('@i18n:objects.cert.present'); - msg = msg.replace('${count}', l); - that.content_el.append( - that.create_status('present', msg, 'fa fa-check')); + var dd_menu = that.get_custom_actions(); - IPA.button({ - name: 'show', - label: '@i18n:buttons.show', - click: function() { - that.certs_visible = true; - that.create_certs(); - } - }).appendTo(that.content_el); + if (reason && reason === 6) { + dd_menu.enable_item('remove_hold'); + dd_menu.disable_item('revoke'); + that.toggle_revoked_note(true); + } + else if (reason === null || reason === undefined) { + dd_menu.enable_item('revoke'); + dd_menu.disable_item('remove_hold'); + } + else if (typeof reason === 'number' && reason >= 0 && + reason < IPA.cert.CRL_REASON.length) { + dd_menu.disable_item('revoke'); + that.toggle_revoked_note(true); } }; that.update = function(values) { - that.certificates = values; - that.create_certs(); + + var certificate = values[0]; + + if (!certificate ) certificate = {}; + + that.certificate = certificate; + + that.update_displayed_data(); }; - that.clear = function() { - that.content_el.empty(); + that.save = function() { + return that.certificate.certificate; + }; + + that.compose_dialog_title = function() { + var cert = that.certificate; + var cn, o; + + if (cert.subject) { + cn = IPA.cert.parse_dn(cert.subject).cn; + o = IPA.cert.parse_dn(cert.subject).o; + } + else { + cn = o = text.get('@i18n:objects.cert.unspecified'); + } + + var r = text.get('@i18n:objects.cert.view_certificate'); + r = r.replace('${entity}', cn); + r = r.replace('${primary_key}', o); + + return r; + }; + + that.open_view_dialog = function() { + + var spec = { + title: that.compose_dialog_title(), + certificate: that.certificate + }; + + var dialog = IPA.cert.view_dialog(spec); + dialog.open(); + }; + + that.open_get_dialog = function() { + var spec = { + title: that.compose_dialog_title(), + certificate: that.certificate.certificate + }; + + var dialog = IPA.cert.download_dialog(spec); + dialog.open(); + }; + + that.perform_download = function() { + var data_uri = IPA.cert.create_data_uri(that.certificate.certificate); + IPA.cert.perform_download(data_uri); + }; + + that.open_revoke_dialog = function() { + var spec = { + title: that.compose_dialog_title(), + message: '@i18n:objects.cert.revoke_confirmation', + ok_label: '@i18n:buttons.revoke', + on_ok: function() { + + var command_spec = { + hide_activity_icon: true, + notify_activity_end: function() { + that.spinner.emit('hide-spinner'); + }, + notify_activity_start: function() { + that.spinner.emit('display-spinner'); + }, + on_success: function() { + var reason = parseInt(dialog.get_reason(), 10); + that.handle_revocation_reason(reason); + that.facet.certificate_updated.notify(); + IPA.notify_success('@i18n:objects.cert.revoked'); + } + }; + + var sn = that.certificate.serial_number; + var revocation_reason = dialog.get_reason(); + IPA.cert.perform_revoke(command_spec, sn, revocation_reason); + } + }; + + var dialog = IPA.cert.revoke_dialog(spec); + dialog.open(); + }; + + that.perform_remove_hold = function() { + var spec = { + title: that.compose_dialog_title(), + message: '@i18n:objects.cert.remove_certificate_hold_confirmation', + ok_label: '@i18n:buttons.remove_hold', + on_ok: function () { + var command_spec = { + hide_activity_icon: true, + notify_activity_end: function() { + that.spinner.emit('hide-spinner'); + }, + notify_activity_start: function() { + that.spinner.emit('display-spinner'); + }, + on_success: function() { + that.toggle_revoked_note(); + that.handle_revocation_reason(); + that.facet.certificate_updated.notify(); + IPA.notify_success('@i18n:objects.cert.hold_removed'); + } + }; + + var sn = that.certificate.serial_number; + IPA.cert.perform_remove_hold(command_spec, sn); + } + }; + + var dialog = IPA.confirm_dialog(spec); + dialog.open(); }; return that; diff --git a/install/ui/test/data/ipa_init.json b/install/ui/test/data/ipa_init.json index cdb7bdbef..49eca4de9 100644 --- a/install/ui/test/data/ipa_init.json +++ b/install/ui/test/data/ipa_init.json @@ -69,6 +69,8 @@ "cancel": "Cancel", "close": "Close", "disable": "Disable", + "download": "Download", + "download_title": "Download certificate as PEM formatted file.", "edit": "Edit", "enable": "Enable", "filter": "Filter", @@ -268,6 +270,7 @@ "md5_fingerprint": "MD5 Fingerprint", "missing": "No Valid Certificate", "new_certificate": "New Certificate", + "new_cert_format": "Certificate in base64 or PEM format", "note": "Note", "organization": "Organization", "organizational_unit": "Organizational Unit", @@ -287,6 +290,7 @@ "revoke_certificate_simple": "Revoke Certificate", "revoke_confirmation": "To confirm your intention to revoke this certificate, select a reason from the pull-down list, and click the \"Revoke\" button.", "revoked": "Certificate Revoked", + "revoked_status": "REVOKED", "serial_number": "Serial Number", "serial_number_hex": "Serial Number (hex)", "sha1_fingerprint": "SHA1 Fingerprint", @@ -295,6 +299,8 @@ "superseded": "Superseded", "unspecified": "Unspecified", "valid": "Valid Certificate Present", + "valid_from": "Valid from", + "valid_to": "Valid to", "validity": "Validity", "view_certificate": "Certificate for ${entity} ${primary_key}", "view_certificate_btn": "View Certificate" diff --git a/ipaserver/plugins/internal.py b/ipaserver/plugins/internal.py index 8552f63d6..5b0a3db37 100644 --- a/ipaserver/plugins/internal.py +++ b/ipaserver/plugins/internal.py @@ -211,6 +211,8 @@ class i18n_messages(Command): "cancel": _("Cancel"), "close": _("Close"), "disable": _("Disable"), + "download": _("Download"), + "download_title": _("Download certificate as PEM formatted file."), "edit": _("Edit"), "enable": _("Enable"), "filter": _("Filter"), @@ -411,6 +413,7 @@ class i18n_messages(Command): "md5_fingerprint": _("MD5 Fingerprint"), "missing": _("No Valid Certificate"), "new_certificate": _("New Certificate"), + "new_cert_format": _("Certificate in base64 or PEM format"), "note": _("Note"), "organization": _("Organization"), "organizational_unit": _("Organizational Unit"), @@ -430,6 +433,7 @@ class i18n_messages(Command): "revoke_certificate_simple": _("Revoke Certificate"), "revoke_confirmation": _("To confirm your intention to revoke this certificate, select a reason from the pull-down list, and click the \"Revoke\" button."), "revoked": _("Certificate Revoked"), + "revoked_status": _("REVOKED"), "serial_number": _("Serial Number"), "serial_number_hex": _("Serial Number (hex)"), "sha1_fingerprint": _("SHA1 Fingerprint"), @@ -438,6 +442,8 @@ class i18n_messages(Command): "superseded": _("Superseded"), "unspecified": _("Unspecified"), "valid": _("Valid Certificate Present"), + "valid_from": _("Valid from"), + "valid_to": _("Valid to"), "validity": _("Validity"), "view_certificate": _("Certificate for ${entity} ${primary_key}"), "view_certificate_btn": _("View Certificate"), -- cgit