PageRenderTime 25ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/code/zato-cli/src/zato/cli/rest/channel.py

https://github.com/zatosource/zato
Python | 262 lines | 171 code | 51 blank | 40 comment | 16 complexity | 5342fd2e677ca44772a2b52b8b16c4e4 MD5 | raw file
Possible License(s): LGPL-3.0
  1. # -*- coding: utf-8 -*-
  2. """
  3. Copyright (C) 2022, Zato Source s.r.o. https://zato.io
  4. Licensed under LGPLv3, see LICENSE.txt for terms and conditions.
  5. """
  6. # stdlib
  7. import sys
  8. from uuid import uuid4
  9. # Zato
  10. from zato.cli import ServerAwareCommand
  11. from zato.common.api import CONNECTION, ZATO_NONE
  12. from zato.common.util.api import fs_safe_now
  13. from zato.common.util.cli import APIKeyManager, BasicAuthManager
  14. # ################################################################################################################################
  15. # ################################################################################################################################
  16. if 0:
  17. from argparse import Namespace
  18. from zato.common.typing_ import anytuple, stranydict
  19. Namespace = Namespace
  20. # ################################################################################################################################
  21. # ################################################################################################################################
  22. class Config:
  23. ServiceName = 'pub.zato.ping'
  24. MaxBytesRequests = 500 # 0.5k because requests are usually shorter
  25. MaxBytesResponses = 5000 # 5k because responses are usually longer
  26. # ################################################################################################################################
  27. # ################################################################################################################################
  28. class SecurityAwareCommand(ServerAwareCommand):
  29. def _extract_credentials(self, name:'str', credentials:'str', needs_header:'bool') -> 'anytuple':
  30. credentials_lower = credentials.lower()
  31. if credentials_lower == 'true':
  32. username = name
  33. value_type = 'key' if needs_header else 'password'
  34. password = 'api.{}.'.format(value_type) + uuid4().hex
  35. # If the username is represented through an HTTP header,
  36. # turn the value into one.
  37. if needs_header:
  38. # 'T' is included below because it was part of the timestamp,
  39. # e.g. auto.rest.channel.2022_03_26T19_47_12_191630.
  40. username = username.replace('.', '-').replace('_', '-').replace('T', '-')
  41. username = username.split('-')
  42. username = [elem.capitalize() for elem in username]
  43. username = 'X-' + '-'.join(username)
  44. elif credentials_lower == 'false':
  45. username, password = None, None
  46. else:
  47. _credentials = credentials.split(',')
  48. _credentials = [elem.strip() for elem in _credentials]
  49. username, password = _credentials
  50. return username, password
  51. # ################################################################################################################################
  52. def _get_security_id(self, *, name:'str', basic_auth:'str', api_key:'str') -> 'stranydict':
  53. out = {}
  54. if basic_auth:
  55. username, password = self._extract_credentials(name, basic_auth, False)
  56. manager = BasicAuthManager(self, name, True, username, 'API', password)
  57. response = manager.create()
  58. out['username'] = username
  59. out['password'] = password
  60. out['security_id'] = response['id']
  61. elif api_key:
  62. header, key = self._extract_credentials(name, api_key, True)
  63. manager = APIKeyManager(self, name, True, header, key)
  64. response = manager.create()
  65. out['header'] = header
  66. out['key'] = key
  67. out['security_id'] = response['id']
  68. else:
  69. # Use empty credentials to explicitly indicate that none are required
  70. out['username'] = None
  71. out['password'] = None
  72. out['security_id'] = ZATO_NONE
  73. # No matter what we had on input, we can return our output now.
  74. return out
  75. # ################################################################################################################################
  76. # ################################################################################################################################
  77. class CreateChannel(SecurityAwareCommand):
  78. """ Creates a new REST channel.
  79. """
  80. opts = [
  81. {'name':'--name', 'help':'Name of the channel to create', 'required':False,},
  82. {'name':'--is-active', 'help':'Should the channel be active upon creation', 'required':False},
  83. {'name':'--url-path', 'help':'URL path to assign to the channel', 'required':False},
  84. {'name':'--service', 'help':'Service reacting to requests sent to the channel',
  85. 'required':False, 'default':Config.ServiceName},
  86. {'name':'--basic-auth', 'help':'HTTP Basic Auth credentials for the channel', 'required':False},
  87. {'name':'--api-key', 'help':'API key-based credentials for the channel', 'required':False},
  88. {'name':'--store-requests', 'help':'How many requests to store in audit log',
  89. 'required':False, 'default':0, 'type': int},
  90. {'name':'--store-responses', 'help':'How many responses to store in audit log',
  91. 'required':False, 'default':0, 'type': int},
  92. {'name':'--max-bytes-requests', 'help':'How many bytes of each request to store',
  93. 'required':False, 'default':500, 'type': int},
  94. {'name':'--max-bytes-responses', 'help':'How many bytes of each response to store',
  95. 'required':False, 'default':500, 'type': int},
  96. {'name':'--path', 'help':'Path to a Zato server', 'required':True},
  97. ]
  98. # ################################################################################################################################
  99. def execute(self, args:'Namespace'):
  100. name = getattr(args, 'name', None)
  101. is_active = getattr(args, 'is_active', None)
  102. url_path = getattr(args, 'url_path', None)
  103. channel_service = getattr(args, 'service', None) or Config.ServiceName
  104. basic_auth = getattr(args, 'basic_auth', '')
  105. api_key = getattr(args, 'api_key', '')
  106. store_requests = getattr(args, 'store_requests', 0)
  107. store_responses = getattr(args, 'store_responses', 0)
  108. max_bytes_requests = getattr(args, 'max_bytes_requests', None) or Config.MaxBytesRequests
  109. max_bytes_responses = getattr(args, 'max_bytes_requests', None) or Config.MaxBytesResponses
  110. # For later use
  111. now = fs_safe_now()
  112. # Assume that the channel should be active
  113. is_active = getattr(args, 'is_active', True)
  114. if is_active is None:
  115. is_active = True
  116. # Generate a name if one is not given
  117. name = name or 'auto.rest.channel.' + now
  118. # If we have no URL path, base it on the auto-generate name
  119. if not url_path:
  120. url_path = '/'+ name
  121. # Enable the audit log if told to
  122. is_audit_log_received_active = bool(store_requests)
  123. is_audit_log_sent_active = bool(store_responses)
  124. # Obtain the security ID based on input data, creating the definition if necessary.
  125. sec_name = 'auto.sec.' + now
  126. security_info = self._get_security_id(name=sec_name, basic_auth=basic_auth, api_key=api_key)
  127. security_id = security_info.pop('security_id')
  128. # API service to invoke
  129. service = 'zato.http-soap.create'
  130. # API request to send
  131. request = {
  132. 'name': name,
  133. 'url_path': url_path,
  134. 'service': channel_service,
  135. 'is_active': is_active,
  136. 'connection': CONNECTION.CHANNEL,
  137. 'security_id': security_id,
  138. 'is_audit_log_received_active': is_audit_log_received_active,
  139. 'is_audit_log_sent_active': is_audit_log_sent_active,
  140. 'max_len_messages_received': store_requests,
  141. 'max_len_messages_sent': store_responses,
  142. 'max_bytes_per_message_received': max_bytes_requests,
  143. 'max_bytes_per_message_sent': max_bytes_responses,
  144. }
  145. # Invoke the base service that creates a channel ..
  146. response = self._invoke_service(service, request)
  147. # .. update the response with the channel security definition's details ..
  148. response.update(security_info)
  149. # .. finally, log the response for the caller.
  150. self._log_response(response, needs_stdout=True)
  151. # ################################################################################################################################
  152. # ################################################################################################################################
  153. class DeleteChannel(SecurityAwareCommand):
  154. """ Deletes a REST channel.
  155. """
  156. opts = [
  157. {'name':'--id', 'help':'ID of the channel to delete', 'required':False},
  158. {'name':'--name', 'help':'Name of the channel to delete', 'required':False},
  159. {'name':'--path', 'help':'Path to a Zato server', 'required':True},
  160. ]
  161. def execute(self, args:'Namespace'):
  162. id = getattr(args, 'id', None)
  163. name = getattr(args, 'name', None)
  164. # Make sure we have input data to delete the channel by
  165. if not (id or name):
  166. self.logger.warn('Cannot continue. To delete a REST channel, either --id or --name is required on input.')
  167. sys.exit(self.SYS_ERROR.INVALID_INPUT)
  168. # API service to invoke
  169. service = 'zato.http-soap.delete'
  170. # API request to send
  171. request = {
  172. 'id': id,
  173. 'name': name,
  174. 'connection': CONNECTION.CHANNEL,
  175. 'should_raise_if_missing': False
  176. }
  177. self._invoke_service_and_log_response(service, request)
  178. # ################################################################################################################################
  179. # ################################################################################################################################
  180. if __name__ == '__main__':
  181. # stdlib
  182. from argparse import Namespace
  183. from os import environ
  184. now = fs_safe_now()
  185. username = 'cli.username.' + now
  186. password = 'cli.password.' + now
  187. args = Namespace()
  188. args.verbose = True
  189. args.store_log = False
  190. args.store_config = False
  191. args.service = Config.ServiceName
  192. # args.basic_auth = f'{username}, {password}'
  193. args.api_key = 'true'
  194. args.path = environ['ZATO_SERVER_BASE_DIR']
  195. command = CreateChannel(args)
  196. command.run(args)
  197. # ################################################################################################################################
  198. # ################################################################################################################################