diff --git a/spuc/services/aws_handler.py b/spuc/services/aws_handler.py index 60ae78d..157c07f 100644 --- a/spuc/services/aws_handler.py +++ b/spuc/services/aws_handler.py @@ -4,10 +4,27 @@ def create_user(user_config, service_config): - user_config = \ - utils.convert_config_file(user_config)['aws'] - service_config = \ - utils.convert_config_file(service_config)['aws'] + if not user_config: + raise utils.SpucException('No user config was provided') + if not service_config: + raise utils.SpucException('No service config was provided') - client = boto3.client('iam', **service_config) - return client.create_user(UserName=user_config['UserName']) + service_config = utils.convert_config_file(service_config) + user_configs = utils.convert_config_file(user_config) + + try: + aws_service_config = service_config['aws'] + user_config = user_configs['aws'] + except KeyError as exception: + raise exception + + client = boto3.client('iam', **aws_service_config) + return [ + client.create_user( + UserName=user_config['UserName'] + ), + client.update_login_profile( + UserName=user_config['UserName'], + Password=user_config['Password'] + ) + ] diff --git a/spuc/services/gapps_handler.py b/spuc/services/gapps_handler.py index ee9e7d9..37822d0 100644 --- a/spuc/services/gapps_handler.py +++ b/spuc/services/gapps_handler.py @@ -1,23 +1,28 @@ -import httplib2 -from googleapiclient import discovery - from spuc import utils def create_user(user_config, service_config): - user_config = \ - utils.convert_config_file(user_config)['gapps'] - service_config = \ - utils.convert_config_file(service_config)['gapps'] + if not user_config: + raise utils.SpucException('No user config was provided') + if not service_config: + raise utils.SpucException('No service config was provided') - credentials = utils.get_oauth_credentials( - credential_config_dict=service_config, - scopes=utils.GOOGLE_SCOPES, - name_prefix='google' - ) + service_config = utils.convert_config_file(service_config) + user_configs = utils.convert_config_file(user_config) - http = credentials.authorize(httplib2.Http()) - service = discovery.build('admin', 'directory_v1', http=http) + try: + gapps_service_config = service_config['gapps'] + user_config = user_configs['gapps'] + except KeyError as exception: + raise exception + + service = utils.get_service( + gapps_service_config, + utils.GOOGLE_SCOPES, + 'admin', + 'directory_v1', + 'google' + ) result = service.users().insert(body=user_config).execute() return result diff --git a/spuc/services/gapps_script_handler.py b/spuc/services/gapps_script_handler.py new file mode 100644 index 0000000..11ebf6c --- /dev/null +++ b/spuc/services/gapps_script_handler.py @@ -0,0 +1,367 @@ +import json +import unicodedata + +import boto3 +from botocore import exceptions as botocore_exceptions +from googleapiclient import http +from jira import exceptions, JIRA + +import aws_handler +import gapps_handler +import github_handler +import jira_handler +from spuc import utils + +RESULT_INDEXES = { + 'username': 1, + 'first_name': 2, + 'last_name': 3, + 'job_description': 4, + 'mobile_number': 7, + 'direct_manager': 9, + 'country': 17, + 'email_domain': 18, + 'github_username': 19, + 'github_organization': 20, + 'created_email_addr': 25, + 'user_row': 26 +} +SERVICES_CREATE_METHODS = { + 'aws': aws_handler, + 'gapps': gapps_handler, + 'github': github_handler, + 'jira': jira_handler +} + +logger = utils.logger + + +def create_users(service_config, service_name): + service_config_holder = utils.convert_config_file(service_config) + gapps_config = service_config_holder['gapps'] + script_config = service_config_holder['script'] + endpoint_service_config = service_config_holder[service_name] + + response = \ + get_script_result( + gapps_config, + script_config, + service_name + )['response']['result'] + decoded_response = unicodedata.normalize('NFKD', response).encode( + 'ascii', + 'ignore') + user_details = json.loads(decoded_response) + user_configs = _construct_user_configs( + user_details, + endpoint_service_config, + service_name + ) + if len(user_configs) != 0: + counter = 0 + for user_config in user_configs: + + if service_name == 'github': + getattr(SERVICES_CREATE_METHODS[service_name], 'invite_user')( + {service_name: user_config}, + {service_name: endpoint_service_config} + ) + else: + getattr(SERVICES_CREATE_METHODS[service_name], 'create_user')( + {service_name: user_config}, + {service_name: endpoint_service_config} + ) + mark_as_created( + user_details[counter][RESULT_INDEXES['user_row']], + service_config, + script_config, + service_name + ) + if service_name == 'gapps': + save_created_email( + user_details[counter][RESULT_INDEXES['user_row']], + user_config['primaryEmail'], + gapps_config, + script_config + ) + counter += 1 + else: + logger.info('Nothing to create') + + return user_configs + + +def _construct_user_configs(user_details, service_config, service_name): + user_construct_methods = { + 'aws': _construct_aws_user_configs, + 'gapps': _construct_gapps_user_configs, + 'github': _construct_github_user_configs, + 'jira': _construct_jira_user_configs + } + + kwargs = { + 'user_details': user_details, + 'service_config': service_config + } + return user_construct_methods[service_name](**kwargs) + + +def _construct_aws_user_configs(**kwargs): + user_configs = [] + for user in kwargs['user_details']: + user_configs.append( + { + 'UserName': + _get_available_username( + first_name=user[RESULT_INDEXES['first_name']], + last_name=user[RESULT_INDEXES['last_name']], + service_config=kwargs['service_config'], + service_name='aws' + ), + 'Password': utils.gen_password() + } + ) + return user_configs + + +def _construct_gapps_user_configs(**kwargs): + user_configs = [] + for user in kwargs['user_details']: + user_configs.append({ + 'primaryEmail': _get_available_username( + first_name=user[RESULT_INDEXES['first_name']], + last_name=user[RESULT_INDEXES['last_name']], + domain=user[RESULT_INDEXES['email_domain']], + service_config=kwargs['service_config'], + service_name='gapps' + ), + 'name': { + 'givenName': user[RESULT_INDEXES['first_name']], + 'familyName': user[RESULT_INDEXES['last_name']], + 'fullName': '{0} {1}'.format( + user[RESULT_INDEXES['first_name']], + user[RESULT_INDEXES['last_name']]) + }, + 'relations': [ + { + 'value': user[RESULT_INDEXES['direct_manager']], + 'type': 'manager' + } + ], + 'phones': user[RESULT_INDEXES['mobile_number']], + 'addresses': + { + 'type': 'work', + 'country': user[RESULT_INDEXES['country']] + }, + 'password': utils.gen_password() + } + ) + return user_configs + + +def _construct_github_user_configs(**kwargs): + user_configs = [] + for user in kwargs['user_details']: + user_configs.append( + { + 'username': + user[RESULT_INDEXES['github_username']], + 'organization': + user[RESULT_INDEXES['github_organization']] + } + ) + return user_configs + + +def _construct_jira_user_configs(**kwargs): + user_configs = [] + for user in kwargs['user_details']: + user_configs.append( + { + 'username': + _get_available_username( + first_name=user[RESULT_INDEXES['first_name']], + last_name=user[RESULT_INDEXES['last_name']], + service_config=kwargs['service_config'], + service_name='jira' + ), + 'fullname': + user[RESULT_INDEXES['first_name']] + + ' ' + + user[RESULT_INDEXES['last_name']], + 'password': utils.gen_password(), + 'email': user[RESULT_INDEXES['created_email_addr']] + } + ) + return user_configs + + +def _get_available_username(first_name, last_name, service_config, + service_name, domain=None): + get_available_name_methods = { + 'aws': _get_available_aws_username, + 'gapps': _get_available_gapps_username, + 'jira': _get_available_jira_username + } + + kwargs = { + 'first_name': first_name, + 'last_name': last_name, + 'service_config': service_config, + 'domain': domain + } + + return get_available_name_methods[service_name](**kwargs) + + +def _get_available_aws_username(**kwargs): + desired_username = kwargs['first_name'] + client = boto3.client('iam', **kwargs['service_config']) + try: + client.get_user(UserName=desired_username) + for c in kwargs['last_name']: + desired_username += c + client.get_user(UserName=desired_username) + except botocore_exceptions.ClientError as e: + if e.response['Error']['Code'] == 'NoSuchEntity': + return desired_username + raise e + + raise Exception('No appropriate username was available') + + +def _get_available_gapps_username(**kwargs): + desired_username = kwargs['first_name'] + suffix = '@' + kwargs['domain'] + service = utils.get_service( + kwargs['service_config'], + utils.GOOGLE_SCOPES, + 'admin', + 'directory_v1', + 'google' + ) + cnt = 0 + try: + service.users().get( + userKey=desired_username + suffix).execute() + for c in kwargs['last_name']: + cnt += 1 + desired_username += c + service.users().get( + userKey=desired_username + suffix).execute() + except http.HttpError as e: + if e.resp['status'] == '404': + return desired_username + suffix + if e.resp['status'] == '403': + return _get_available_username( + desired_username, + kwargs['last_name'][cnt + 1:], + kwargs['domain'], + kwargs['service_config'] + ) + raise e + + raise Exception('No appropriate email address was available') + + +def _get_available_jira_username(**kwargs): + desired_username = kwargs['first_name'] + jira = JIRA(options=kwargs['service_config']['jira_options'], + basic_auth=( + kwargs['service_config']['username'], + kwargs['service_config']['password'] + )) + try: + jira.user(id=desired_username) + for c in kwargs['last_name']: + desired_username += c + jira.user(id=desired_username) + except exceptions.JIRAError as e: + if e.status_code == 404: + return desired_username + raise e + + raise Exception('No appropriate username was available') + + +def get_script_result(service_config, script_config, service_name): + service = utils.get_service( + service_config, + utils.GOOGLE_SCOPES, + 'script', + 'v1', + 'google' + ) + + request = { + 'function': script_config['get_api_method'], + 'parameters': service_name + } + + response = service.scripts().run( + body=request, + scriptId=script_config['id'] + ).execute() + + if 'error' in response: + # The API executed, but the script returned an error. + error = response['error']['details'][0] + raise Exception( + "Script error! Message: {0}".format(error['errorMessage'])) + + return response + + +def mark_as_created(user_row, service_config, script_config, service_name): + service = utils.get_service( + service_config, + utils.GOOGLE_SCOPES, + 'script', + 'v1', + 'google' + ) + + request = { + 'function': script_config['post_api_method'], + 'parameters': [user_row, service_name] + } + response = service.scripts().run( + body=request, + scriptId=script_config['id'] + ).execute() + + if 'error' in response: + # The API executed, but the script returned an error. + error = response['error']['details'][0] + raise Exception( + "Script error! Message: {0}".format(error['errorMessage'])) + + return response + + +def save_created_email(user_row, user_email, service_config, script_config): + service = utils.get_service( + service_config, + utils.GOOGLE_SCOPES, + 'script', + 'v1', + 'google' + ) + + request = { + 'function': script_config['save_email_api_method'], + 'parameters': [user_row, user_email] + } + response = service.scripts().run( + body=request, + scriptId=script_config['id'] + ).execute() + + if 'error' in response: + # The API executed, but the script returned an error. + error = response['error']['details'][0] + raise Exception( + "Script error! Message: {0}".format(error['errorMessage'])) + + return response diff --git a/spuc/services/github_handler.py b/spuc/services/github_handler.py index 4006e7b..e3658cf 100644 --- a/spuc/services/github_handler.py +++ b/spuc/services/github_handler.py @@ -4,22 +4,46 @@ def invite_user(user_config, service_config): - user_config = \ - utils.convert_config_file(user_config)['github'] - service_config = \ - utils.convert_config_file(service_config)['github'] - credentials = (service_config['username'], service_config['password']) + if not user_config: + raise utils.SpucException('No user config was provided') + if not service_config: + raise utils.SpucException('No service config was provided') + + service_config = utils.convert_config_file(service_config) + user_configs = utils.convert_config_file(user_config) + + try: + github_service_config = service_config['github'] + user_config = user_configs['github'] + credentials = ( + github_service_config['username'], + github_service_config['password'] + ) + user_organization = user_config['organization'] + username = user_config['username'] + except KeyError as exception: + raise exception response = requests.put( 'https://api.github.com/orgs/' '{0}/memberships/{1}'.format( - user_config['organization'], - user_config['username'] + user_organization, + username ), auth=credentials ) - return "Message: {0} | Status Code: {1}".format( - response.text, - response.status_code + status_code = response.status_code + if 300 > status_code >= 200 and response.text: + return "Message: {0} | Status Code: {1}".format( + response.text, + response.status_code + ) + + raise RuntimeError( + 'GitHub server responded with status {0}' + ' and the message: {1}'.format( + status_code, + response.text + ) ) diff --git a/spuc/services/jira_handler.py b/spuc/services/jira_handler.py index 6a9c751..596d696 100644 --- a/spuc/services/jira_handler.py +++ b/spuc/services/jira_handler.py @@ -4,14 +4,18 @@ def create_user(user_config, service_config): - user_config = \ - utils.convert_config_file(user_config)['jira'] - service_config = \ - utils.convert_config_file(service_config)['jira'] + service_config = utils.convert_config_file(service_config) + user_configs = utils.convert_config_file(user_config) - jira = JIRA(options=service_config['jira_options'], + try: + jira_service_config = service_config['jira'] + user_config = user_configs['jira'] + except KeyError as exception: + raise exception + + jira = JIRA(options=jira_service_config['jira_options'], basic_auth=( - service_config['username'], - service_config['password'] + jira_service_config['username'], + jira_service_config['password'] )) return jira.add_user(**user_config) diff --git a/spuc/spuc.py b/spuc/spuc.py index 689e7d5..6d38074 100644 --- a/spuc/spuc.py +++ b/spuc/spuc.py @@ -2,56 +2,150 @@ import logging import utils -from services import gapps_handler, jira_handler, aws_handler, github_handler +from services import gapps_handler, jira_handler, aws_handler, \ + github_handler, gapps_script_handler + +logger = utils.logger class Spuc(object): + SERVICES_MODULES = { + 'aws': aws_handler, + 'gapps': gapps_handler, + 'github': github_handler, + 'jira': jira_handler + } + def __init__(self, services_config, user_config): - self.user_config = utils.convert_config_file( - user_config) + self.user_configs = utils.convert_config_file(user_config) self.services_config = \ utils.convert_config_file(services_config) def create_all(self): - response = gapps_handler.create_user( - self.user_config['gapps'], - self.services_config['gapps'] - ) - logging.debug(response) - - response = jira_handler.create_user( - self.user_config['jira'], - self.services_config['jira'] - ) - logging.debug(response) - - response = aws_handler.create_user( - self.user_config['aws'], - self.services_config['aws'] - ) - logging.debug(response) - - response = github_handler.invite_user( - self.user_config['github'], - self.services_config['github'] - ) - logging.debug(response) + # GAPPS mailbox must be create first for the others to run + # service_names = ['gapps', 'aws', 'github', 'jira'] + + if self.user_configs: + logger.info('Creating users from config file...') + + def create_user(service_name): + logger.info('Creating user for the {0} service...'.format( + service_name) + ) + + users_config = self.user_configs[service_name] + if service_name == 'github': + for user_config in users_config: + getattr( + self.SERVICES_MODULES[service_name], + 'invite_user' + )( + {service_name: user_config}, + self.services_config + ) + else: + for user_config in users_config: + getattr( + self.SERVICES_MODULES[service_name], + 'create_user' + )( + {service_name: user_config}, + self.services_config + ) + + create_user('gapps') + create_user('aws') + create_user('github') + create_user('jira') + + else: + logger.info('Fetching users\' data from gapps script API...') + + self.user_configs = {} + + def create_user_from_script(service_name): + logger.info('Creating user for the {0} service...'.format( + service_name) + ) + + user_config = gapps_script_handler.create_users( + self.services_config, + service_name + ) + self.user_configs[service_name] = user_config + + create_user_from_script('gapps') + create_user_from_script('aws') + create_user_from_script('github') + create_user_from_script('jira') + + def print_passwords(self): + if self.user_configs: + if len(self.user_configs) != 0: + logger.info('Printing passwords...') + + for service_name, users_config in \ + self.user_configs.iteritems(): + for user_config in users_config: + password = \ + self._extract_password( + user_config, + service_name + ) + logger.info('\t{0} service: {1}'.format( + service_name, + password + )) + else: + logger.info('Nothing to do here') + else: + raise utils.SpucException('No user config was provided') + + def _extract_password(self, user_config, service_name): + methods = { + 'aws': self._extract_from_aws, + 'gapps': self._extract_from_gapps, + 'jira': self._extract_from_jira, + } + + if service_name == 'github': + return 'Invite has been sent' + + return methods[service_name](**user_config) + + @staticmethod + def _extract_from_aws(**kwargs): + return kwargs['Password'] + + @staticmethod + def _extract_from_gapps(**kwargs): + return kwargs['password'] + + @staticmethod + def _extract_from_jira(**kwargs): + return kwargs['password'] @click.group() def main(): pass + @main.command(name='create-all') @click.option('-c', '--config-path', help='The path to the config file.', required=True) @click.option('-u', '--user-config-path', - help='The path to the user yaml config file.', - required=True) -def create_all_users(config_path, user_config_path): + help='The path to the user yaml config file.') +@click.option('-p', '--without-passwords', + is_flag=True, + default=True, + help='The path to the user yaml config file.') +def create_all_users(config_path, user_config_path, without_passwords): spuc = Spuc(config_path, user_config_path) spuc.create_all() + if without_passwords: + spuc.print_passwords() @main.group() @@ -64,8 +158,7 @@ def gapps(): help='The path to the config file.', required=True) @click.option('-u', '--user-config-path', - help='The path to the user yaml config file.', - required=True) + help='The path to the user yaml config file.') def create_user_google(config_path, user_config_path): response = gapps_handler.create_user( user_config_path, @@ -84,8 +177,7 @@ def jira(): help='The path to the config file.', required=True) @click.option('-u', '--user-config-path', - help='The path to the user config file.', - required=True) + help='The path to the user config file.') def create_user_jira(config_path, user_config_path): response = jira_handler.create_user( user_config_path, @@ -105,8 +197,7 @@ def aws(): help='The path to the config file.', required=True) @click.option('-u', '--user-config-path', - help='The path to the user yaml config file.', - required=True) + help='The path to the user yaml config file.') def create_user_aws(config_path, user_config_path): response = aws_handler.create_user( user_config_path, @@ -125,8 +216,7 @@ def github(): help='The path to the config file.', required=True) @click.option('-u', '--user-config-path', - help='The path to the user config file.', - required=True) + help='The path to the user config file.') def invite_user_github(config_path, user_config_path): response = github_handler.invite_user( user_config_path, diff --git a/spuc/tests/test_aws_handler.py b/spuc/tests/test_aws_handler.py new file mode 100644 index 0000000..e69de29 diff --git a/spuc/tests/test_gapps_handler.py b/spuc/tests/test_gapps_handler.py new file mode 100644 index 0000000..e69de29 diff --git a/spuc/tests/test_gapps_script_handler.py b/spuc/tests/test_gapps_script_handler.py new file mode 100644 index 0000000..e69de29 diff --git a/spuc/tests/test_github_handler.py b/spuc/tests/test_github_handler.py new file mode 100644 index 0000000..e69de29 diff --git a/spuc/tests/test_jira_handler.py b/spuc/tests/test_jira_handler.py new file mode 100644 index 0000000..e69de29 diff --git a/spuc/tests/test_spuc.py b/spuc/tests/test_spuc.py new file mode 100644 index 0000000..e69de29 diff --git a/spuc/tests/test_user_creator.py b/spuc/tests/test_user_creator.py index 1235fba..0ef363a 100644 --- a/spuc/tests/test_user_creator.py +++ b/spuc/tests/test_user_creator.py @@ -1,17 +1,15 @@ import unittest from click.testing import CliRunner -from spuc import spuc +from spuc import spuc, services, utils class TestOAuth(unittest.TestCase): def test_get_credentials_with_correct_input(self): runner = CliRunner() result = runner.invoke(spuc.main, [ - 'gapps', - 'create', - '-c/home/david/development/creds/credentials.yaml', - '-u/home/david/development/yamls/google_user.yaml' + 'create-all', + '-c/home/david/development/creds/credentials.yaml' ]) assert result.exit_code == 0 @@ -24,7 +22,6 @@ def test_user_create_with_correct_input(self): 'jira', 'create', '-c/home/david/development/creds/credentials.yaml', - '-u/home/david/development/yamls/jira_user.yaml' ]) assert result.exit_code == 0 @@ -36,8 +33,35 @@ def test_user_create_with_correct_input(self): result = runner.invoke(spuc.main, [ 'aws', 'create', - '-c/home/david/development/creds/credentials.yaml', - '-u/home/david/development/yamls/aws_user.yaml' + '-c/home/david/development/creds/credentials.yaml' ]) assert result.exit_code == 0 + + +class TestGitHub(unittest.TestCase): + def test_user_create_with_correct_input(self): + runner = CliRunner() + result = runner.invoke(spuc.main, [ + 'github', + 'invite', + '-c/home/david/development/creds/credentials.yaml' + ]) + + assert result.exit_code == 0 + + +class TestDummy(unittest.TestCase): + def test_app_script_response(self): + service_config = utils.convert_file_to_yaml( + '/home/david/development/creds/credentials.yaml' + )['gapps'] + script_config = service_config.pop('script') + result = utils.mark_as_created( + '14', + service_config, + script_config + + ) + + print result diff --git a/spuc/tests/test_utils.py b/spuc/tests/test_utils.py new file mode 100644 index 0000000..e69de29 diff --git a/spuc/utils.py b/spuc/utils.py index 4d8e0ea..042ea46 100644 --- a/spuc/utils.py +++ b/spuc/utils.py @@ -1,14 +1,25 @@ import os import json + +import sys import yaml +import string +import random +import logging import tempfile +import httplib2 -from oauth2client import client from oauth2client import file from oauth2client import tools +from oauth2client import client +from googleapiclient import discovery APPLICATION_NAME = 'SPUC' -GOOGLE_SCOPES = 'https://www.googleapis.com/auth/admin.directory.user' +GOOGLE_SCOPES = [ + 'https://www.googleapis.com/auth/admin.directory.user', + 'https://www.googleapis.com/auth/drive', + 'https://www.googleapis.com/auth/spreadsheets' +] def get_oauth_credentials(credential_config_dict, scopes, name_prefix): @@ -41,13 +52,13 @@ def get_oauth_credentials(credential_config_dict, scopes, name_prefix): flow.user_agent = APPLICATION_NAME flags = tools.argparser.parse_args(args=[]) credentials = tools.run_flow(flow, store, flags) - print('Storing credentials to ' + credential_path) + logging.debug('Storing credentials to ' + credential_path) return credentials def create_credential_json(credential_dict): tmp_dir_path = tempfile.mkdtemp() - file_path = tmp_dir_path + 'spuc_secret.json' + file_path = tmp_dir_path + '_spuc_secret.json' with open(file_path, 'w') as output: json.dump(credential_dict, output) return file_path @@ -59,6 +70,46 @@ def convert_file_to_yaml(yaml_file_path): def convert_config_file(path): - if isinstance(path, basestring) and os.path.isfile(path): + if path and isinstance(path, basestring) and os.path.isfile(path): return convert_file_to_yaml(path) - return path \ No newline at end of file + return path + + +def gen_password(size=8, chars=string.ascii_letters + string.digits): + if size <= 0: + raise SpucException('Size must be greater than 0') + + return ''.join(random.choice(chars) for _ in range(size)) + + +def get_service(service_config, scope, service_name, version, name_prefix): + credentials = get_oauth_credentials( + credential_config_dict=service_config, + scopes=scope, + name_prefix=name_prefix + ) + + http = credentials.authorize(httplib2.Http()) + return discovery.build(service_name, version, http=http) + + +def get_logger(): + handler = logging.StreamHandler(sys.stdout) + formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s') + handler.setFormatter(formatter) + logger = logging.getLogger('spuc') + logger.addHandler(handler) + logger.setLevel(logging.INFO) + return logger + + +logger = get_logger() + + +class SpucException(Exception): + def __init__(self, message): + self.message = message + + def __str__(self): + return self.message