text: ###
  # Copyright 2016-2021 Hewlett Packard Enterprise, Inc. All rights reserved.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #  http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  ###
  
  # -*- coding: utf-8 -*-
  """ Add Federation Command for rdmc """
  
  import json
  import getpass
  
  from argparse import Action, RawDescriptionHelpFormatter
  
  from redfish.ris.ris import SessionExpired
  from redfish.ris.rmc_helper import IdTokenError
  
  from rdmc_helper import ReturnCodes, InvalidCommandLineError, ResourceExists,\
                  InvalidCommandLineErrorOPTS, NoContentsFoundForOperationError,\
                  IncompatibleiLOVersionError, UsernamePasswordRequiredError, Encryption
  
  __subparsers__ = ['add', 'modify', 'changekey', 'delete']
  
  class _FederationParse(Action):
      def __init__(self, option_strings, dest, nargs, **kwargs):
          super(_FederationParse, self).__init__(option_strings, dest, nargs, **kwargs)
      def __call__(self, parser, namespace, values, option_strings):
          """ Federation privileges option helper"""
  
          privkey = {1: 'LoginPriv', 2: 'RemoteConsolePriv', 3:'UserConfigPriv', 4:'iLOConfigPriv',
                     5: 'VirtualMediaPriv', 6: 'VirtualPowerAndResetPriv', 7: 'HostNICConfigPriv',
                     8: 'HostBIOSConfigPriv', 9: 'HostStorageConfigPriv', 10: 'SystemRecoveryConfigPriv'}
  
          for priv in next(iter(values)).split(','):
              try:
                  priv = int(priv)
              except ValueError:
                  try:
                      parser.error("Invalid privilege entered: %s. Privileges must " \
                                             "be numbers." % priv)
                  except:
                      raise InvalidCommandLineErrorOPTS("")
  
              try:
                  if not isinstance(namespace.optprivs, list):
                      namespace.optprivs = list()
                  if option_strings.startswith('--add'):
                      namespace.optprivs.append({privkey[priv]: True})
                  elif option_strings.startswith('--remove'):
                      namespace.optprivs.append({privkey[priv]: False})
              except KeyError:
                  try:
                      parser.error("Invalid privilege entered: %s. Number does not " \
                                             "match an available privlege." % priv)
                  except:
                      raise InvalidCommandLineErrorOPTS("")
  
  class IloFederationCommand():
      """ Add a new ilo federation to the server """
      def __init__(self):
          self.ident = {
              'name':'ilofederation',
              'usage': None,
              'description':'View, Add, Remove and Modify iLO Federation accoutns based on the '
                            'sub-command used.\nTo view help on specific sub-commands run: ilofederation '
                            '<sub-command> -h\n\nExample: ilofederation add -h\n'
                            'NOTE 1: By default only the login privilege is added to the newly created\n\t\t'
                            'federation group with role "ReadOnly" in iLO 5 and no privileges in iLO 4.\n\t'
                            'NOTE 2: Federation credentials are case-sensitive.',
              'summary':'Adds / deletes an iLO federation group on the currently logged in server.',
              'aliases': [],
              'auxcommands': []
          }
  
          self.cmdbase = None
          self.rdmc = None
          self.auxcommands = dict()
  
      def run(self, line, help_disp=False):
          """ Main addfederation function
          :param line: string of arguments passed in
          :type line: str.
          :param help_disp: display help flag
          :type line: bool.
          """
          if help_disp:
              self.parser.print_help()
              return ReturnCodes.SUCCESS
          body = dict()
          try:
              ident_subparser = False
              for cmnd in __subparsers__:
                  if cmnd in line:
                      (options, args) = self.rdmc.rdmc_parse_arglist(self, line)
                      ident_subparser = True
                      break
              if not ident_subparser:
                  (options, args) = self.rdmc.rdmc_parse_arglist(self, line, default=True)
          except (InvalidCommandLineErrorOPTS, SystemExit):
              if ("-h" in line) or ("--help" in line):
                  return ReturnCodes.SUCCESS
              else:
                  raise InvalidCommandLineErrorOPTS("")
  
          self.ilofederationvalidation(options)
  
          redfish = self.rdmc.app.monolith.is_redfish
          path = self.rdmc.app.typepath.defs.federationpath
          results = self.rdmc.app.get_handler(path, service=True, silent=True).dict
  
          if redfish:
              results = results['Members']
          else:
              if 'Member' in results['links']:
                  results = results['links']['Member']
              else:
                  results = list()
  
          if not results:
              raise NoContentsFoundForOperationError("No iLO Federation accounts were found.")
  
          mod_fed = None
          path = None
          for indx, acct in enumerate(results):
              fed = self.rdmc.app.get_handler(acct[self.rdmc.app.typepath.defs.hrefstring],
                                               service=True, silent=True).dict
              try:
                  if hasattr(options, 'fedname'):
                      if fed['Name'].lower() == options.fedname.lower():
                          if redfish:
                              path = acct['@odata.id']
                          else:
                              path = acct['links']['self']['href']
                          mod_fed = fed
                  elif options.command == 'default':
                      results[indx] = fed
                  else:
                      raise KeyError
              except KeyError:
                  continue
              else:
                  if mod_fed:
                      break
  
          if mod_fed:
              if options.command.lower() == 'add':
                  raise ResourceExists('Federation name %s is already in use.' % options.fedname)
          else:
              if options.command.lower() != 'add' and options.command.lower() != 'default':
                  raise InvalidCommandLineError("Unable to find the specified federation %s." % options.fedname)
  
          if options.command.lower() == 'add':
              privs = self.getprivs(options)
              path = self.rdmc.app.typepath.defs.federationpath
  
              body = {"Name": options.fedname, "Key": options.fedkey}
              if privs:
                  body.update({"Privileges": privs})
              self.addvalidation(options.fedname, options.fedkey, results)
  
              if path and body:
                  resp = self.rdmc.app.post_handler(path, body, silent=False, service=False)
  
              if resp and resp.dict:
                  if 'resourcealreadyexist' in str(resp.dict).lower():
                      raise ResourceExists('')
  
          elif options.command.lower() == 'changekey':
  
              try:
                  name = options.fedname
                  newkey = options.fedkey
              except:
                  raise InvalidCommandLineError('Invalid number of parameters.')
  
              body = {'Key': newkey}
  
              if path and body:
                  self.rdmc.app.patch_handler(path, body, service=True)
              else:
                  raise NoContentsFoundForOperationError('Unable to find '
                                                         'the specified federation.')
          elif options.command.lower() == 'modify':
  
              if options.optprivs:
                  body.update({'Privileges': {}})
                  if any(priv for priv in options.optprivs if 'SystemRecoveryConfigPriv' in priv) \
                                                          and 'SystemRecoveryConfigPriv' not in \
                                                          list(self.getsesprivs().keys()):
                      raise IdTokenError("The currently logged in federation must have The System "
                                         "Recovery Config privilege to add the System Recovery "
                                         "Config privilege.")
                  privs = self.getprivs(options)
                  body['Privileges'] = privs
  
              self.rdmc.app.patch_handler(path, body)
  
          elif options.command.lower() == 'delete':
              if path:
                  self.rdmc.app.delete_handler(path)
          else:
              self.rdmc.ui.printer("iLO Federation Id list with Privileges:\n")
              if options.json:
                  outdict = dict()
                  for fed in sorted(results, key=lambda k: k['Name']):
                      outdict[fed['Name']] = fed['Privileges']
                  self.rdmc.ui.print_out_json_ordered(str(json.dumps(outdict, indent=2)))
              else:
                  for fed in sorted(results, key=lambda k: k['Name']):
                      privstr = ""
                      privs = fed['Privileges']
                      for priv in privs:
                          privstr += priv + '=' + str(privs[priv]) + '\n'
                      self.rdmc.ui.printer("\nName=%s:\n%s" % (fed['Name'], privstr))
  
          self.cmdbase.logout_routine(self, options)
          #Return code
          return ReturnCodes.SUCCESS
  
      def getprivs(self, options):
          """ find and return the privileges to set
          :param options: command line options
          :type options: list.
          """
          sesprivs = self.getsesprivs()
          setprivs = {}
          availableprivs = self.getsesprivs(availableprivsopts=True)
  
          if not 'UserConfigPriv' in list(sesprivs.keys()):
                  raise IdTokenError("The currently logged in federation does not have the Config"
                                     "Privilege and cannot add or modify federations.")
  
          if options.optprivs:
              for priv in options.optprivs:
                  priv = next(iter(list(priv.keys())))
                  if not options.user or not options.password:
                      if priv == "SystemRecoveryConfigPriv" and self.rdmc.app.current_client.base_url == "blobstore://.":
                          raise UsernamePasswordRequiredError("Privilege %s need username and password to be specified." % priv)
                  if priv not in availableprivs:
                      raise IncompatibleiLOVersionError("Privilege %s is not available on this "\
                                                        "iLO version." % priv)
  
              if all(priv.values() for priv in options.optprivs):
                  if any(priv for priv in options.optprivs if 'SystemRecoveryConfigPriv' in priv) and\
                                              'SystemRecoveryConfigPriv' not in sesprivs.keys():
                      raise IdTokenError("The currently logged in account must have The System "
                                         "Recovery Config privilege to add the System Recovery "
                                         "Config privilege.")
                  else:
                      setprivs = {}
              for priv in options.optprivs:
                  setprivs.update(priv)
  
          return setprivs
  
      def getsesprivs(self, availableprivsopts=False):
          """Finds and returns the current session's privileges
          :param availableprivsopts: return available privileges
          :type availableprivsopts: boolean.
          """
          if self.rdmc.app.current_client:
              sespath = self.rdmc.app.current_client.session_location
              sespath = self.rdmc.app.current_client.default_prefix + \
                                  sespath.split(self.rdmc.app.current_client.\
                                                default_prefix)[-1]
  
              ses = self.rdmc.app.get_handler(sespath, service=False, silent=True)
  
              if ses.status != 200:
                  raise SessionExpired("Invalid session. Please logout and "
                                       "log back in or include credentials.")
  
              sesprivs = {'HostBIOSConfigPriv': True, 'HostNICConfigPriv': True, 'HostStorageConfigPriv': True,
                          'LoginPriv': True, 'RemoteConsolePriv': True, 'SystemRecoveryConfigPriv': True,
                          'UserConfigPriv': True, 'VirtualMediaPriv': True, 'VirtualPowerAndResetPriv': True, 'iLOConfigPriv': True}
              if 'Oem' in ses.dict:
                  sesoemhp = ses.dict['Oem'][self.rdmc.app.typepath.defs.oemhp]
                  if 'Privileges' in list(sesoemhp.keys()):
                      sesprivs = sesoemhp['Privileges']
              availableprivs = list(sesprivs.keys())
              keepprivs = dict()
              for priv, val in sesprivs.items():
                  if val:
                      keepprivs[priv] = sesprivs[priv]
              sesprivs = keepprivs
          else:
              sesprivs = None
  
          if availableprivsopts:
              return availableprivs
          else:
              return sesprivs
  
      def addvalidation(self, username, key, feds):
          """ add validation function
          :param username: username to be added
          :type username: str.
          :param key: key to be added
          :type key: str.
          :param feds: list of federation accounts
          :type feds: list.
          """
  
          if len(username) >= 32:
              raise InvalidCommandLineError('User name exceeds maximum length.')
          elif len(key) >= 32 or len(key) <= 7:
              raise InvalidCommandLineError('Password is invalid length.')
  
      def ilofederationvalidation(self, options):
          """ addfederation validation function
          :param options: command line options
          :type options: list.
          """
          self.cmdbase.login_select_validation(self, options)
  
      @staticmethod
      def options_addprivs_argument_group(parser):
          """ Define optional arguments group
          :param parser: The parser to add the addprivs option group to
          :type parser: ArgumentParser/OptionParser
          """
          group = parser.add_argument_group('GLOBAL OPTION:', 'Option(s) are available for'
                                                              ' all arguments within the scope of this subcommand.')
  
          parser.add_argument(
              '--addprivs',
              dest='optprivs',
              nargs='*',
              action=_FederationParse,
              type=str,
              help="Optionally include this flag if you wish to specify "\
              "which privileges you want added to the iLO federation. This overrides the default of "\
              "duplicating privileges of the currently logged in federation on the new federation. "\
              "Pick privileges from the privilege list in the above help text. EX: --addprivs=1,2,4",
              default=None
          )
  
      @staticmethod
      def options_removeprivs_argument_group(parser):
          """ Additional argument
          :param parser: The parser to add the removeprivs option group to
          :type parser: ArgumentParser/OptionParser
          """
  
          group = parser.add_argument_group('GLOBAL OPTION:', 'Option(s) are available'
                                                              'for all arguments within the scope of this command.')
          parser.add_argument(
              '--removeprivs',
              dest='optprivs',
              nargs='*',
              action=_FederationParse,
              type=str,
              help="Optionally include this flag if you wish to specify "\
              "which privileges you want removed from the iLO federation. This overrides the default"\
              " of duplicating privileges of the currently logged in federation on the new "\
              "federation. Pick privileges from the privilege list in the above help text. "\
              "EX: --removeprivs=1,2,4",
              default=None
          )
  
      def definearguments(self, customparser):
          """ Wrapper function for new command main function
          :param customparser: command line input
          :type customparser: parser.
          """
          if not customparser:
              return
  
          self.cmdbase.add_login_arguments_group(customparser)
          subcommand_parser = customparser.add_subparsers(dest='command')
          privilege_help='\n\nPRIVILEGES:\n\t1: Login\n\t2: Remote Console\n\t3: User Config\n\t4:'\
              ' iLO Config\n\t5: Virtual Media\n\t6: Virtual Power and Reset\n\n\tiLO 5 added '\
              'privileges:\n\t7: Host NIC Config\n\t8: Host Bios Config\n\t9: Host Storage Config'\
              '\n\t10: System Recovery Config'
          #default sub-parser
          default_parser = subcommand_parser.add_parser(
              'default',
              help='Running without any sub-command will return all federation group information '\
              ' on the currently logged in server.'
          )
          default_parser.add_argument(
              '-j',
              '--json',
              dest='json',
              action="store_true",
              help="Optionally include this flag if you wish to change the"\
              " displayed output to JSON format. Preserving the JSON data"\
              " structure makes the information easier to parse.",
              default=False
          )
          self.cmdbase.add_login_arguments_group(default_parser)        #add sub-parser
          add_help='Adds an iLO federation group to the currently logged in server. Federation '\
                   'group privileges may be specified with\n\"--addprivs\". If a federation key '\
                   'is not '\
                   'provided, the user will be prompted to provide one prior to account creation.'
          add_parser = subcommand_parser.add_parser(
              __subparsers__[0],
              help=add_help,
              description=add_help+'\n\tilofederation add [FEDERATIONNAME] [FEDERATIONKEY] ' +\
              privilege_help+ '\n\tilofederation add newilofedname thisfedkey --addprivs 1,3,4',
              formatter_class=RawDescriptionHelpFormatter
          )
          add_parser.add_argument(
              'fedname',
              help= 'Federation name of the federation group to add.',
              type= str,
              metavar='FEDERATION KEY',
          )
          add_parser.add_argument(
              'fedkey',
              help= 'Federation key of the federation group to add.',
              type= str,
              metavar='FEDERATION KEY',
          )
          self.options_addprivs_argument_group(add_parser)
          self.cmdbase.add_login_arguments_group(add_parser)
  
          modify_help='Modify the privileges on an existing federation group.'
          modify_parser = subcommand_parser.add_parser(
              __subparsers__[1],
              help=modify_help,
              description=modify_help+"\n\nTo add privileges:\n\tilofederation modify "\
              "[FEDNAME] --addprivs <list of numbered privileges>\n\nTo remove privileges:\n\t"\
              "ilofederation modify [FEDNAME] --removeprivs <list of numbered privileges>\n\n"\
              +privilege_help,
              formatter_class=RawDescriptionHelpFormatter
          )
          modify_parser.add_argument(
              'fedname',
              help='The federation name of the iLO account to modify.',
              metavar='FEDERATION NAME',
              type= str
          )
          self.options_addprivs_argument_group(modify_parser)
          self.options_removeprivs_argument_group(modify_parser)
          self.cmdbase.add_login_arguments_group(modify_parser)
  
          #changepass sub-parser
          changekey_help='Change the key of an iLO federation group on the currently logged in '\
                         'server.'
          changekey_parser = subcommand_parser.add_parser(
              __subparsers__[2],
              help=changekey_help,
              description=changekey_help+'\n\nexample:ilofederation changekey [FEDNAME] [NEWFEDKEY]',
              formatter_class=RawDescriptionHelpFormatter
          )
          changekey_parser.add_argument(
              'fedname',
              help='The iLO federation account to be updated with a new federation key (password).',
              metavar='FEDERATION NAME',
              type= str
          )
          changekey_parser.add_argument(
              'fedkey',
              help='The federation key (password) to be altered for the selected iLO federation '\
              ' account. If you do not include a federation key, you will be prompted to enter one.',
              metavar='FEDERATION KEY',
              type= str,
              nargs='?',
              default=''
          )
          self.cmdbase.add_login_arguments_group(changekey_parser)
  
          #delete sub-parser
          delete_help='Deletes the provided iLO user account on the currently logged in server.'
          delete_parser = subcommand_parser.add_parser(
              __subparsers__[3],
              help=delete_help,
              description=delete_help+'\n\nexample: ilofederation delete fedname',
              formatter_class=RawDescriptionHelpFormatter
          )
          delete_parser.add_argument(
              'fedname',
              help='The iLO federation account to delete.',
              metavar='FEDERATION NAME',
              type= str
          )
          self.cmdbase.add_login_arguments_group(delete_parser)
  