#!/usr/bin/env python
# Copyright 2019-2021 VMware, Inc. All rights reserved. -- VMware Confidential

import logging
import sys
import os
import warnings
import time
import featureState

from cis.utils import log, FileBuffer
from cis.defaults import get_component_home_dir
from cis.cisreglib import AuthzClient, CisregOptionParser, \
    LookupServiceClient, SsoClient, _get_syscfg_info
from cis.vecs import vmafd_machine_id, SsoGroup

CISREG_PATH = os.path.join(get_component_home_dir('cm'), 'bin')
ARCTIC_H5C_FSS = 'ARCTIC_H5C'

featureState.init(enableLogging=False)

sys.path.append(CISREG_PATH)
sys.path.append('/usr/lib/vmware-vmafd/lib64')

# Suppress warnings coming from vmafd and identity imports.
with warnings.catch_warnings():
    warnings.simplefilter('ignore', RuntimeWarning)
    import vmafdpy3
    from identity.vmkeystore import VmKeyStore

logger = logging.getLogger(__name__)

AUTHZ_TYPE_ID = 'cs.authorization'
EP_AUTHZ_PROTOCOL = 'vmomi'
EP_AUTHZ_TYPE_ID = 'com.vmware.cis.authorization.server'
VSPHERE_CLIENT_AUTHZ_ROLE = 'vSphere Client Solution User'
MACHINE_NAME = 'machine'
VSPHERE_CLIENT_ROLE_ID=1003
H5_CLIENT_SOLUTION_USER = "vsphere-webclient"
VSPHERE_CLIENT_SOLUTION_USERS_GROUP_ID = 'vSphereClientSolutionUsers'
EXISTING_ACT_AS_USERS_LINKED_GROUP_ID = 'ActAsUsers'

########################################################################
#  Methods intended to use by firstboot, B2B and Minor upgrade scripts
########################################################################

def create_vsphere_client_solution_users_sso_group():
   _log_info('create_vsphere_client_solution_users_sso_group - Create vsphere client solution users SSO group and add the solution user to the group')
   sso_group = _init_sso_group()
   machine_id = vmafd_machine_id()
   solution_user_id = '%s-%s' % (H5_CLIENT_SOLUTION_USER, machine_id)

   sso_group.create(VSPHERE_CLIENT_SOLUTION_USERS_GROUP_ID, 'vSphere Client Solution Users Group')
   _log_info('create_vsphere_client_solution_users_sso_group - Add solution user to the group')
   sso_group.add_user(VSPHERE_CLIENT_SOLUTION_USERS_GROUP_ID, solution_user_id)

def create_and_assign_vsphere_client_solution_user_role(config_dir, ready_only=False, is_vmc_gateway=False):
   vsphereclient_privileges_xml_file = os.path.join(config_dir, 'privileges.xml')
   _add_privileges(vsphereclient_privileges_xml_file)

   # Create the privileges for Arctic / breaking glass only if the feature is enabled.
   # TODO: when removing the FSS, merge the privileges into privileges.xml.
   if getattr(featureState, ARCTIC_H5C_FSS):
      _log_info('Adding privileges for Arctic')
      arctic_privileges_xml_file = os.path.join(config_dir, 'arctic_h5c-privileges.xml')
      _add_privileges(arctic_privileges_xml_file)

   vsphereclient_roles_xml_file = os.path.join(config_dir, 'roles.xml')
   _modify_role_privileges(vsphereclient_roles_xml_file)
   if ready_only and not is_vmc_gateway:
      ready_privileges_xml_file = os.path.join(config_dir, 'roles_removed_unready_privileges.xml')
      _remove_unready_privileges(vsphereclient_roles_xml_file, ready_privileges_xml_file)
      create_or_update_vsphereclient_role(ready_privileges_xml_file)
   else:
      create_or_update_vsphereclient_role(vsphereclient_roles_xml_file)

