Skip to content

Commit

Permalink
Add a widget to select a public key
Browse files Browse the repository at this point in the history
  • Loading branch information
imobachgs committed Oct 29, 2018
1 parent 130b449 commit 4cf06c6
Show file tree
Hide file tree
Showing 10 changed files with 596 additions and 0 deletions.
6 changes: 6 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,17 @@ ylibdialog_DATA = \
lib/users/dialogs/users_to_import.rb \
lib/users/dialogs/encryption_method.rb

ywidgetdir = @ylibdir@/users/widgets
ywidget_DATA = \
lib/users/widgets/disk_selector.rb \
lib/users/widgets/public_key_selector.rb

ylibdir = @ylibdir@/users
ylib_DATA = \
lib/users/ca_password_validator.rb \
lib/users/local_password.rb \
lib/users/encryption_method.rb \
lib/users/leaf_blk_device.rb \
lib/users/proposal.rb \
lib/users/encryption_proposal.rb \
lib/users/ssh_authorized_keys_file.rb \
Expand Down
119 changes: 119 additions & 0 deletions src/lib/users/leaf_blk_device.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# encoding: utf-8

# Copyright (c) [2018] SUSE LLC
#
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of version 2 of the GNU General Public License as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, contact SUSE LLC.
#
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.

require "json"
require "yast2/execute"

module Y2Users
# This class represents a block device reported by `lsblk` as a leaf node
#
# As we cannot use a devicegraph for the time being, this class extracts the
# block devices information from the lsblk command. Only the leaf devices
# are taken into account and it only includes information which is relevant
# for the {Y2Users::Widgets::DiskSelector} widget.
class LeafBlkDevice
class << self
# Returns all relevant block devices
#
# @note It takes the information from `lsblk`
#
# @return [Array<LeafBlkDevice>] List of relevant block devices
def all
@all ||= lsblk["blockdevices"].map { |h| new_from_hash(h) }
end

# Instantiates a new object
#
# @note It uses a Hash with information from `lsblk`.
#
# @return [LeafBlkDevice] New LeafBlkDevice instance
def new_from_hash(hash)
parent = find_root_device(hash)
removable = hash["rm"] == "1"
new(
name: hash["name"], disk: parent["name"], fstype: hash["fstype"],
model: parent["model"], removable: removable
)
end

private

# Gets `lsblk` into a Hash
#
# @note The output is in inverse order, as we are only interested in leaf nodes.
#
# @return [Hash] Hash containing data from `lsblk`
def lsblk
output = Yast::Execute.locally(
"/usr/bin/lsblk", "--inverse", "--json", "--paths",
"--output", "NAME,TRAN,FSTYPE,RM,MODEL", stdout: :capture
)
JSON.parse(output)
end

# Finds the root for a given device
#
# @return [Hash]
def find_root_device(hash)
hash.key?("children") ? find_root_device(hash["children"][0]) : hash
end
end

# @return [String] Kernel name
attr_reader :name

# @return [String] Hardware model
attr_reader :model

# @return [String] Disk's kernel name
attr_reader :disk

# @return [Symbol] Filesystem type
attr_reader :fstype

# @return [Boolean] Indicates whether the device is mountable or not
attr_reader :removable

alias_method :removable?, :removable

# Constructor
#
# @param name [String] Kernel name
# @param model [String] Hardware model
# @param disk [String] Disk's kernel name
# @param fstype [Symbol] Filesystem type
# @param removable [Boolean] Indicates whether the device is mountable or not
def initialize(name:, model:, disk:, fstype:, removable:)
@name = name
@model = model
@disk = disk
@fstype = fstype.to_sym if fstype
@removable = removable
end

# Determines whether the device has a filesystem
#
# @return [Boolean]
def filesystem?
!!fstype
end
end
end
69 changes: 69 additions & 0 deletions src/lib/users/widgets/disk_selector.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# encoding: utf-8

# Copyright (c) [2018] SUSE LLC
#
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of version 2 of the GNU General Public License as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, contact SUSE LLC.
#
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.

require "cwm"
require "users/leaf_blk_device"

module Y2Users
module Widgets
# This widget allows to select a removable device
class DiskSelector < ::CWM::ComboBox
# Constructor
def initialize
textdomain "users"
self.widget_id = "disk_selector"
end

# @see CWM::AbstractWidget
def label
""
end

# @see CWM::AbstractWidget
def init
self.value = devices.first.name unless devices.empty?
end

# @see CWM::ComboBox#items
def items
devices.map { |d| [d.name, item_label(d)] }
end

private

# Returns the list of devices that can be selected
#
# @note Basically only removable devices are supported.
#
# @return [Array<LeafBlkDevice>]
def devices
@devices ||= LeafBlkDevice.all.select(&:filesystem?)
end

# Returns the description to be used for a given item
#
# @return [String]
def item_label(dev)
"#{dev.model} (#{dev.name})"
end
end
end
end
147 changes: 147 additions & 0 deletions src/lib/users/widgets/public_key_selector.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# encoding: utf-8

# Copyright (c) [2018] SUSE LLC
#
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of version 2 of the GNU General Public License as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, contact SUSE LLC.
#
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.

