f8148e7701
Adds JSON support. These patches can be removed when we update apparmor to contain JSON support. Until then, this is required for the smooth merge of yast-apparmor OBS-URL: https://build.opensuse.org/request/show/511034 OBS-URL: https://build.opensuse.org/package/show/security:apparmor/apparmor?expand=0&rev=177
308 lines
10 KiB
Diff
308 lines
10 KiB
Diff
commit aa95d7c9d4b0a7386ac52ad8dbcb28922198c8b7
|
|
Author: Goldwyn Rodrigues <rgoldwyn@suse.de>
|
|
Date: Thu Jun 15 18:22:43 2017 +0200
|
|
|
|
json support for logprof and genprof
|
|
|
|
From: Goldwyn Rodrigues <rgoldwyn@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 <rgoldwyn@suse.com>
|
|
|
|
|
|
Acked-by: Christian Boltz <apparmor@cboltz.de>
|
|
Acked-by: Seth Arnold <seth.arnold@canonical.com>
|
|
|
|
diff --git a/utils/aa-genprof b/utils/aa-genprof
|
|
index e2e65442..c9415e10 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 <kgupta8592@gmail.com>
|
|
+# Copyright (C) 2017 Christian Boltz <apparmor@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'
|
|
|