malcontent/eos-parental-controls-client/eos-parental-controls-clien...

175 lines
6.4 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright © 2018 Endless Mobile, Inc.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
import argparse
import os
import pwd
import sys
import gi
gi.require_version('EosParentalControls', '0') # noqa
from gi.repository import EosParentalControls, GLib
# Exit codes, which are a documented part of the API.
EXIT_SUCCESS = 0
EXIT_INVALID_OPTION = 1
EXIT_PERMISSION_DENIED = 2
EXIT_PATH_NOT_ALLOWED = 3
def __get_app_filter(user_id, interactive):
"""Get the app filter for `user_id` off the bus.
If `interactive` is `True`, interactive polkit authorisation dialogues will
be allowed. An exception will be raised on failure."""
app_filter = None
exception = None
def __get_cb(obj, result, user_data):
nonlocal app_filter, exception
try:
app_filter = EosParentalControls.get_app_filter_finish(result)
except Exception as e:
exception = e
EosParentalControls.get_app_filter_async(
connection=None, user_id=user_id,
allow_interactive_authorization=interactive, cancellable=None,
callback=__get_cb, user_data=None)
context = GLib.MainContext.default()
while not app_filter and not exception:
context.iteration(True)
if exception:
raise exception
return app_filter
def __get_app_filter_or_error(user_id, interactive):
"""Wrapper around __get_app_filter() which prints an error and raises
SystemExit, rather than an internal exception."""
try:
return __get_app_filter(user_id, interactive)
except GLib.Error as e:
print('Error getting app filter for user {}: {}'.format(
user_id, e.message), file=sys.stderr)
raise SystemExit(EXIT_PERMISSION_DENIED)
def __lookup_user_id(user):
"""Convert a command-line specified username or ID into a user ID. If
`user` is empty, use the current user ID.
Raise KeyError if lookup fails."""
if user == '':
return os.getuid()
elif user.isdigit():
return int(user)
else:
return pwd.getpwnam(user).pw_uid
def __lookup_user_id_or_error(user):
"""Wrapper around __lookup_user_id() which prints an error and raises
SystemExit, rather than an internal exception."""
try:
return __lookup_user_id(user)
except KeyError:
print('Error getting ID for username {}'.format(user), file=sys.stderr)
raise SystemExit(EXIT_INVALID_OPTION)
def command_get(user, quiet=False, interactive=True):
"""Get the app filter for the given user."""
user_id = __lookup_user_id_or_error(user)
__get_app_filter_or_error(user_id, interactive)
print('App filter for user {} retrieved'.format(user_id))
def command_check(user, path, quiet=False, interactive=True):
"""Check the given path is runnable by the given user, according to their
app filter."""
user_id = __lookup_user_id_or_error(user)
app_filter = __get_app_filter_or_error(user_id, interactive)
path = os.path.abspath(path)
if app_filter.is_path_allowed(path):
print('Path {} is allowed by app filter for user {}'.format(
path, user_id))
return
else:
print('Path {} is not allowed by app filter for user {}'.format(
path, user_id))
raise SystemExit(EXIT_PATH_NOT_ALLOWED)
def main():
# Parse command line arguments
parser = argparse.ArgumentParser(
description='Query and update parental controls.')
subparsers = parser.add_subparsers(metavar='command',
help='command to run (default: get)')
parser.set_defaults(function=command_get)
parser.add_argument('-q', '--quiet', action='store_true',
help='output no informational messages')
parser.set_defaults(quiet=False)
# Common options for the subcommands which might need authorisation.
common_parser = argparse.ArgumentParser(add_help=False)
group = common_parser.add_mutually_exclusive_group()
group.add_argument('-n', '--no-interactive', dest='interactive',
action='store_false',
help='do not allow interactive polkit authorization '
'dialogues')
group.add_argument('--interactive', dest='interactive',
action='store_true',
help='opposite of --no-interactive')
common_parser.set_defaults(interactive=True)
# get command
parser_get = subparsers.add_parser('get', parents=[common_parser],
help='get current parental controls '
'settings')
parser_get.set_defaults(function=command_get)
parser_get.add_argument('user', default='', nargs='?',
help='user ID or username to get the app filter '
'for (default: current user)')
# check command
parser_check = subparsers.add_parser('check', parents=[common_parser],
help='check whether a path is '
'allowed by app filter')
parser_check.set_defaults(function=command_check)
parser_check.add_argument('user', default='', nargs='?',
help='user ID or username to get the app filter '
'for (default: current user)')
parser_check.add_argument('path',
help='path to a program to check')
# Parse the command line arguments and run the subcommand.
args = parser.parse_args()
args_dict = dict((k, v) for k, v in vars(args).items() if k != 'function')
args.function(**args_dict)
if __name__ == '__main__':
main()