From 3729f86364a1c0242687d1c254c650fd54a955a5 Mon Sep 17 00:00:00 2001 From: Marius Vollmer Date: Tue, 26 Sep 2017 15:18:03 +0300 Subject: [PATCH] storage: Checkboxes for common crypto and mount options --- pkg/storaged/crypto-tab.jsx | 9 +- pkg/storaged/format-dialog.jsx | 175 ++++++++++++++++++++++++----- pkg/storaged/fsys-tab.jsx | 27 +---- test/verify/check-storage-hidden | 2 +- test/verify/check-storage-luks | 6 +- test/verify/check-storage-mounting | 58 ++++++++++ test/verify/check-storage-msdos | 2 +- 7 files changed, 216 insertions(+), 63 deletions(-) diff --git a/pkg/storaged/crypto-tab.jsx b/pkg/storaged/crypto-tab.jsx index 129644734ea..3c29138236e 100644 --- a/pkg/storaged/crypto-tab.jsx +++ b/pkg/storaged/crypto-tab.jsx @@ -105,18 +105,13 @@ var CryptoTab = React.createClass({ function edit_options() { edit_config(function (config, commit) { dialog.open({ Title: _("Encryption Options"), - Fields: [ - { TextInput: "options", - Title: _("Options"), - Value: old_options - } - ], + Fields: FormatDialog.crypto_options_dialog_fields(old_options), Action: { Title: _("Apply"), action: function (vals) { config["options"] = { t: 'ay', - v: utils.encode_filename(vals.options) + v: utils.encode_filename(FormatDialog.crypto_options_dialog_options(vals)) } return commit(); } diff --git a/pkg/storaged/format-dialog.jsx b/pkg/storaged/format-dialog.jsx index 26cc9cafdb9..85f4099fd52 100644 --- a/pkg/storaged/format-dialog.jsx +++ b/pkg/storaged/format-dialog.jsx @@ -28,6 +28,144 @@ var StorageControls = require("./storage-controls.jsx"); var _ = cockpit.gettext; +function parse_options(options) { + if (options) + return (options.split(",") + .map(function (s) { return s.trim() }) + .filter(function (s) { return s != "" })); + else + return [ ]; +} + +function unparse_options(split) { + return split.join(","); +} + +function extract_option(split, opt) { + var index = split.indexOf(opt); + if (index >= 0) { + split.splice(index, 1); + return true; + } else { + return false; + } +} + +function mounting_dialog_fields(is_custom, mount_dir, mount_options, visible) { + + if (!visible) + visible = function () { return true; }; + + var split_options = parse_options(mount_options == "defaults" ? "" : mount_options); + var opt_auto = !extract_option(split_options, "noauto"); + var opt_ro = extract_option(split_options, "ro"); + var extra_options = unparse_options(split_options); + + return [ + { SelectOne: "mounting", + Title: _("Mounting"), + Options: [ + { value: "default", Title: _("Default"), selected: !is_custom }, + { value: "custom", Title: _("Custom"), selected: is_custom } + ], + visible: visible + }, + { TextInput: "mount_point", + Title: _("Mount Point"), + Value: mount_dir, + visible: function (vals) { + return visible(vals) && vals.mounting == "custom"; + }, + validate: function (val) { + if (val.trim() == "") + return _("Mount point can not be empty"); + } + }, + { RowTitle: _("Mount options"), + CheckBox: "mount_auto", + Title: _("Mount at boot"), + Value: opt_auto, + visible: function (vals) { + return visible(vals) && vals.mounting == "custom"; + }, + update: function (vals, trigger) { + if (trigger == "crypto_options_auto" && vals.crypto_options_auto == false) + return false; + else + return vals.mount_auto; + } + }, + { CheckBox: "mount_ro", + Title: _("Mount read only"), + Value: opt_ro, + visible: function (vals) { + return visible(vals) && vals.mounting == "custom"; + }, + update: function (vals, trigger) { + if (trigger == "crypto_options_ro" && vals.crypto_options_ro == true) + return true; + else + return vals.mount_ro; + } + }, + { CheckBoxText: "mount_extra_options", + Title: _("Custom mount options"), + Value: extra_options == "" ? false : extra_options, + visible: function (vals) { + return visible(vals) && vals.mounting == "custom"; + } + } + ]; +} + +function mounting_dialog_options(vals) { + var opts = [ ]; + if (!vals.mount_auto) + opts.push("noauto"); + if (vals.mount_ro) + opts.push("ro"); + if (vals.mount_extra_options !== false) + opts = opts.concat(parse_options(vals.mount_extra_options)); + return unparse_options(opts); +} + +function crypto_options_dialog_fields(options, visible) { + var split_options = parse_options(options); + var opt_auto = !extract_option(split_options, "noauto"); + var opt_ro = extract_option(split_options, "readonly"); + var extra_options = unparse_options(split_options); + + return [ + { RowTitle: _("Encryption Options"), + CheckBox: "crypto_options_auto", + Title: _("Unlock at boot"), + Value: opt_auto, + visible: visible + }, + { CheckBox: "crypto_options_ro", + Title: _("Unlock read only"), + Value: opt_ro, + visible: visible + }, + { CheckBoxText: "crypto_extra_options", + Title: _("Custom encryption options"), + Value: extra_options == "" ? false : extra_options, + visible: visible + } + ]; +} + +function crypto_options_dialog_options(vals) { + var opts = [ ]; + if (!vals.crypto_options_auto) + opts.push("noauto"); + if (vals.crypto_options_ro) + opts.push("readonly"); + if (vals.crypto_extra_options !== false) + opts = opts.concat(parse_options(vals.crypto_extra_options)); + return unparse_options(opts); +} + function format_dialog(client, path, start, size, enable_dos_extended) { var block = client.blocks[path]; var block_ptable = client.blocks_ptable[path]; @@ -172,32 +310,9 @@ function format_dialog(client, path, start, size, enable_dos_extended) { { CheckBox: "store_passphrase", Title: _("Store passphrase"), visible: is_encrypted_and_not_old_udisks2 - }, - { TextInput: "crypto_options", - Title: _("Encryption Options"), - visible: is_encrypted_and_not_old_udisks2 - }, - { SelectOne: "mounting", - Title: _("Mounting"), - Options: [ - { value: "default", Title: _("Default") }, - { value: "custom", Title: _("Custom") } - ], - visible: is_filesystem_and_not_old_udisks2 - }, - { TextInput: "mount_point", - Title: _("Mount Point"), - visible: function (vals) { - return is_filesystem_and_not_old_udisks2(vals) && vals.mounting == "custom"; - } - }, - { TextInput: "mount_options", - Title: _("Mount Options"), - visible: function (vals) { - return is_filesystem_and_not_old_udisks2(vals) && vals.mounting == "custom"; - } } - ], + ].concat(crypto_options_dialog_fields("", is_encrypted_and_not_old_udisks2)) + .concat(mounting_dialog_fields(false, "", "", is_filesystem_and_not_old_udisks2)), Action: { Title: create_partition? _("Create partition") : _("Format"), Danger: (create_partition? @@ -216,23 +331,25 @@ function format_dialog(client, path, start, size, enable_dos_extended) { options.label = { t: 's', v: vals.name }; var config_items = [ ]; + var mount_options = mounting_dialog_options(vals); if (vals.mounting == "custom") config_items.push([ "fstab", { dir: { t: 'ay', v: utils.encode_filename(vals.mount_point) }, type: { t: 'ay', v: utils.encode_filename("auto") }, - opts: { t: 'ay', v: utils.encode_filename(vals.mount_options || "defaults") }, + opts: { t: 'ay', v: utils.encode_filename(mount_options || "defaults") }, freq: { t: 'i', v: 0 }, passno: { t: 'i', v: 0 }, "track-parents": { t: 'b', v: true } }]); + var crypto_options = crypto_options_dialog_options(vals); if (is_encrypted(vals)) { vals.type = vals.type.replace("luks+", ""); options["encrypt.passphrase"] = { t: 's', v: vals.passphrase }; var item = { - options: { t: 'ay', v: utils.encode_filename(vals.crypto_options) }, + options: { t: 'ay', v: utils.encode_filename(crypto_options) }, "track-parents": { t: 'b', v: true } }; if (vals.store_passphrase) { @@ -284,6 +401,10 @@ var FormatButton = React.createClass({ }); module.exports = { + mounting_dialog_fields: mounting_dialog_fields, + mounting_dialog_options: mounting_dialog_options, + crypto_options_dialog_fields: crypto_options_dialog_fields, + crypto_options_dialog_options: crypto_options_dialog_options, format_dialog: format_dialog, FormatButton: FormatButton }; diff --git a/pkg/storaged/fsys-tab.jsx b/pkg/storaged/fsys-tab.jsx index a900d323560..5815b17920a 100644 --- a/pkg/storaged/fsys-tab.jsx +++ b/pkg/storaged/fsys-tab.jsx @@ -118,34 +118,13 @@ var FilesystemTab = React.createClass({ function mounting_dialog() { dialog.open({ Title: _("Filesystem Mounting"), - Fields: [ - { SelectOne: "mounting", - Title: _("Mounting"), - Options: [ - { value: "default", Title: _("Default"), selected: !old_config }, - { value: "custom", Title: _("Custom"), selected: !!old_config } - ], - }, - { TextInput: "mount_point", - Title: _("Mount Point"), - Value: old_dir, - visible: function (vals) { - return vals.mounting == "custom"; - } - }, - { TextInput: "mount_options", - Title: _("Mount Options"), - Value: old_opts, - visible: function (vals) { - return vals.mounting == "custom"; - } - } - ], + Fields: FormatDialog.mounting_dialog_fields(!!old_config, old_dir, old_opts), Action: { Title: _("Apply"), action: function (vals) { return maybe_update_config(vals.mounting == "custom", - vals.mount_point, vals.mount_options); + vals.mount_point, + FormatDialog.mounting_dialog_options(vals)); } } }); diff --git a/test/verify/check-storage-hidden b/test/verify/check-storage-hidden index fc4d5a7117b..a90214736e3 100755 --- a/test/verify/check-storage-hidden +++ b/test/verify/check-storage-hidden @@ -65,7 +65,7 @@ class TestStorage(StorageCase): "passphrase2": "einszweidrei", "mounting": "custom", "mount_point": mount_point_1, - "crypto_options": "my-crypto-tag" }) + "crypto_extra_options": CheckBoxText("my-crypto-tag") }) self.content_row_wait_in_col(2, 1, "46 MiB ext4 File System") self.wait_in_storaged_configuration(mount_point_1) self.wait_in_storaged_configuration("my-crypto-tag") diff --git a/test/verify/check-storage-luks b/test/verify/check-storage-luks index eba7d0981c0..ab92ec52fdc 100755 --- a/test/verify/check-storage-luks +++ b/test/verify/check-storage-luks @@ -52,7 +52,7 @@ class TestStorage(StorageCase): "store_passphrase": True, "mounting": "custom", "mount_point": mount_point_secret, - "crypto_options": "crypto,options" }) + "crypto_extra_options": CheckBoxText("crypto,options") }) self.content_row_wait_in_col(1, 1, "Encrypted data") self.content_row_wait_in_col(2, 1, "ext4 File System") @@ -76,8 +76,8 @@ class TestStorage(StorageCase): # Change options. We keep trying until the stack has synched # up with crypttab and we see the old options. self.dialog_with_retry(trigger = lambda: self.content_tab_info_action(1, 2, "Options"), - expect = { "options": "crypto,options" }, - values = { "options": "weird,options" }) + expect = { "crypto_extra_options": CheckBoxText("crypto,options") }, + values = { "crypto_extra_options": CheckBoxText("weird,options") }) assert m.execute("grep 'weird,options' /etc/crypttab") != "" self.wait_in_storaged_configuration("weird,options") diff --git a/test/verify/check-storage-mounting b/test/verify/check-storage-mounting index fa1851bfc6b..936ad8064da 100755 --- a/test/verify/check-storage-mounting +++ b/test/verify/check-storage-mounting @@ -140,5 +140,63 @@ class TestStorage(StorageCase): self.dialog({ "type": "empty" }) self.content_row_wait_in_col(1, 1, "Unrecognized Data") + def testMountOptions(self): + m = self.machine + b = self.browser + + if self.storaged_is_old_udisks: + self.skipTest("No mount/crypto options with old UDisks") + + self.login_and_go("/storage") + + # Add a disk + m.add_disk("50M", serial="MYDISK") + b.wait_in_text("#drives", "MYDISK") + b.click('#drives tr:contains("MYDISK")') + b.wait_visible('#storage-detail') + + # Open format dialog and play with the checkboxes + + self.content_tab_action(1, 1, "Format") + self.dialog_wait_open() + self.dialog_set_val("type", "luks+ext4") + self.dialog_set_val("mounting", "custom") + + def wait_checked(field): + b.wait_present(self.dialog_field(field) + ":checked") + + def wait_not_checked(field): + b.wait_present(self.dialog_field(field) + ":not(:checked)") + + wait_checked("crypto_options_auto") + wait_checked("mount_auto") + wait_not_checked("crypto_options_ro") + wait_not_checked("mount_ro") + + # Uncheck crypto auto. This gets propagated to mount auto. + self.dialog_set_val("crypto_options_auto", False) + wait_not_checked("mount_auto") + + # Check crypto ro. This gets propagated to mount ro. + self.dialog_set_val("crypto_options_ro", True) + wait_checked("mount_ro") + + # Set extra options. + self.dialog_set_val("crypto_extra_options", CheckBoxText("foo")) + self.dialog_set_val("mount_extra_options", CheckBoxText("foo")) + + # Fill in the erst and do the format + self.dialog_set_val("passphrase", "vainu-reku-toma-rolle-kaja") + self.dialog_set_val("passphrase2", "vainu-reku-toma-rolle-kaja") + self.dialog_set_val("mount_point", "/data") + self.dialog_apply() + self.dialog_wait_close() + + self.content_row_wait_in_col(1, 1, "Encrypted data") + self.content_row_wait_in_col(2, 1, "ext4 File System") + self.wait_in_storaged_configuration("/data") + m.execute("grep 'noauto,readonly,foo' /etc/crypttab") + m.execute("grep 'noauto,ro,foo' /etc/fstab") + if __name__ == '__main__': test_main() diff --git a/test/verify/check-storage-msdos b/test/verify/check-storage-msdos index d963d1345fb..6b2068ffbee 100755 --- a/test/verify/check-storage-msdos +++ b/test/verify/check-storage-msdos @@ -63,7 +63,7 @@ class TestStorage(StorageCase): self.dialog_wait_not_visible("name") self.dialog_wait_not_visible("mounting") self.dialog_wait_not_visible("mount_point") - self.dialog_wait_not_visible("mount_options") + self.dialog_wait_not_visible("mount_auto") self.dialog_apply() self.dialog_wait_close() self.content_row_wait_in_col(2, 1, "Extended Partition")