From: Goldwyn Rodrigues <rgold...@suse.com>

Provides json support to tools in order to interact with other
utilities such as Yast.

The JSON output is one per line, in order to differentiate between
multiple records. Each JSON record has a "dialog" entry which defines
the type of message passed. A response must contain the "dialog"
entry. "info" message does not require a response.

"apparmor-json-version" added in order to identify the communication
protocol version for future updates.

This is based on work done by Christian Boltz.

Signed-off-by: Goldwyn Rodrigues <rgold...@suse.com>

---
Changes since v1:
 - implementation of set_json_mode(), write_json()
 - Changed the way output is provided to keep input the same. This would
   write in either of two formats: text or json, but will keep input the same.
   This helps in keeping localizations in place. I so wish UI was a class..
 - Removed all yast calls.

Changes since v2:
 - use if UI_mode == json else, to make sure some output is returned in case of
   faulty UI_mode.
 - spelling correction yesnocancal
 - Added default_key
 - added dialog entry in all communication, to identify the kind of json output.

Changes since v3:
 - apparmor-json-version to identify communication protocol version
 - response to each commands presented in JSON format

Changes since v4:
 - Comments spacing
 - response error to print the entire JSON string received
 - Make sure the control goes through, for validation of ans and selected.

 utils/aa-genprof     |   4 ++
 utils/aa-logprof     |   5 ++
 utils/apparmor/ui.py | 129 +++++++++++++++++++++++++++++++++++++--------------
 3 files changed, 103 insertions(+), 35 deletions(-)