def create_or_update_vsphereclient_role(vsphereclient_roles_xml_file):
   _log_info('Adding following privilege set: %s to vSphere CLient Solution User role' % vsphereclient_roles_xml_file)
   authz_client, sso_client = _create_authz_client()
   machine_id = vmafd_machine_id()
   solution_user_id = '%s-%s' % (H5_CLIENT_SOLUTION_USER, machine_id)

   try:
      cisreg_options = {'permission.newRole': vsphereclient_roles_xml_file}

      cisreg_optparser = CisregOptionParser(cisreg_options)
      _log_info('create_or_update_vsphereclient_role - Logging into Authz')

      role_data_list = cisreg_optparser.get_roles()
      role_obj = authz_client.get_role(role_id=VSPHERE_CLIENT_ROLE_ID)
      if role_obj is None:
         _log_info('create_or_update_vsphereclient_role -Creating vSphere Client Solution User role in Authz')
         authz_client.load_roles(role_data_list)
      else:
         _log_info('create_or_update_vsphereclient_role - Updating vSphere Client Solution User role in Authz')
         for role_data in role_data_list:
            if int(role_data['id']) == VSPHERE_CLIENT_ROLE_ID:
               authz_client.update_role(role_id=VSPHERE_CLIENT_ROLE_ID, priv_ids=role_data['priv_ids'])
               break

      _log_info('create_or_update_vsphereclient_role - Assigning vSphere Client Solution User role to vsphere-webclient solution user')
      ls_url, domain_name = _get_syscfg_info()
      authz_client.set_permission(domain_name, VSPHERE_CLIENT_AUTHZ_ROLE,
         solution_user_id,is_group=False)

      _log_info('create_or_update_vsphereclient_role - Assigning vSphere Client Solution User role to vSphere Client Solution Users group')
      authz_client.set_permission(domain_name,VSPHERE_CLIENT_AUTHZ_ROLE,
         VSPHERE_CLIENT_SOLUTION_USERS_GROUP_ID,is_group=True)
   finally:
      sso_client.cleanup()

# Adds vSphereClientSolutionUsers group to the VC trusts, if there exists a link with other groups (for eg. ActAsUsers) and
# vSphereClientSolutionUsers group is not already present in the group map.
def add_vsphere_client_solution_user_group_to_vctrusts(vc_trusts):
   _log_info('add_vsphere_client_solution_user_group_to_vctrusts - fetching the VC trusts summary')
   summary_list = []
   for i in range(1, 5):
      try:
         summary_list = vc_trusts.list()
         break
      except Exception as ex:
         _log_info("Unable to fetch the VC trusts info. Retrying again in 60 secs - %s." %str(ex))
         time.sleep(60)
   if not summary_list:
      _log_info('add_vsphere_client_solution_user_group_to_vctrusts - VC trusts does not exist, skipping the update')
   for summary in summary_list:
      _update_group_map(vc_trusts, summary)

############
#  Helpers
############

def _add_privileges(vsphereclient_privileges_xml_file):
   authz_client, sso_client = _create_authz_client()

   try:
      cisreg_privileges_options = {'permission.newPrivilege': vsphereclient_privileges_xml_file}
      cisreg_privileges_optparser = CisregOptionParser(cisreg_privileges_options)
      authz_client.load_privs(cisreg_privileges_optparser.get_privs())
   finally:
      sso_client.cleanup()

def _create_authz_client():
   _log_info('_create_authz_client - Adding privileges')
   vmafdClient = vmafdpy3.client('localhost')
   local_nodeid = vmafdClient.GetLDU()
   _log_info('_create_authz_client - The LDU is '+ local_nodeid)
   _log_info('_create_authz_client - Connecting to Lookup Service')
   ls_url, domain_name = _get_syscfg_info()
   ls_obj = LookupServiceClient(ls_url, retry_count=1)
   _log_info('_create_authz_client - Getting STS endpoint')
   sts_url, sts_cert_data = ls_obj.get_sts_endpoint_data()
   cert = None
   key = None

   with VmKeyStore('VKS') as ks:
      ks.load(MACHINE_NAME)
      cert = ks.get_certificate(MACHINE_NAME)
      key = ks.get_key(MACHINE_NAME)

   sso_client = SsoClient(sts_url, sts_cert_data, None, None, cert=cert, key=key)

   ls_obj.set_sso_client(sso_client)
   authz_endpoints = ls_obj.get_service_endpoints(AUTHZ_TYPE_ID,
      ep_protocol=EP_AUTHZ_PROTOCOL, ep_type=EP_AUTHZ_TYPE_ID, local_nodeid=local_nodeid)
   authz_client = AuthzClient(authz_endpoints[0].url, sso_client)

   return authz_client, sso_client

