On Wed, 7 Jun 2017 15:38:47 -0500 Goldwyn Rodrigues <rgoldwyn@suse.de> wrote:
From: Goldwyn Rodrigues <rgoldwyn@suse.com>
The apparmor_ui_dialog communicates with aa-logprof in JSON. Each dialog is converted to a yast dialog for input from the user and returns the actions performed in yast in the form of JSON response.
An extra opendialog has been used to indicate the end of the logprof execution.
Signed-off-by: Goldwyn Rodrigues <rgoldwyn@suse.com> --- src/clients/apparmor.rb | 1 + src/clients/logprof.rb | 94 +++++++++++++ src/lib/apparmor/apparmor_ui_dialog.rb | 243 +++++++++++++++++++++++++++++++++ 3 files changed, 338 insertions(+) create mode 100644 src/clients/logprof.rb create mode 100644 src/lib/apparmor/apparmor_ui_dialog.rb
diff --git a/src/clients/apparmor.rb b/src/clients/apparmor.rb index 1e6d7e3..7c2ed6f 100644 --- a/src/clients/apparmor.rb +++ b/src/clients/apparmor.rb @@ -70,6 +70,7 @@ module Yast [ # Selection box items Item(Id("apparmor-settings"), _("Settings"), true), + Item(Id("logprof"), _("Scan Audit logs")), Item(Id("AA_AddProfile"), _("Manually Add Profile")) ] ), diff --git a/src/clients/logprof.rb b/src/clients/logprof.rb new file mode 100644 index 0000000..95cbf5c --- /dev/null +++ b/src/clients/logprof.rb @@ -0,0 +1,94 @@ + +require "open3" +require "json" +require "yast" +require "apparmor/apparmor_ui_dialog" + +Yast.import "UI" +Yast.import "Label" +Yast.import "Popup" + +module AppArmor + class LogProf + LOGPROF = "/usr/sbin/aa-logprof" + attr_reader :logfile, :raw + include Yast::UIShortcuts + include Yast::Logger + include Yast::I18n + + def initialize(logfile="") + @logfile = logfile + log.info "Logfile is #{logfile}." + end + + def execute() + cmd = "#{LOGPROF} --json" + if @logfile.length > 0 + cmd += " -f #{@logfile}" + end + log.info "Executing #{cmd}" + IO.popen(cmd, "r+") do |f| + f.sync = true + f.each do | line | + log.info "aa-logprof lines #{line}." + if !line.start_with?("{") + next + end + hm = JSON.parse(line) + log.info "aa-logprof hashmap #{hm}." + l = get_dialog(hm) + r = l.run + if !r.nil? + f.puts r.to_json + f.flush + end + end + end + + Yast::UI.OpenDialog( + Opt(:decorated, :defaultsize), + VBox( + Label(_("No more records in logfile #{@logfile} to process")), + VSpacing(2), + HBox( + HStretch(), + HWeight(1, PushButton(Id(:ok), Yast::Label.OKButton)), + HStretch() + ) + ) + ) + Yast::UI.UserInput() + Yast::UI.CloseDialog() + end + + private + def get_dialog(hm) + case hm["dialog"] + when "yesno" + return YesNoDialog.new(hm) + when "yesnocancel" + return YesNoCancelDialog.new(hm) + when "info" + return InfoDialog.new(hm) + when "getstring" + return GetStringDialog.new(hm) + when "getfile" + return GetFileDialog.new(hm) + when "getstring" + return PromptDialog.new(hm) + when "promptuser" + return PromptDialog.new(hm) + when "apparmor-json-version" + return AAJSONVersion.new(hm) + else + Yast::Report.Error(_("Unknown Dialog %s returned by apparmor") % hm["dialog"]) + return nil + end + end + end +end + +#AppArmor::LogProf.new.execute +AppArmor::LogProf.new("/var/log/audit/audit.log").execute + + diff --git a/src/lib/apparmor/apparmor_ui_dialog.rb b/src/lib/apparmor/apparmor_ui_dialog.rb new file mode 100644 index 0000000..8045301 --- /dev/null +++ b/src/lib/apparmor/apparmor_ui_dialog.rb @@ -0,0 +1,243 @@ +# This file has all the dialog translations/representations +# which come from the apparmor's json +# show_respond() must return what would be communicated back to +# the program in the form of a hash map. It could be nil if no +# data must be written. + +require "yast" + +Yast.import "UI" +Yast.import "Label" +Yast.import "Report" +Yast.import "Popup" + +module AppArmor + class YesNoDialog + include Yast::UIShortcuts + include Yast::I18n + def initialize(hm) + @data = hm["text"] + @map = Hash.new + @map["dialog"] = "yesno" + end + + def run() + Yast::UI.OpenDialog( + Opt(:decorated, :defaultsize), + VBox( + Label(@data), + VSpacing(1), + HBox( + HStretch(), + HWeight(1, PushButton(Id(:y), _("Yes"))), + HSpacing(2), + HWeight(1, PushButton(Id(:n), _("No"))), + ) + ) + ) + case Yast::UI.UserInput + when :y + @map["response"] = "yes" + @map["response_key"] = "y" + when :n + @map["response"] = "no" + @map["response_key"] = "n" + end + Yast::UI.CloseDialog + return @map + end + end
how big data you expect? In general maybe easier way is to simply use Yast::Popup.YesNo http://www.rubydoc.info/github/yast/yast-yast2/Yast/PopupClass#YesNo-instanc...
+ + class YesNoCancelDialog + include Yast::UIShortcuts + include Yast::I18n + include Yast::Logger + def initialize(hm) + @data = hm["data"] + @map = Hash.new + @map["dialog"] = "yesnocancel" + end + + def run() + Yast::UI.OpenDialog( + Opt(:decorate, :defaultsize), + VBox( + Label(@data), + VSpacing(0.3), + HBox( + HWeight(1, PushButton(Id(:y), _("Yes"))), + HStretch(), + HWeight(1, PushButton(Id(:n), _("No"))), + HStretch(), + HWeight(1, PushButton(Id(:c), Label.CancelButton)) + ) + ) + ) + case Yast::UI.UserInput + when :y + @map["response"] = "yes" + @map["response_key"] = "y" + when :n + @map["response"] = "no" + @map["response_key"] = "n" + when :c + @map["response"] = "cancel" + @map["response_key"] = "c" + end + Yast::UI.CloseDialog + return @map + end + end
Here I would reuse Popup.AnyQuestion3 http://www.rubydoc.info/github/yast/yast-yast2/Yast%2FPopupClass:AnyQuestion...
+ + class InfoDialog + include Yast::UIShortcuts + include Yast::I18n + include Yast::Logger + def initialize(hm) + log.info "Hash map #{hm}" + end + def run() + return nil + end + end
Ugh, what? what is purpose of this dialog? It do nothing.
+ + class GetStringDialog + include Yast::UIShortcuts + include Yast::I18n + include Yast::Logger + def initialize(hm) + log.info "Hash map #{hm}" + @map = Hash.new + @map["dialog"] = "getstring" + end + def run() + Yast::UI.OpenDialog( + Opt(:decorate, :defaultsize), + VBox( + Inputfield(Id(:str), @text), + PushButton("&OK") + ) + ) + str = Yast::UI.QueryWidget(Id(:str), :Value) + Yast::UI.UserInput() + Yast::UI.CloseDialog() + @map["response"] = str + return @map + end + end + + class GetFileDialog + include Yast::UIShortcuts + include Yast::I18n + include Yast::Logger + def initialize(hm) + log.info "Hash map #{hm}" + @map = Hash.new + @map["dialog"] = "getfile" + end + def run() + filename = Yast::UI.AskForExistingFile("/etc/apparmor.d", "*", _("Choose a file")) + if !filename.nil? + @map["response"] = filename + end + return @map + end + + end + + class PromptDialog + include Yast::UIShortcuts + include Yast::I18n + include Yast::Logger + include Yast + def initialize(hm) + log.info "Hash map #{hm}" + @map = Hash.new + @map["dialog"] = "promptuser" + @title = hm["title"] + @headers = hm["headers"] + @explanation = hm["explanation"] + @options = hm["options"] + @menu_items = hm["menu_items"] + end + def run() + @map["selected"] = 0 + # Display the dynamic widget + + UI.OpenDialog( + Opt(:decorated, :defaultsize), + VBox( + *explanation_label, + *header_labels, + *options_radiobtns, + *menubtns + ) + ) + + @map["response_key"] = Yast::UI.UserInput() + selected = Yast::UI.QueryWidget(:options, :CurrentButton) + @map["selected"] = selected.to_i + return @map + end + private + + def explanation_label + box = VBox() + box[box.size] = VSpacing(1)
term have << method http://www.rubydoc.info/github/yast/yast-ruby-bindings/Yast/Term#%3C%3C-inst... but here. I suggest to simply have ``` box = VBox(VSpacing(1)) ```
+ if !@explanation.nil? + box[box.size] = Label(@explanation) and here
box << Label(@explanation) if @explanation
+ end + box + end + + def + box = VBox() + @headers.each do | key, value | + box[box.size] = Label(key.to_s + ": " + value.to_s) + box[box.size] = VSpacing(1)
same here. << operator is easier to read.
+ end + box
btw whole method can be done using each_with_object like ``` def header_labels @headers.each_with_object(VBox()) do |pair, result| key, value = pair result << Label(key.to_s + ": " + value.to_s) result << VSpacing(1) end end ```
+ end + + def options_radiobtns
typo here
+ box = VBox() + @options.each_with_index do | opt, i| + box[box.size] = RadioButton(Id(i.to_s), opt, i==0) + box[box.size] = VSpacing(1) + end + VBox(RadioButtonGroup(Id(:options), box)) + end + def menu_to_text_key(menu) + ind = menu.index('(') + key = menu[ind+1, 1].downcase + text = menu.delete('(').delete(')').delete('[').delete(']') + return text, key + end + def menubtns + box = HBox() + @menu_items.each do | menu | + text, key = menu_to_text_key(menu) + box[box.size] = HWeight(1, PushButton(Id(key.to_s), text)) + box[box.size] = HSpacing(1) + end + VBox(box) + end + end + + class AAJSONVersion + include Yast::I18n + include Yast::Logger + AA_JSON = 2.12
I expect this will break often in future. Is there any plans to have json somehow backward compatible?
+ def initialize(hm) + log.info "Hash map #{hm}" + @json_version = hm["data"].to_f + if (@json_version > AA_JSON) + Yast::Report.Error(_("Apparmor JSON version %s is greater than %0.2f" % [@json_version, AA_JSON])) + end + end + def run() + return nil + end + end +end +
And that is all, a lot of code, some parts looks really interesting. Good that you send it for early review. Do not hesitate to contact us on IRC if you have any questions. Josef -- To unsubscribe, e-mail: yast-devel+unsubscribe@opensuse.org To contact the owner, e-mail: yast-devel+owner@opensuse.org