"""Neo4j user management.""" import argparse import random import string import sys import config from dotenv import load_dotenv from neo4j import GraphDatabase from neo4j.exceptions import ClientError # Load env file if it exists load_dotenv(verbose=True) def create_user_query(user_name, password): """Create a Neo4j user query.""" return ( f"CREATE USER `{user_name}` SET PASSWORD '{password}'" ' CHANGE NOT REQUIRED' ) def rename_user_query(old_user_name, new_user_name): """Create a Neo4j rename user query.""" return ( f"RENAME USER `{old_user_name}` TO `{new_user_name}`" ) def reset_user_password_query(user_name, password): """Create a Neo4j user query.""" return ( f"ALTER USER `{user_name}` SET PASSWORD '{password}'" ) def delete_user_query(user_name): """Generate a Neo4j delete user query.""" return f'DROP USER `{user_name}`' def add_user_to_role_query(user_name, role_name): """Add a user to a role.""" return f'GRANT ROLE {role_name} TO `{user_name}`' def generate_complex_password(): """Generate a complex random password.""" password = '' while len(password) < config.PASSWORD_MIN_LENGTH: upper = random.choice(string.ascii_uppercase) lower = random.choice(string.ascii_lowercase) num = random.choice(string.digits) symbol = random.choice('#$^()') chars = upper + lower + num + symbol password += ''.join(random.sample(chars, len(chars))) print('Neo4j password is {}'.format(password)) return password def main(): """Enter main function.""" parser = argparse.ArgumentParser() parser.add_argument( '-a', '--action', required=False, default='create', help='Action to take. Choices are create, ' 'addrole, rename and delete. ' 'If not specified default is to create user.', ) parser.add_argument( '-u', '--user', required=True, help='Name of user to create/modify/delete', ) parser.add_argument( '-n', '--newname', required=False, help='New name of user to rename. If not specified, ' ) parser.add_argument( '-r', '--role', required=False, help='Name of role to associate with user. If not ' 'specified default will be used per environment.', ) args = parser.parse_args() role_name = args.role or config.NEO4J_DEFAULT_ROLE args.action in ['create', 'addrole', 'resetpassword', 'rename', 'delete'] or sys.exit( 'Action must be create or addrole or delete' ) if args.action in ['create', 'resetpassword']: # Generate a password password = generate_complex_password() uri = 'neo4j+ssc://{}'.format(config.NEO4J_CLUSTER_DNS_NAME) driver = GraphDatabase.driver( uri, auth=( config.NEO4J_CONNECTION_USER, config.NEO4J_CONNECTION_PASSWORD, ), ) try: with driver.session(database='system') as session: if args.action == 'create': session.run(create_user_query(args.user, password)) session.run(add_user_to_role_query(args.user, role_name)) elif args.action == 'addrole': session.run(add_user_to_role_query(args.user, role_name)) elif args.action == 'resetpassword': session.run(reset_user_password_query(args.user, password)) elif args.action == 'rename': if not args.newname: sys.exit('New name must be specified for rename action') session.run(rename_user_query(args.user, args.newname)) elif args.action == 'delete': session.run(delete_user_query(args.user)) print(f'{args.action} Neo4j user successful on {uri}') except ClientError as error: print(f"Neo4j client error: {error}", file=sys.stderr) return 1 except Exception as error: print(f"Unexpected error: {error}", file=sys.stderr) return 1 finally: driver.close() return 0 if __name__ == '__main__': sys.exit(main())