From 3b36022e5a1056a28a83494b1ab8fab209399c2d Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Tue, 4 Feb 2020 21:09:42 -0800 Subject: [PATCH] add id parser --- README.md | 33 ++++- jc/cli.py | 2 + jc/parsers/id.py | 214 ++++++++++++++++++++++++++++++ tests/fixtures/centos-7.7/id.out | 1 + tests/fixtures/osx-10.14.6/id.out | 1 + 5 files changed, 250 insertions(+), 1 deletion(-) create mode 100644 jc/parsers/id.py create mode 100644 tests/fixtures/centos-7.7/id.out create mode 100644 tests/fixtures/osx-10.14.6/id.out diff --git a/README.md b/README.md index 276e2249..27574fca 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # JC JSON CLI output utility -`jc` is used to JSONify the output of many standard linux cli tools for easier parsing in scripts. See the **Parsers** section for supported commands. +`jc` is used to JSONify the output of many standard linux cli tools and file types for easier parsing in scripts. See the **Parsers** section for supported commands. This allows further command line processing of output with tools like `jq` simply by piping commands: @@ -74,6 +74,7 @@ jc PARSER [OPTIONS] - `--fstab` enables the `/etc/fstab` file parser - `--history` enables the `history` parser - `--hosts` enables the `/etc/hosts` file parser +- `--id` enables the `id` parser - `--ifconfig` enables the `ifconfig` parser - `--ini` enables the `ini` file parser - `--iptables` enables the `iptables` parser @@ -670,6 +671,36 @@ $ cat /etc/hosts | jc --hosts -p } ] ``` +### id +``` +$ id | jc --id -p + { + "uid": { + "id": 1000, + "name": "kbrazil" + }, + "gid": { + "id": 1000, + "name": "kbrazil" + }, + "groups": [ + { + "id": 1000, + "name": "kbrazil" + }, + { + "id": 10, + "name": "wheel" + } + ], + "context": { + "user": "unconfined_u", + "role": "unconfined_r", + "type": "unconfined_t", + "level": "s0-s0:c0.c1023" + } + } +``` ### ifconfig ``` $ ifconfig | jc --ifconfig -p diff --git a/jc/cli.py b/jc/cli.py index ad669b8a..fb39cdf2 100644 --- a/jc/cli.py +++ b/jc/cli.py @@ -18,6 +18,7 @@ import jc.parsers.free import jc.parsers.fstab import jc.parsers.history import jc.parsers.hosts +import jc.parsers.id import jc.parsers.ifconfig import jc.parsers.ini import jc.parsers.iptables @@ -57,6 +58,7 @@ parser_map = { '--fstab': jc.parsers.fstab, '--history': jc.parsers.history, '--hosts': jc.parsers.hosts, + '--id': jc.parsers.id, '--ifconfig': jc.parsers.ifconfig, '--ini': jc.parsers.ini, '--iptables': jc.parsers.iptables, diff --git a/jc/parsers/id.py b/jc/parsers/id.py new file mode 100644 index 00000000..6a844285 --- /dev/null +++ b/jc/parsers/id.py @@ -0,0 +1,214 @@ +"""jc - JSON CLI output utility id Parser + +Usage: + + specify --id as the first argument if the piped input is coming from id + +Compatibility: + + 'linux', 'darwin', 'aix', 'freebsd' + +Examples: + + $ id | jc --id -p + { + "uid": { + "id": 1000, + "name": "kbrazil" + }, + "gid": { + "id": 1000, + "name": "kbrazil" + }, + "groups": [ + { + "id": 1000, + "name": "kbrazil" + }, + { + "id": 10, + "name": "wheel" + } + ], + "context": { + "user": "unconfined_u", + "role": "unconfined_r", + "type": "unconfined_t", + "level": "s0-s0:c0.c1023" + } + } + + $ id | jc --id -p -r + { + "uid": { + "id": "1000", + "name": "kbrazil" + }, + "gid": { + "id": "1000", + "name": "kbrazil" + }, + "groups": [ + { + "id": "1000", + "name": "kbrazil" + }, + { + "id": "10", + "name": "wheel" + } + ], + "context": { + "user": "unconfined_u", + "role": "unconfined_r", + "type": "unconfined_t", + "level": "s0-s0:c0.c1023" + } + } +""" +import jc.utils + + +class info(): + version = '1.0' + description = 'id parser' + author = 'Kelly Brazil' + author_email = 'kellyjonbrazil@gmail.com' + # details = 'enter any other details here' + + # compatible options: linux, darwin, cygwin, win32, aix, freebsd + compatible = ['linux', 'darwin', 'aix', 'freebsd'] + + +__version__ = info.version + + +def process(proc_data): + """ + Final processing to conform to the schema. + + Parameters: + + proc_data: (dictionary) raw structured data to process + + Returns: + + Dictionary. Structured data with the following schema: + + { + "uid": { + "id": integer, + "name": string + }, + "gid": { + "id": integer, + "name": string + }, + "groups": [ + { + "id": integer, + "name": string + }, + { + "id": integer, + "name": string + } + ], + "context": { + "user": string, + "role": string, + "type": string, + "level": string + } + } + """ + if 'uid' in proc_data: + if 'id' in proc_data['uid']: + try: + proc_data['uid']['id'] = int(proc_data['uid']['id']) + except (ValueError): + proc_data['uid']['id'] = None + + if 'gid' in proc_data: + if 'id' in proc_data['gid']: + try: + proc_data['gid']['id'] = int(proc_data['gid']['id']) + except (ValueError): + proc_data['gid']['id'] = None + + if 'groups' in proc_data: + for group in proc_data['groups']: + if 'id' in group: + try: + group['id'] = int(group['id']) + except (ValueError): + group['id'] = None + + return proc_data + + +def parse(data, raw=False, quiet=False): + """ + Main text parsing function + + Parameters: + + data: (string) text data to parse + raw: (boolean) output preprocessed JSON if True + quiet: (boolean) suppress warning messages if True + + Returns: + + List of dictionaries. Raw or processed structured data. + """ + if not quiet: + jc.utils.compatibility(__name__, info.compatible) + + raw_output = {} + cleandata = data.split() + + # Clear any blank lines + cleandata = list(filter(None, cleandata)) + + if cleandata: + for section in cleandata: + if section.startswith('uid'): + uid_parsed = section.replace('(', '=').replace(')', '=') + uid_parsed = uid_parsed.split('=') + raw_output['uid'] = {} + raw_output['uid']['id'] = uid_parsed[1] + raw_output['uid']['name'] = uid_parsed[2] + + if section.startswith('gid'): + gid_parsed = section.replace('(', '=').replace(')', '=') + gid_parsed = gid_parsed.split('=') + raw_output['gid'] = {} + raw_output['gid']['id'] = gid_parsed[1] + raw_output['gid']['name'] = gid_parsed[2] + + if section.startswith('groups'): + groups_parsed = section.replace('(', '=').replace(')', '=') + groups_parsed = groups_parsed.replace('groups=', '') + groups_parsed = groups_parsed.split(',') + raw_output['groups'] = [] + + for group in groups_parsed: + group_dict = {} + grp_parsed = group.split('=') + group_dict['id'] = grp_parsed[0] + group_dict['name'] = grp_parsed[1] + raw_output['groups'].append(group_dict) + + if section.startswith('context'): + context_parsed = section.replace('context=', '') + context_parsed = context_parsed.split(':', maxsplit=3) + raw_output['context'] = {} + raw_output['context']['user'] = context_parsed[0] + raw_output['context']['role'] = context_parsed[1] + raw_output['context']['type'] = context_parsed[2] + raw_output['context']['level'] = context_parsed[3] + + if raw: + return raw_output + else: + return process(raw_output) diff --git a/tests/fixtures/centos-7.7/id.out b/tests/fixtures/centos-7.7/id.out new file mode 100644 index 00000000..d74f1c9f --- /dev/null +++ b/tests/fixtures/centos-7.7/id.out @@ -0,0 +1 @@ +uid=1000(kbrazil) gid=1000(kbrazil) groups=1000(kbrazil),10(wheel) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 diff --git a/tests/fixtures/osx-10.14.6/id.out b/tests/fixtures/osx-10.14.6/id.out new file mode 100644 index 00000000..86435954 --- /dev/null +++ b/tests/fixtures/osx-10.14.6/id.out @@ -0,0 +1 @@ +uid=501(kbrazil) gid=20(staff) groups=20(staff),501(access_bpf),12(everyone),61(localaccounts),79(_appserverusr),80(admin),81(_appserveradm),98(_lpadmin),703(com.apple.sharepoint.group.3),701(com.apple.sharepoint.group.1),702(com.apple.sharepoint.group.2),33(_appstore),100(_lpoperator),204(_developer),250(_analyticsusers),395(com.apple.access_ftp),398(com.apple.access_screensharing)