diff --git a/src/linkahead/utils/linkahead_admin.py b/src/linkahead/utils/linkahead_admin.py new file mode 100755 index 0000000000000000000000000000000000000000..0b6293c3a518578982cbf8f0edc52acbf6eb59ad --- /dev/null +++ b/src/linkahead/utils/linkahead_admin.py @@ -0,0 +1,636 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# ** header v3.0 +# This file is a part of the CaosDB Project. +# +# Copyright (C) 2018 Research Group Biomedical Physics, +# Max-Planck-Institute for Dynamics and Self-Organization Göttingen +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. +# +# ** end header +# + +"""A small caosdb client with a focus on administration of the server.""" + +from __future__ import print_function, unicode_literals + +import getpass +import sys +from argparse import ArgumentParser, RawDescriptionHelpFormatter + +import caosdb as db +from caosdb import administration as admin +from caosdb.exceptions import ClientErrorException + +__all__ = [] +__version__ = 0.3 +__date__ = '2016-09-19' +__updated__ = '2018-12-11' + + +def do_update_role(args): + """ + Update the description of a role. + + Allowed keyword arguments: + role_name: Name of the role to update + role_description: New description of the role + """ + admin._update_role(name=args.role_name, description=args.role_description) + + +def do_create_role(args): + admin._insert_role(name=args.role_name, description=args.role_description) + + +def do_retrieve_role(args): + print(admin._retrieve_role(name=args.role_name)) + + +def do_delete_role(args): + admin._delete_role(name=args.role_name) + + +def do_retrieve(args): + c = None + + if args.query: + if len(args.entities) > 1: + raise Exception("Only one query at a time can be retrieved.") + c = db.execute_query(args.entities[0], flags=eval(args.flags)) + else: + c = db.Container() + + for i in args.entities: + try: + eid = int(i) + c.append(db.Entity(id=eid)) + except ValueError: + c.append(db.Entity(name=i)) + c.retrieve() + print(c) + + +def do_update(args): + fdict = eval(args.flags) + xml = open(args.xml_path, "r") + ret = db.get_connection().update( + entity_uri_segment=["Entity"], reconnect=True, body=xml) + db.Container._response_to_entities(ret) + + +def do_delete(args): + c = db.Container() + + for i in args.entities: + c.append(db.Entity(id=i)) + + c.delete() + + +def do_insert(args): + fdict = eval(args.flags) + xml = open(args.xml_path, "r") + ret = db.get_connection().insert( + entity_uri_segment=["Entity"], + reconnect=True, + query_dict=fdict, + body=xml) + print(db.Container._response_to_entities(ret)) + + +def _promt_for_pw(): + password = getpass.getpass(prompt="Please type password: ") + password2 = getpass.getpass(prompt="Please type password again: ") + + if password != password2: + raise Exception("Password strings didn't match") + + return password + + +def do_create_user(args): + password = None + + if args.ask_password is True: + password = _promt_for_pw() + try: + admin._insert_user(name=args.user_name, + email=args.user_email, password=password) + except ClientErrorException as e: + print(e.msg) + + +def do_activate_user(args): + admin._update_user(name=args.user_name, status="ACTIVE") + + +def do_deactivate_user(args): + admin._update_user(name=args.user_name, status="INACTIVE") + + +def do_set_user_password(args): + password = _promt_for_pw() + admin._update_user(name=args.user_name, password=password, realm=args.realm) + + +def do_add_user_roles(args): + roles = admin._get_roles(user=args.user_name, realm=None) + + for r in args.user_roles: + roles.add(r) + admin._set_roles(user=args.user_name, roles=roles) + + +def do_remove_user_roles(args): + roles = admin._get_roles(user=args.user_name, realm=None) + + for r in args.user_roles: + if r in roles: + roles.remove(r) + admin._set_roles(user=args.user_name, roles=roles) + + +def do_set_user_entity(args): + admin._update_user(name=args.user_name, entity=args.user_entity) + + +def do_reset_user_entity(args): + admin._update_user(name=args.user_name, entity="") + + +def do_set_user_email(args): + admin._update_user(name=args.user_name, email=args.user_email) + + +def do_retrieve_user(args): + print(admin._retrieve_user(name=args.user_name)) + + +def do_delete_user(args): + admin._delete_user(name=args.user_name) + + +def do_retrieve_user_roles(args): + print(admin._get_roles(user=args.user_name)) + + +def do_retrieve_role_permissions(args): + print(admin._get_permissions(role=args.role_name)) + + +def do_grant_role_permissions(args): + perms = admin._get_permissions(args.role_name) + + for p in args.role_permissions: + g = admin.PermissionRule( + action="Grant", permission=p, priority=args.permissions_priority) + d = admin.PermissionRule( + action="Deny", permission=p, priority=args.permissions_priority) + + if g in perms: + perms.remove(g) + + if d in perms: + perms.remove(d) + perms.add(g) + admin._set_permissions(role=args.role_name, permission_rules=perms) + + +def do_revoke_role_permissions(args): + perms = admin._get_permissions(args.role_name) + + for p in args.role_permissions: + g = admin.PermissionRule( + action="Grant", permission=p, priority=args.permissions_priority) + d = admin.PermissionRule( + action="Deny", permission=p, priority=args.permissions_priority) + + if g in perms: + perms.remove(g) + + if d in perms: + perms.remove(d) + admin._set_permissions(role=args.role_name, permission_rules=perms) + + +def do_deny_role_permissions(args): + perms = admin._get_permissions(args.role_name) + + for p in args.role_permissions: + g = admin.PermissionRule( + action="Grant", permission=p, priority=args.permissions_priority) + d = admin.PermissionRule( + action="Deny", permission=p, priority=args.permissions_priority) + + if g in perms: + perms.remove(g) + + if d in perms: + perms.remove(d) + perms.add(d) + admin._set_permissions(role=args.role_name, permission_rules=perms) + + +def do_retrieve_entity_acl(args): + entities = db.execute_query(q=args.query, flags={"ACL": None}) + + for entity in entities: + print(entity.id) + print(entity.acl) + + +def do_action_entity_permissions(args): + entities = db.execute_query(q=args.query, flags={"ACL": None}) + + for entity in entities: + for p in args.permissions: + getattr(entity, args.action)(role=args.role, priority=args.priority, + permission=p) + entities.update(flags={"ACL": None}) + + for entity in entities: + print(entity.id) + print(entity.acl) + + +def main(argv=None): + """Command line options.""" + + if argv is None: + argv = sys.argv + else: + sys.argv.extend(argv) + + # program_name = os.path.basename(sys.argv[0]) + program_version = "v%s" % __version__ + program_build_date = str(__updated__) + program_version_message = '%%(prog)s %s (%s)' % ( + program_version, program_build_date) + program_shortdesc = __import__('__main__').__doc__ + program_license = '''%s + +USAGE +''' % (program_shortdesc) + + # Setup argument parser + parser = ArgumentParser(description=program_license, + formatter_class=RawDescriptionHelpFormatter) + parser.add_argument('-V', '--version', action='version', + version=program_version_message) + parser.add_argument("--auth-token", metavar="AUTH_TOKEN", + dest="auth_token", + help=("A CaosDB authentication token (default: None). " + "If the authentication token is passed, the " + "`password_method` of the connection is set to " + "`auth_token` and the respective configuration " + "from the pycaosdb.ini is effectively being " + "overridden.\nTODO: Also allow passing the token " + "via environmenty variables.")) + subparsers = parser.add_subparsers( + title="commands", + metavar="COMMAND", + description="You can invoke the following commands. Print the detailed help for each command with #> caosdb_admin COMMAND -h") + + # users (CRUD) + subparser = subparsers.add_parser( + "create_user", + help="Create a new user in caosdb's internal user database.") + subparser.set_defaults(call=do_create_user) + subparser.add_argument("-a", "--ask-password", + help="Prompt for a password.", action="store_true") + subparser.add_argument( + metavar='USERNAME', + dest="user_name", + help="A user name which is unique in the internal user database.") + subparser.add_argument( + metavar="EMAIL", + nargs='?', + dest="user_email", + help="The email address of the new user.") + + subparser = subparsers.add_parser( + "activate_user", help="(Re-)activate an inactive (but existing) user.") + subparser.set_defaults(call=do_activate_user) + subparser.add_argument(metavar='USERNAME', dest="user_name", + help="The name of the user who is to be activated.") + + subparser = subparsers.add_parser( + "deactivate_user", help="Deactivate an active user.") + subparser.set_defaults(call=do_deactivate_user) + subparser.add_argument( + metavar='USERNAME', + dest="user_name", + help="The name of the user who is to be deactivated.") + + subparser = subparsers.add_parser( + "set_user_password", + help="Set a new password for a user. The password is not to be given on the command line for security reasons. You will be prompted for the password.") + subparser.set_defaults(call=do_set_user_password) + subparser.add_argument( + metavar='USERNAME', + dest="user_name", + help="The name of the user who's password is to be set.") + subparser.add_argument( + metavar='REALM', + dest="realm", + nargs="?", + default=None, + help="The realm of the user who's password is to be set.") + + subparser = subparsers.add_parser( + "set_user_entity", + help="Associate a user with an existing entity (which should represent a person, a program, an organization or something similar).") + subparser.set_defaults(call=do_set_user_entity) + subparser.add_argument( + metavar='USERNAME', + dest="user_name", + help="The name of the user who's associated entity you want to set.") + subparser.add_argument(metavar='ENTITY', dest="user_entity", + help="An ID of an existing entity.") + + subparser = subparsers.add_parser( + "reset_user_entity", + help="Terminate the association of a user with an entity.") + subparser.set_defaults(call=do_reset_user_entity) + subparser.add_argument( + metavar='USERNAME', + dest="user_name", + help="The name of the user who's associated entity you want to reset.") + + subparser = subparsers.add_parser( + "set_user_email", help="Set a new email for a user.") + subparser.set_defaults(call=do_set_user_email) + subparser.add_argument( + metavar='USERNAME', + dest="user_name", + help="The name of the user who's email is to be set.") + subparser.add_argument( + metavar='EMAIL', + dest="user_email", + help="The name of the user who's email is to be set.") + + subparser = subparsers.add_parser( + "retrieve_user", help="Retrieve a user (email, entity)") + subparser.set_defaults(call=do_retrieve_user) + subparser.add_argument( + metavar='USERNAME', dest="user_name", help="The name of the user.") + + subparser = subparsers.add_parser( + "delete_user", + help="Delete a user from caosdb's internal user database.") + subparser.set_defaults(call=do_delete_user) + subparser.add_argument(metavar='USERNAME', dest="user_name", + help="The name of the user who is to be deleted.") + + # user roles + subparser = subparsers.add_parser( + "add_user_roles", help="Extend the roles of a user.") + subparser.set_defaults(call=do_add_user_roles) + subparser.add_argument( + metavar='USERNAME', + dest="user_name", + help="The name of the user who's roles are to be extended.") + subparser.add_argument( + metavar='ROLES', + dest="user_roles", + nargs='+', + help="A space separated list of (existing) roles.") + + subparser = subparsers.add_parser( + "remove_user_roles", help="Remove some of the roles of a user.") + subparser.set_defaults(call=do_remove_user_roles) + subparser.add_argument( + metavar='USERNAME', + dest="user_name", + help="The name of the user from whom you want to take some roles away.") + subparser.add_argument( + metavar='ROLES', + dest="user_roles", + nargs='+', + help="A space separated list of (existing) roles.") + + subparser = subparsers.add_parser( + "retrieve_user_roles", help="Retrieve a user's roles.") + subparser.set_defaults(call=do_retrieve_user_roles) + subparser.add_argument( + metavar='USERNAME', dest="user_name", help="The name of the user.") + + # role permissions + subparser = subparsers.add_parser( + "retrieve_role_permissions", + help="Retrieve the set of permission rules of a role.") + subparser.set_defaults(call=do_retrieve_role_permissions) + subparser.add_argument( + metavar='ROLE', + dest="role_name", + help="The name of the role which permissions are to be retrieved.") + + subparser = subparsers.add_parser( + "grant_role_permissions", help="Grant permissions to a role.") + subparser.set_defaults(call=do_grant_role_permissions) + subparser.add_argument( + '--priority', + dest="permissions_priority", + action="store_true", + default=False, + help="This flag enables priority permission rules.") + subparser.add_argument( + metavar='ROLE', + dest="role_name", + help="The name of the role to which the permissions are to be granted.") + subparser.add_argument( + metavar='PERMISSIONS', + dest="role_permissions", + nargs='+', + help="A space separated list of permissions.") + + subparser = subparsers.add_parser( + "revoke_role_permissions", + help="Remove previously granted or denied permissions from a role.") + subparser.set_defaults(call=do_revoke_role_permissions) + subparser.add_argument( + '--priority', + dest="permissions_priority", + action="store_true", + default=False, + help="This flag is needed to revoke priority permissions.") + subparser.add_argument( + metavar='ROLE', + dest="role_name", + help="The name of the role from which you want to revoke permissions.") + subparser.add_argument( + metavar='PERMISSIONS', + dest="role_permissions", + nargs='+', + help="A space separated list of permissions.") + + subparser = subparsers.add_parser( + "deny_role_permissions", help="Deny a role permissions.") + subparser.set_defaults(call=do_deny_role_permissions) + subparser.add_argument( + '--priority', + dest="permissions_priority", + action="store_true", + default=False, + help="This flag enables priority permission rules.") + subparser.add_argument( + metavar='ROLE', + dest="role_name", + help="The name of the role which you want to deny permissions.") + subparser.add_argument( + metavar='PERMISSIONS', + dest="role_permissions", + nargs='+', + help="A space separated list of permissions.") + + # entities (CRUD) + subparser = subparsers.add_parser("insert", help="Insert entities.") + subparser.set_defaults(call=do_insert) + subparser.add_argument( + '-f', + '--flags', + dest="flags", + help="A python dictionary (dict) with flag keys and their values.", + metavar="FLAGS", + default="{}") + subparser.add_argument(metavar='PATH', dest="xml_path", + help="Path to an xml file.") + + subparser = subparsers.add_parser("retrieve", help="Retrieve entities.") + subparser.set_defaults(call=do_retrieve) + subparser.add_argument( + '-f', + '--flags', + dest="flags", + help="A python dictionary (dict) with flag keys and their values.", + metavar="FLAGS", + default="{}") + subparser.add_argument('-q', '--query', dest='query', action="store_true", + help="If the ENTITIES argument is a query.") + subparser.add_argument(metavar='ENTITIES', dest="entities", nargs='+', + help="A space separated list of ids or names of" + "entities or ai single query.") + + subparser = subparsers.add_parser("update", help="Update entities.") + subparser.set_defaults(call=do_update) + subparser.add_argument( + '-f', + '--flags', + dest="flags", + help="A python dictionary (dict) with flag keys and their values.", + metavar="FLAGS", + default="{}") + subparser.add_argument(metavar='PATH', dest="xml_path", + help="Path to an xml file.") + + subparser = subparsers.add_parser("delete", help="Delete entities.") + subparser.set_defaults(call=do_delete) + subparser.add_argument( + '-f', + '--flags', + dest="flags", + help="A python dictionary (dict) with flag keys and their values.", + metavar="FLAGS", + default="{}") + subparser.add_argument( + metavar='ENTITIES', + dest="entities", + nargs='+', + help="A space separated list of ids or names of entities.") + + # roles (CRUD) + create_role_parser = subparsers.add_parser( + "create_role", help="Create a new role.") + create_role_parser.set_defaults(call=do_create_role) + create_role_parser.add_argument( + dest="role_name", metavar="ROLENAME", help="The name of the new role.") + create_role_parser.add_argument( + dest="role_description", + metavar="DESCRIPTION", + help="A description of the role's purpose, it's intended use case, characteristics of the users who have this role, etc.") + + retrieve_role_parser = subparsers.add_parser( + "retrieve_role", help="Retrieve the description of an existing role.") + retrieve_role_parser.set_defaults(call=do_retrieve_role) + retrieve_role_parser.add_argument( + dest="role_name", + metavar="ROLENAME", + help="The name of the existing role.") + + update_role_parser = subparsers.add_parser( + "update_role", help="Change the description of an existing role.") + update_role_parser.set_defaults(call=do_update_role) + update_role_parser.add_argument( + dest="role_name", + metavar="ROLENAME", + help="The name of the existing role.") + update_role_parser.add_argument( + dest="role_description", + metavar="DESCRIPTION", + help="A new description of the role's purpose, it's intended use case, characteristics of the users who have this role, etc.") + + delete_role_parser = subparsers.add_parser( + "delete_role", help="Delete a role.") + delete_role_parser.set_defaults(call=do_delete_role) + delete_role_parser.add_argument( + dest="role_name", + metavar="ROLENAME", + help="The name of the existing role.") + + # entity acl + retrieve_entity_acl_parser = subparsers.add_parser( + "retrieve_entity_acl", help="Retrieve an entity ACL.") + retrieve_entity_acl_parser.set_defaults(call=do_retrieve_entity_acl) + retrieve_entity_acl_parser.add_argument(dest="query", metavar="QUERY", + help="A FIND query.") + + for action in ["grant", "deny", "revoke_denial", "revoke_grant"]: + action_entity_permissions_parser = subparsers.add_parser( + "{}_entity_permissions".format(action), + help="{} entity permissions to a role.".format(action)) + action_entity_permissions_parser.set_defaults( + call=do_action_entity_permissions, action=action) + action_entity_permissions_parser.add_argument(dest="query", metavar="QUERY", + help="A FIND query.") + action_entity_permissions_parser.add_argument(dest="role", metavar="ROLE", + help="The name of an exising role.") + action_entity_permissions_parser.add_argument( + dest="permissions", + metavar="PERMISSION", + help="A list of permissions", + nargs='+') + action_entity_permissions_parser.add_argument( + '--priority', + dest="priority", + action="store_true", + default=False, + help="This flag enables priority permission rules.") + + # Process arguments + args = parser.parse_args() + auth_token = args.auth_token + if auth_token is not None: + db.configure_connection(password_method="auth_token", + auth_token=auth_token) + else: + db.configure_connection() + + return args.call(args) + + +if __name__ == "__main__": + sys.exit(main())