diff --git a/utils/aa-genprof b/utils/aa-genprof
index e2e65442..7776eaee 100755
--- a/utils/aa-genprof
+++ b/utils/aa-genprof
@@ -61,8 +61,12 @@ parser = argparse.ArgumentParser(description=_('Generate 
profile for the given p
 parser.add_argument('-d', '--dir', type=str, help=_('path to profiles'))
 parser.add_argument('-f', '--file', type=str, help=_('path to logfile'))
 parser.add_argument('program', type=str, help=_('name of program to profile'))
+parser.add_argument('-j', '--json', action="store_true", help=_('Input and 
Output in JSON'))
 args = parser.parse_args()
 
+if args.json:
+       aaui.set_json_mode()
+
 profiling = args.program
 profiledir = args.dir
 
diff --git a/utils/aa-logprof b/utils/aa-logprof
index c05cbef3..0ff37652 100755
--- a/utils/aa-logprof
+++ b/utils/aa-logprof
@@ -16,6 +16,7 @@ import argparse
 import os
 
 import apparmor.aa as apparmor
+import apparmor.ui as aaui
 
 # setup exception handling
 from apparmor.fail import enable_aa_exception_handler
@@ -29,8 +30,12 @@ parser = argparse.ArgumentParser(description=_('Process log 
entries to generate
 parser.add_argument('-d', '--dir', type=str, help=_('path to profiles'))
 parser.add_argument('-f', '--file', type=str, help=_('path to logfile'))
 parser.add_argument('-m', '--mark', type=str, help=_('mark in the log to start 
processing after'))
+parser.add_argument('-j', '--json', action='store_true', help=_('Input and 
Output in JSON'))
 args = parser.parse_args()
 
+if args.json:
+    aaui.set_json_mode()
+
 profiledir = args.dir
 logmark = args.mark or ''
 
diff --git a/utils/apparmor/ui.py b/utils/apparmor/ui.py
index f25fff31..0010f468 100644
--- a/utils/apparmor/ui.py
+++ b/utils/apparmor/ui.py
@@ -1,5 +1,7 @@
 # ----------------------------------------------------------------------
 #    Copyright (C) 2013 Kshitij Gupta <kgupta8...@gmail.com>
+#    Copyright (C) 2017 Christian Boltz <appar...@cboltz.de>
+#    Copyright (C) 2017 SUSE Linux
 #
 #    This program is free software; you can redistribute it and/or
 #    modify it under the terms of version 2 of the GNU General Public
@@ -11,6 +13,8 @@
 #    GNU General Public License for more details.
 #
 # ----------------------------------------------------------------------
+
+import json
 import sys
 import re
 import readline
@@ -24,14 +28,32 @@ _ = init_translation()
 # Set up UI logger for separate messages from UI module
 debug_logger = DebugLogger('UI')
 
-# The operating mode: yast or text, text by default
-UI_mode = 'text'
-
 # If Python3, wrap input in raw_input so make check passes
 if not 'raw_input' in dir(__builtins__): raw_input = input
 
 ARROWS = {'A': 'UP', 'B': 'DOWN', 'C': 'RIGHT', 'D': 'LEFT'}
 
+UI_mode = 'text'
+
+def write_json(jsonout):
+   print(json.dumps(jsonout, sort_keys=False, separators=(',', ': ')))
+   sys.stdout.flush()
+
+def set_json_mode():
+    global UI_mode
+    UI_mode = 'json'
+    jsonout = {'dialog': 'apparmor-json-version', 'data': '2.12'}
+    write_json(jsonout)
+
+# reads the response on command line for json and verifies the response
+# for the dialog type
+def json_response(dialog_type):
+    string = raw_input('\n')
+    rh = json.loads(string.strip())
+    if rh["dialog"] != dialog_type:
+        raise AppArmorException('Expected response %s got %s.' % (dialog_type, 
string))
+    return rh
+
 def getkey():
     key = readkey()
     if key == '\x1B':
@@ -44,12 +66,18 @@ def getkey():
 
 def UI_Info(text):
     debug_logger.info(text)
-    if UI_mode == 'text':
+    if UI_mode == 'json':
+        jsonout = {'dialog': 'info', 'data': text}
+        write_json(jsonout)
+    else:  # text mode
         sys.stdout.write(text + '\n')
 
 def UI_Important(text):
     debug_logger.debug(text)
-    if UI_mode == 'text':
+    if UI_mode == 'json':
+        jsonout = {'dialog': 'important', 'data': text}
+        write_json(jsonout)
+    else:  # text mode
         sys.stdout.write('\n' + text + '\n')
 
 def get_translated_hotkey(translated, cmsg=''):
@@ -67,14 +95,18 @@ def get_translated_hotkey(translated, cmsg=''):
 def UI_YesNo(text, default):
     debug_logger.debug('UI_YesNo: %s: %s %s' % (UI_mode, text, default))
     default = default.lower()
-    ans = None
-    if UI_mode == 'text':
-        yes = CMDS['CMD_YES']
-        no = CMDS['CMD_NO']
-        yeskey = get_translated_hotkey(yes).lower()
-        nokey = get_translated_hotkey(no).lower()
-        ans = 'XXXINVALIDXXX'
-        while ans not in ['y', 'n']:
+    yes = CMDS['CMD_YES']
+    no = CMDS['CMD_NO']
+    yeskey = get_translated_hotkey(yes).lower()
+    nokey = get_translated_hotkey(no).lower()
+    ans = 'XXXINVALIDXXX'
+    while ans not in ['y', 'n']:
+        if UI_mode == 'json':
+            jsonout = {'dialog': 'yesno', 'text': text, 'default': default}
+            write_json(jsonout)
+            hm = json_response('yesno')
+            ans = hm['response_key']
+        else:  # text mode
             sys.stdout.write('\n' + text + '\n')
             if default == 'y':
                 sys.stdout.write('\n[%s] / %s\n' % (yes, no))
@@ -102,18 +134,22 @@ def UI_YesNo(text, default):
 def UI_YesNoCancel(text, default):
     debug_logger.debug('UI_YesNoCancel: %s: %s %s' % (UI_mode, text, default))
     default = default.lower()
-    ans = None
-    if UI_mode == 'text':
-        yes = CMDS['CMD_YES']
-        no = CMDS['CMD_NO']
-        cancel = CMDS['CMD_CANCEL']
-
-        yeskey = get_translated_hotkey(yes).lower()
-        nokey = get_translated_hotkey(no).lower()
-        cancelkey = get_translated_hotkey(cancel).lower()
-
-        ans = 'XXXINVALIDXXX'
-        while ans not in ['c', 'n', 'y']:
+    yes = CMDS['CMD_YES']
+    no = CMDS['CMD_NO']
+    cancel = CMDS['CMD_CANCEL']
+
+    yeskey = get_translated_hotkey(yes).lower()
+    nokey = get_translated_hotkey(no).lower()
+    cancelkey = get_translated_hotkey(cancel).lower()
+
+    ans = 'XXXINVALIDXXX'
+    while ans not in ['c', 'n', 'y']:
+        if UI_mode == 'json':
+            jsonout = {'dialog': 'yesnocancel', 'text': text, 'default': 
default}
+            write_json(jsonout)
+            hm = json_response('yesnocancel')
+            ans = hm['response_key']
+        else:  # text mode
             sys.stdout.write('\n' + text + '\n')
             if default == 'y':
                 sys.stdout.write('\n[%s] / %s / %s\n' % (yes, no, cancel))
@@ -148,7 +184,11 @@ def UI_YesNoCancel(text, default):
 def UI_GetString(text, default):
     debug_logger.debug('UI_GetString: %s: %s %s' % (UI_mode, text, default))
     string = default
-    if UI_mode == 'text':
+    if UI_mode == 'json':
+        jsonout = {'dialog': 'getstring', 'text': text, 'default': default}
+        write_json(jsonout)
+        string = json_response('getstring')["response"]
+    else:  # text mode
         readline.set_startup_hook(lambda: readline.insert_text(default))
         try:
             string = raw_input('\n' + text)
@@ -161,15 +201,18 @@ def UI_GetString(text, default):
 def UI_GetFile(file):
     debug_logger.debug('UI_GetFile: %s' % UI_mode)
     filename = None
-    if UI_mode == 'text':
+    if UI_mode == 'json':
+        jsonout = {'dialog': 'getfile', 'text': file['description']}
+        write_json(jsonout)
+        filename = json_response('getfile')["response"]
+    else:  # text mode
         sys.stdout.write(file['description'] + '\n')
         filename = sys.stdin.read()
     return filename
 
 def UI_BusyStart(message):
     debug_logger.debug('UI_BusyStart: %s' % UI_mode)
-    if UI_mode == 'text':
-        UI_Info(message)
+    UI_Info(message)
 
 def UI_BusyStop():
     debug_logger.debug('UI_BusyStop: %s' % UI_mode)
@@ -254,8 +297,7 @@ class PromptQuestion(object):
     def promptUser(self, params=''):
         cmd = None
         arg = None
-        if UI_mode == 'text':
-            cmd, arg = self.Text_PromptUser()
+        cmd, arg = self.Text_PromptUser()
         if cmd == 'CMD_ABORT':
             confirm_and_abort()
             cmd = 'XXXINVALIDXXX'
@@ -324,6 +366,17 @@ class PromptQuestion(object):
         function_regexp += ')$'
 
         ans = 'XXXINVALIDXXX'
+        hdict = dict()
+        jsonprompt = {
+            'dialog': 'promptuser',
+            'title': title,
+            'headers': hdict,
+            'explanation': explanation,
+            'options': options,
+            'menu_items': menu_items,
+            'default_key': default_key,
+        }
+
         while not re.search(function_regexp, ans, flags=re.IGNORECASE):
 
             prompt = '\n'
@@ -335,6 +388,7 @@ class PromptQuestion(object):
                 while header_copy:
                     header = header_copy.pop(0)
                     value = header_copy.pop(0)
+                    hdict[header] = value
                     prompt += formatstr % (header + ':', value)
                 prompt += '\n'
 
@@ -352,9 +406,14 @@ class PromptQuestion(object):
 
             prompt += ' / '.join(menu_items)
 
-            sys.stdout.write(prompt + '\n')
-
-            ans = getkey().lower()
+            if UI_mode == 'json':
+                write_json(jsonprompt)
+                hm = json_response('promptuser')
+                ans = hm["response_key"]
+                selected = hm["selected"]
+            else:  # text mode
+                sys.stdout.write(prompt + '\n')
+                ans = getkey().lower()
 
             if ans:
                 if ans == 'up':
@@ -381,7 +440,7 @@ class PromptQuestion(object):
                         selected = ans - 1
                     ans = 'XXXINVALIDXXX'
 
-            if keys.get(ans, False) == 'CMD_HELP':
+            if keys.get(ans, False) == 'CMD_HELP' and UI_mode != 'json':
                 sys.stdout.write('\n%s\n' % helptext)
                 ans = 'again'
 
-- 
2.12.3


-- 
AppArmor mailing list
AppArmor@lists.ubuntu.com
Modify settings or unsubscribe at: 
https://lists.ubuntu.com/mailman/listinfo/apparmor

Reply via email to