def _init_sso_group():
   _log_info('init_sso_group - initializing sso group')
   vmafdClient = vmafdpy3.client('localhost')
   username = vmafdClient.GetMachineName()
   password = vmafdClient.GetMachinePassword()

   sso_group = SsoGroup(login=username, password=password)
   return sso_group

def _modify_role_privileges(vsphereclient_roles_xml_file):
   intercom_required = True

   roles_buff = FileBuffer()
   roles_buff.readFile(vsphereclient_roles_xml_file)
   changed = False
   if not intercom_required:
      _log_info('Intercom is disabled, removing the privileges which are not required.')
      roles_buff.findAndReplace("<privilegeId>IntercomNamespace.Read</privilegeId>", "")
      roles_buff.findAndReplace("<privilegeId>IntercomNamespace.Write</privilegeId>", "")
      changed = True

   if changed:
      roles_buff.writeFile(vsphereclient_roles_xml_file)

def _remove_unready_privileges(vsphereclient_roles_xml_file, ready_privileges_xml_file):
   roles_buff = FileBuffer()
   roles_buff.readFile(vsphereclient_roles_xml_file)

   # Remove privileges which depend on firstboots executed after the vsphere-ui firstboot
   # Most of this privileges  are added to the solution user in the vsphere-firstboot-wait.py script
   # since the wait script is executed after most of the firstboots and every needed privilege is
   # already created.
   # For example the vpxd creates the Tast.Create privilege and this privilege is needed for the
   # vSphere Client Solution User, but since the vpxd firstboot is executed after the vsphere-ui-firstboot
   # we need to assign this privilege to the solution user after the vpxd firstboot has succeeded which
   # is in the vsphere-ui-firstboot-wait.py script
   roles_buff.findAndReplace("<privilegeId>Task.Create</privilegeId>", "")
   roles_buff.findAndReplace("<privilegeId>Task.Update</privilegeId>", "")

   roles_buff.writeFile(ready_privileges_xml_file)

# Checks if there is a link already established between gateway and cloud
# (for eg. by checking the presence of ActAsUsers) and returns True, if the group map does
# not contain vSphereClientSolutionUsers group
def _update_group_map(vc_trusts, summary):
   _log_info('update_group_map - fetching group map')
   info = vc_trusts.get(summary.domain)
   group_map_values = []
   if info is None:
      _log_info('update_group_map - Not able to get the Vc trusts info for the domain - %s' % summary.label)
   else:
      group_map = info.group_map
      # summary object contains the summary information of each trusts in local node including domain id and label
      vsphere_client_group_key = VSPHERE_CLIENT_SOLUTION_USERS_GROUP_ID + '@' + summary.label
      existing_act_as_user_group_key = EXISTING_ACT_AS_USERS_LINKED_GROUP_ID + '@' + summary.label
      if existing_act_as_user_group_key in group_map and vsphere_client_group_key not in group_map:
         _log_info('update_group_map - Adding vSphere solution user group to the VC Trusts group map')
         spec = {}
         spec['group_map'] = group_map

         spec['group_map'][vsphere_client_group_key] = [VSPHERE_CLIENT_SOLUTION_USERS_GROUP_ID]
         vc_trusts.update(summary.domain, spec)
      else:
         _log_info('update_group_map - VC trusts group map already has vSphere solution user group or trust does not exist')

def _log_info(message):
   logger.info(message)
   log(message)
