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]

Reply via email to