require "cwm"
require "users/widgets/disk_selector"
require "yast2/execute"
require "yast2/popup"
require "tmpdir"

Yast.import "UI"
Yast.import "SSHAuthorizedKeys"

module Y2Users
module Widgets
# This widget allows to select a public key from a removable device
class PublicKeySelector < ::CWM::CustomWidget
# @!attribute [r]
# @return [String] Public key content
attr_reader :value

# Constructor
def initialize
textdomain "users"
end

# @return [String] Widget label
def label
_("Import Public Key")
end

# @return [Yast::Term] Dialog content
# @see CWM::CustomWidget
def contents
VBox(
Left(Label(label)),
HBox(
disk_selector,
HStretch(),
PushButton(Id(:browse), "Browse..."),
),
ReplacePoint(Id(:fingerprint), Empty())
)
end

# Events handler
#
# @see CWM::AbstractWdiget
def handle(event)
read_key if event["ID"] == :browse
nil
end

# @see CWM::AbstractWdiget
def store
Yast::SSHAuthorizedKeys.import_keys("/root", [value]) if value
nil
end

private

attr_writer :value

# Builds a widget to select the disk to read the public key from
#
# @return [DiskSelector] Disk selection widget
def disk_selector
@disk_selector ||= DiskSelector.new
end

# Reads the key selected by the user
#
# @note This method mounts the selected filesystem.
#
# @return [String] Key content
def read_key
dir = Dir.mktmpdir
begin
mounted = Yast::SCR.Execute(
Yast::Path.new(".target.mount"), [disk_selector.value, dir], "-o ro"
)
if mounted
self.value = read_key_from(dir)
refresh_fingerprint
else
report_mount_error(disk_selector.value)
end
ensure
Yast::SCR.Execute(Yast::Path.new(".target.umount"), dir) if mounted
FileUtils.remove_entry_secure(dir)
end
end

# Reads a the key from the given directory
#
# @note Asks the user to select a file and tries to read it.
def read_key_from(dir)
path = Yast::UI.AskForExistingFile(dir, "*", _("Select a public key"))
File.read(path) if path && File.exist?(path)
end

# Refreshes the fingerprint which is shown in the UI.
def refresh_fingerprint
Yast::UI::ReplaceWidget(
Id(:fingerprint), Left(Label(fingerprint(value)))
)
end

# Displays an error about the device which failed to be mounted
#
# @param device [String] Device's name
def report_mount_error(device)
message = format(_("Could not mount device %s"), device)
Yast2::Popup.show(message, headline: :error)
end

# Calculates the fingerprint for a given key
#
# @param key [String] SSH public key
# @return [String] Key's fingerprint
def fingerprint(key)
return "" unless key
output = Yast::Execute.locally!(
["echo", key], ["ssh-keygen", "-l", "-f", "/dev/stdin"], stdout: :capture
)
output.split(" ").at(1).to_s
end
end
end
end
3 changes: 3 additions & 0 deletions test/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ TESTS = \
lib/users/encryption_method_test.rb \
lib/users/ssh_authorized_keys_file_test.rb \
lib/users/users_database_test.rb \
lib/users/leaf_blk_device_test.rb \
lib/users/widgets/disk_selector_test.rb \
lib/users/widgets/public_key_selector_test.rb \
dialogs_test.rb \
ssh_authorized_keys_test.rb \
users_test.rb \
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/id_rsa.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCpZC8ctjmn90B/MxLOdSjYM3Yl3qd+BhTWYdBNgO3B1fJ1JSegTgCpDM0krMHqd/OAslW5H3MRED7g7g9WkKZh5xTMGvH56yRitJySfSiK8uSxCu6Jg7NM11kqOs5/RwycHO8955QrEYyiWOx80unD+CBJxGEZCOu/DH3ca4yEigAt2HSuC8NPicmRJWua6IbDa+VSICvdOTdFTM8izScSd5WBFH1ULz0bBfLnyi/pIiMjuHB69AN4gsUGYgKjzUsnufKli+DmzACgVWTdQ3Ukax/4/wgXFMr3KsDNpTbn7ZZOKzPpIXpzlP9AwbHQdym6J2NAPYV+DDY3Kcr/vql9 dummy1@example.net
16 changes: 16 additions & 0 deletions test/fixtures/lsblk.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"blockdevices": [
{"name": "/dev/sda1", "tran": null, "fstype": "vfat", "rm": "0", "model": null,
"children": [
{"name": "/dev/sda", "tran": "sata", "fstype": null, "rm": "0", "model": "WDC WD10EZEX-75M"}
]
},
{"name": "/dev/sda2", "tran": null, "fstype": "ext4", "rm": "0", "model": null,
"children": [
{"name": "/dev/sda", "tran": "sata", "fstype": null, "rm": "0", "model": "WDC WD10EZEX-75M"}
]
},
{"name": "/dev/sr0", "tran": "sata", "fstype": null, "rm": "1", "model": "DVD-ROM DS-8DBSH"},
{"name": "/dev/sr1", "tran": "usb", "fstype": "iso9660", "rm": "1", "model": "File-CD Gadget "}
]
}
Loading

0 comments on commit 4cf06c6

Please sign in to comment.