eos-parental-controls-client: Add simple client program
This allows querying of the parental controls for a given user (or the current user). Includes documentation but no tests yet. Signed-off-by: Philip Withnall <withnall@endlessm.com> https://phabricator.endlessm.com/T23859
This commit is contained in:
parent
1235c275eb
commit
63d229e653
|
@ -0,0 +1,120 @@
|
||||||
|
.\" Manpage for eos\-parental\-controls\-client.
|
||||||
|
.\" Documentation is under the same licence as the eos\-parental\-controls
|
||||||
|
.\" package.
|
||||||
|
.TH man 8 "03 Oct 2018" "1.0" "eos\-parental\-controls\-client man page"
|
||||||
|
.\"
|
||||||
|
.SH NAME
|
||||||
|
.IX Header "NAME"
|
||||||
|
eos\-parental\-controls\-client — Parental Controls Access Utility
|
||||||
|
.\"
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.IX Header "SYNOPSIS"
|
||||||
|
.\"
|
||||||
|
\fBeos\-parental\-controls\-client get [\-q] [\-n] [\fPUSER\fB]
|
||||||
|
.PP
|
||||||
|
\fBeos\-parental\-controls\-client check [\-q] [\-n] [\fPUSER\fB] \fPPATH\fB
|
||||||
|
.\"
|
||||||
|
.SH DESCRIPTION
|
||||||
|
.IX Header "DESCRIPTION"
|
||||||
|
.\"
|
||||||
|
\fBeos\-parental\-controls\-client\fP is a utility for querying and updating the
|
||||||
|
parental controls settings for users on the system. It will typically require
|
||||||
|
adminstrator access to do anything more than query the current user’s parental
|
||||||
|
controls.
|
||||||
|
.PP
|
||||||
|
It communicates with accounts-service, which stores parental controls data.
|
||||||
|
.PP
|
||||||
|
Its first argument is a command to run. Currently, the only supported commands
|
||||||
|
are \fBget\fP and \fBcheck\fP.
|
||||||
|
.\"
|
||||||
|
.SH \fBget\fP OPTIONS
|
||||||
|
.IX Header "get OPTIONS"
|
||||||
|
.\"
|
||||||
|
.IP "\fBUSER\fP"
|
||||||
|
Username or ID of the user to get the app filter for. If not specified, the
|
||||||
|
current user will be used by default.
|
||||||
|
.\"
|
||||||
|
.IP "\fB\-q\fP, \fB\-\-quiet\fP"
|
||||||
|
Only output error messages, and no informational messages, as the operation
|
||||||
|
progresses. (Default: Output informational messages.)
|
||||||
|
.\"
|
||||||
|
.IP "\fB\-n\fP, \fB\-\-no\-interactive\fP"
|
||||||
|
Do not allow interactive authorization with polkit. If this is needed to
|
||||||
|
complete the operation, the operation will fail. (Default: Allow interactive
|
||||||
|
authorization.)
|
||||||
|
.\"
|
||||||
|
.SH \fBcheck\fP OPTIONS
|
||||||
|
.IX Header "check OPTIONS"
|
||||||
|
.\"
|
||||||
|
.IP "\fBUSER\fP"
|
||||||
|
Username or ID of the user to get the app filter for. If not specified, the
|
||||||
|
current user will be used by default.
|
||||||
|
.\"
|
||||||
|
.IP "\fBPATH\fP"
|
||||||
|
Path to a program to check against the app filter, to see if it can be run by
|
||||||
|
the specified user.
|
||||||
|
.\"
|
||||||
|
.IP "\fB\-q\fP, \fB\-\-quiet\fP"
|
||||||
|
Only output error messages, and no informational messages, as the operation
|
||||||
|
progresses. (Default: Output informational messages.)
|
||||||
|
.\"
|
||||||
|
.IP "\fB\-n\fP, \fB\-\-no\-interactive\fP"
|
||||||
|
Do not allow interactive authorization with polkit. If this is needed to
|
||||||
|
complete the operation, the operation will fail. (Default: Allow interactive
|
||||||
|
authorization.)
|
||||||
|
.\"
|
||||||
|
.SH "ENVIRONMENT"
|
||||||
|
.IX Header "ENVIRONMENT"
|
||||||
|
.\"
|
||||||
|
\fBeos\-parental\-controls\-client\fP supports the standard GLib environment
|
||||||
|
variables for debugging. These variables are \fBnot\fP intended to be used in
|
||||||
|
production:
|
||||||
|
.\"
|
||||||
|
.IP \fI$G_MESSAGES_DEBUG\fP 4
|
||||||
|
.IX Item "$G_MESSAGES_DEBUG"
|
||||||
|
This variable can contain one or more debug domain names to display debug output
|
||||||
|
for. The value \fIall\fP will enable all debug output. The default is for no
|
||||||
|
debug output to be enabled.
|
||||||
|
.\"
|
||||||
|
.SH "EXIT STATUS"
|
||||||
|
.IX Header "EXIT STATUS"
|
||||||
|
.\"
|
||||||
|
\fBeos\-parental\-controls\-client\fP may return one of several error codes if it
|
||||||
|
encounters problems.
|
||||||
|
.\"
|
||||||
|
.IP "0" 4
|
||||||
|
.IX Item "0"
|
||||||
|
No problems occurred. The utility ran and successfully queried the app filter.
|
||||||
|
If running the \fBcheck\fP command, the given path was allowed to be run by the
|
||||||
|
given user.
|
||||||
|
.\"
|
||||||
|
.IP "1" 4
|
||||||
|
.IX Item "1"
|
||||||
|
An invalid option was passed to \fBeos\-parental\-controls\-client\fP on
|
||||||
|
startup.
|
||||||
|
.\"
|
||||||
|
.IP "2" 4
|
||||||
|
.IX Item "2"
|
||||||
|
The current user was not authorized to query the app filter for the given user.
|
||||||
|
.\"
|
||||||
|
.IP "3" 4
|
||||||
|
.IX Item "3"
|
||||||
|
If running the \fBcheck\fP command, the given path was \fInot\fP allowed to be
|
||||||
|
run by the given user.
|
||||||
|
.\"
|
||||||
|
.SH BUGS
|
||||||
|
.IX Header "BUGS"
|
||||||
|
.\"
|
||||||
|
Any bugs which are found should be reported on the project website:
|
||||||
|
.br
|
||||||
|
\fIhttps://support.endlessm.com/\fP
|
||||||
|
.\"
|
||||||
|
.SH AUTHOR
|
||||||
|
.IX Header "AUTHOR"
|
||||||
|
.\"
|
||||||
|
Endless Mobile, Inc.
|
||||||
|
.\"
|
||||||
|
.SH COPYRIGHT
|
||||||
|
.IX Header "COPYRIGHT"
|
||||||
|
.\"
|
||||||
|
Copyright © 2018 Endless Mobile, Inc.
|
|
@ -0,0 +1,174 @@
|
||||||
|
#!/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()
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Python program
|
||||||
|
install_data('eos-parental-controls-client.py',
|
||||||
|
install_dir: bindir,
|
||||||
|
install_mode: 'rwxr-xr-x',
|
||||||
|
rename: ['eos-parental-controls-client'],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
install_man('docs/eos-parental-controls-client.8')
|
||||||
|
|
||||||
|
# TODO subdir('tests')
|
|
@ -17,6 +17,7 @@ meson_make_symlink = join_paths(meson.source_root(), 'tools', 'meson-make-symlin
|
||||||
po_dir = join_paths(meson.source_root(), 'po')
|
po_dir = join_paths(meson.source_root(), 'po')
|
||||||
|
|
||||||
prefix = get_option('prefix')
|
prefix = get_option('prefix')
|
||||||
|
bindir = join_paths(prefix, get_option('bindir'))
|
||||||
datadir = join_paths(prefix, get_option('datadir'))
|
datadir = join_paths(prefix, get_option('datadir'))
|
||||||
|
|
||||||
# FIXME: This isn’t exposed in accountsservice.pc
|
# FIXME: This isn’t exposed in accountsservice.pc
|
||||||
|
@ -103,4 +104,5 @@ cc = meson.get_compiler('c')
|
||||||
add_project_arguments(cc.get_supported_arguments(test_c_args), language: 'c')
|
add_project_arguments(cc.get_supported_arguments(test_c_args), language: 'c')
|
||||||
|
|
||||||
subdir('accounts-service')
|
subdir('accounts-service')
|
||||||
|
subdir('eos-parental-controls-client')
|
||||||
subdir('libeos-parental-controls')
|
subdir('libeos-parental-controls')
|
Loading…
Reference in New Issue