On Wed, 7 Jun 2017 15:38:47 -0500 Goldwyn Rodrigues <[email protected]> wrote:
> From: Goldwyn Rodrigues <[email protected]> > > 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 <[email protected]> > --- > 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-instance_method > + > + 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:AnyQuestion3 > + > + 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-instance_method but here. I suggest to simply have ``` box = VBox(VSpacing(1)) ``` > + if [email protected]? > + 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: [email protected] To contact the owner, e-mail: [email protected]
