/tools/jirashell.py

https://bitbucket.org/jonEbird/jira-python · Python · 190 lines · 148 code · 34 blank · 8 comment · 30 complexity · b06974c8d0d76a570359e0ddc321b8c8 MD5 · raw file

  1. #!/usr/bin/env python
  2. """
  3. Starts an interactive JIRA session in an ipython terminal. Script arguments
  4. support changing the server and a persistent authentication over HTTP BASIC.
  5. """
  6. import ConfigParser
  7. import argparse
  8. from getpass import getpass
  9. from sys import exit
  10. import os
  11. import requests
  12. from jira.packages.requests_oauth.hook import OAuthHook
  13. from urlparse import parse_qsl
  14. import webbrowser
  15. from jira.client import JIRA
  16. from jira import __version__
  17. CONFIG_PATH = os.path.join(os.path.expanduser('~'), '.jira-python', 'jirashell.ini')
  18. def oauth_dance(server, consumer_key, key_cert_data, print_tokens=False):
  19. verify = server.startswith('https')
  20. # step 1: get request tokens
  21. request_oauth_hook = OAuthHook(consumer_key=consumer_key, consumer_secret='',
  22. key_cert=key_cert_data, header_auth=True)
  23. r = requests.post(server + '/plugins/servlet/oauth/request-token', verify=verify,
  24. hooks={'pre_request': request_oauth_hook})
  25. request = dict(parse_qsl(r.text))
  26. request_token = request['oauth_token']
  27. request_token_secret = request['oauth_token_secret']
  28. if print_tokens:
  29. print "Request tokens received."
  30. print " Request token: {}".format(request_token)
  31. print " Request token secret: {}".format(request_token_secret)
  32. # step 2: prompt user to validate
  33. auth_url = '{}/plugins/servlet/oauth/authorize?oauth_token={}'.format(server, request_token)
  34. webbrowser.open_new(auth_url)
  35. print "Your browser is opening the OAuth authorization for this client session."
  36. approved = raw_input('Have you authorized this program to connect on your behalf to {}? (y/n)'.format(server))
  37. if approved.lower() != 'y':
  38. exit('Abandoning OAuth dance. Your partner faceplants. The audience boos. You feel shame.')
  39. # step 3: get access tokens for validated user
  40. access_oauth_hook = OAuthHook(access_token=request_token, access_token_secret=request_token_secret,
  41. consumer_key=consumer_key, consumer_secret='',
  42. key_cert=key_cert_data, header_auth=True)
  43. r = requests.post(server + '/plugins/servlet/oauth/access-token', verify=verify,
  44. hooks={'pre_request': access_oauth_hook})
  45. access = dict(parse_qsl(r.text))
  46. if print_tokens:
  47. print "Access tokens received."
  48. print " Access token: {}".format(access['oauth_token'])
  49. print " Access token secret: {}".format(access['oauth_token_secret'])
  50. return {
  51. 'access_token': access['oauth_token'],
  52. 'access_token_secret': access['oauth_token_secret'],
  53. 'consumer_key': consumer_key,
  54. 'key_cert': key_cert_data,
  55. }
  56. def process_config():
  57. parser = ConfigParser.SafeConfigParser()
  58. try:
  59. parser.read(CONFIG_PATH)
  60. except ConfigParser.ParsingError, err:
  61. print "Couldn't read config file at path: " + CONFIG_PATH + "; reverting to command line"
  62. return process_command_line()
  63. if parser.has_section('options'):
  64. options = dict(parser.items('options'))
  65. else:
  66. options = {}
  67. if parser.has_section('basic_auth'):
  68. basic_auth = dict(parser.items('basic_auth'))
  69. else:
  70. basic_auth = {}
  71. if parser.has_section('oauth'):
  72. oauth = dict(parser.items('oauth'))
  73. else:
  74. oauth = {}
  75. return options, basic_auth, oauth
  76. def process_command_line():
  77. parser = argparse.ArgumentParser(description='Start an interactive JIRA shell with the REST API.')
  78. jira_group = parser.add_argument_group('JIRA server connection options')
  79. jira_group.add_argument('-s', '--server',
  80. help='The JIRA instance to connect to, including context path.')
  81. jira_group.add_argument('-r', '--rest-path',
  82. help='The root path of the REST API to use.')
  83. jira_group.add_argument('-v', '--rest-api-version',
  84. help='The version of the API under the specified name.')
  85. basic_auth_group = parser.add_argument_group('BASIC auth options')
  86. basic_auth_group.add_argument('-u', '--username',
  87. help='The username to connect to this JIRA instance with.')
  88. basic_auth_group.add_argument('-p', '--password',
  89. help='The password associated with this user.')
  90. basic_auth_group.add_argument('-P', '--prompt-for-password', action='store_true',
  91. help='Prompt for the password at the command line.')
  92. oauth_group = parser.add_argument_group('OAuth options')
  93. oauth_group.add_argument('-od', '--oauth-dance', action='store_true',
  94. help='Start a 3-legged OAuth authentication dance with JIRA.')
  95. oauth_group.add_argument('-ck', '--consumer-key',
  96. help='OAuth consumer key.')
  97. oauth_group.add_argument('-k', '--key-cert',
  98. help='Private key to sign OAuth requests with (should be the pair of the public key\
  99. configured in the JIRA application link)')
  100. oauth_group.add_argument('-pt', '--print-tokens', action='store_true',
  101. help='Print the negotiated OAuth tokens as they are retrieved.')
  102. oauth_already_group = parser.add_argument_group('OAuth options for already-authenticated access tokens')
  103. oauth_already_group.add_argument('-at', '--access-token',
  104. help='OAuth access token for the user.')
  105. oauth_already_group.add_argument('-ats', '--access-token-secret',
  106. help='Secret for the OAuth access token.')
  107. args = parser.parse_args()
  108. options = {}
  109. if args.server:
  110. options['server'] = args.server
  111. if args.rest_path:
  112. options['rest_path'] = args.rest_path
  113. if args.rest_api_version:
  114. options['rest_api_version'] = args.rest_api_version
  115. if args.prompt_for_password:
  116. args.password = getpass()
  117. basic_auth = (args.username, args.password) if args.username and args.password else ()
  118. key_cert_data = None
  119. if args.key_cert:
  120. with open(args.key_cert, 'r') as key_cert_file:
  121. key_cert_data = key_cert_file.read()
  122. oauth = None
  123. if args.oauth_dance:
  124. oauth = oauth_dance(args.server, args.consumer_key, key_cert_data, args.print_tokens)
  125. elif args.access_token and args.access_token_secret and args.consumer_key and args.key_cert:
  126. oauth = {
  127. 'access_token': args.access_token,
  128. 'access_token_secret': args.access_token_secret,
  129. 'consumer_key': args.consumer_key,
  130. 'key_cert': key_cert_data,
  131. }
  132. return options, basic_auth, oauth
  133. def get_config():
  134. if os.path.exists(CONFIG_PATH):
  135. options, basic_auth, oauth = process_config()
  136. cmd_options, cmd_basic_auth, cmd_oauth = process_command_line()
  137. options.update(cmd_options)
  138. basic_auth.update(cmd_basic_auth)
  139. oauth.update(cmd_oauth)
  140. return options, basic_auth, oauth
  141. def main():
  142. try:
  143. get_ipython
  144. except NameError:
  145. pass
  146. else:
  147. exit("Running ipython inside ipython isn't supported. :(")
  148. options, basic_auth, oauth = get_config()
  149. jira = JIRA(options=options, basic_auth=basic_auth, oauth=oauth)
  150. from IPython.frontend.terminal.embed import InteractiveShellEmbed
  151. ipshell = InteractiveShellEmbed(banner1='<JIRA Shell ' + __version__ + ' (' + jira.client_info() + ')>')
  152. ipshell("*** JIRA shell active; client is in 'jira'."
  153. ' Press Ctrl-D to exit.')
  154. if __name__ == '__main__':
  155. status = main()
  156. exit(status)