#!/usr/bin/env python """Starts an interactive JIRA session in an ipython terminal. Script arguments support changing the server and a persistent authentication over HTTP BASIC. """ try: import configparser except ImportError: from six.moves import configparser from six.moves import input from six.moves.urllib.parse import parse_qsl import argparse from getpass import getpass from jira import __version__ from jira import JIRA from oauthlib.oauth1 import SIGNATURE_RSA import os import requests from requests_oauthlib import OAuth1 from sys import exit import webbrowser CONFIG_PATH = os.path.join( os.path.expanduser('~'), '.jira-python', 'jirashell.ini') def oauth_dance(server, consumer_key, key_cert_data, print_tokens=False, verify=None): if verify is None: verify = server.startswith('https') # step 1: get request tokens oauth = OAuth1( consumer_key, signature_method=SIGNATURE_RSA, rsa_key=key_cert_data) r = requests.post( server + '/plugins/servlet/oauth/request-token', verify=verify, auth=oauth) request = dict(parse_qsl(r.text)) request_token = request['oauth_token'] request_token_secret = request['oauth_token_secret'] if print_tokens: print("Request tokens received.") print(" Request token: {}".format(request_token)) print(" Request token secret: {}".format(request_token_secret)) # step 2: prompt user to validate auth_url = '{}/plugins/servlet/oauth/authorize?oauth_token={}'.format( server, request_token) if print_tokens: print( "Please visit this URL to authorize the OAuth request:\n\t{}".format(auth_url)) else: webbrowser.open_new(auth_url) print( "Your browser is opening the OAuth authorization for this client session.") approved = input( 'Have you authorized this program to connect on your behalf to {}? (y/n)'.format(server)) if approved.lower() != 'y': exit( 'Abandoning OAuth dance. Your partner faceplants. The audience boos. You feel shame.') # step 3: get access tokens for validated user oauth = OAuth1(consumer_key, signature_method=SIGNATURE_RSA, rsa_key=key_cert_data, resource_owner_key=request_token, resource_owner_secret=request_token_secret ) r = requests.post( server + '/plugins/servlet/oauth/access-token', verify=verify, auth=oauth) access = dict(parse_qsl(r.text)) if print_tokens: print("Access tokens received.") print(" Access token: {}".format(access['oauth_token'])) print(" Access token secret: {}".format( access['oauth_token_secret'])) return { 'access_token': access['oauth_token'], 'access_token_secret': access['oauth_token_secret'], 'consumer_key': consumer_key, 'key_cert': key_cert_data} def process_config(): if not os.path.exists(CONFIG_PATH): return {}, {}, {} parser = configparser.ConfigParser() try: parser.read(CONFIG_PATH) except configparser.ParsingError as err: print("Couldn't read config file at path: {}\n{}".format( CONFIG_PATH, err)) raise if parser.has_section('options'): options = {} for option, value in parser.items('options'): if option in ("verify", "async"): value = parser.getboolean('options', option) options[option] = value else: options = {} if parser.has_section('basic_auth'): basic_auth = dict(parser.items('basic_auth')) else: basic_auth = {} if parser.has_section('oauth'): oauth = {} for option, value in parser.items('oauth'): if option in ("oauth_dance", "print_tokens"): value = parser.getboolean('oauth', option) oauth[option] = value else: oauth = {} return options, basic_auth, oauth def process_command_line(): parser = argparse.ArgumentParser( description='Start an interactive JIRA shell with the REST API.') jira_group = parser.add_argument_group('JIRA server connection options') jira_group.add_argument('-s', '--server', help='The JIRA instance to connect to, including context path.') jira_group.add_argument('-r', '--rest-path', help='The root path of the REST API to use.') jira_group.add_argument('--auth-url', help='Path to URL to auth against.') jira_group.add_argument('-v', '--rest-api-version', help='The version of the API under the specified name.') jira_group.add_argument('--no-verify', action='store_true', help='do not verify the ssl certificate') basic_auth_group = parser.add_argument_group('BASIC auth options') basic_auth_group.add_argument('-u', '--username', help='The username to connect to this JIRA instance with.') basic_auth_group.add_argument('-p', '--password', help='The password associated with this user.') basic_auth_group.add_argument('-P', '--prompt-for-password', action='store_true', help='Prompt for the password at the command line.') oauth_group = parser.add_argument_group('OAuth options') oauth_group.add_argument('-od', '--oauth-dance', action='store_true', help='Start a 3-legged OAuth authentication dance with JIRA.') oauth_group.add_argument('-ck', '--consumer-key', help='OAuth consumer key.') oauth_group.add_argument('-k', '--key-cert', help='Private key to sign OAuth requests with (should be the pair of the public key\ configured in the JIRA application link)') oauth_group.add_argument('-pt', '--print-tokens', action='store_true', help='Print the negotiated OAuth tokens as they are retrieved.') oauth_already_group = parser.add_argument_group( 'OAuth options for already-authenticated access tokens') oauth_already_group.add_argument('-at', '--access-token', help='OAuth access token for the user.') oauth_already_group.add_argument('-ats', '--access-token-secret', help='Secret for the OAuth access token.') args = parser.parse_args() options = {} if args.server: options['server'] = args.server if args.rest_path: options['rest_path'] = args.rest_path if args.auth_url: options['auth_url'] = args.auth_url if args.rest_api_version: options['rest_api_version'] = args.rest_api_version options['verify'] = True if args.no_verify: options['verify'] = False if args.prompt_for_password: args.password = getpass() basic_auth = {} if args.username: basic_auth['username'] = args.username if args.password: basic_auth['password'] = args.password key_cert_data = None if args.key_cert: with open(args.key_cert, 'r') as key_cert_file: key_cert_data = key_cert_file.read() oauth = {} if args.oauth_dance: oauth = { 'oauth_dance': True, 'consumer_key': args.consumer_key, 'key_cert': key_cert_data, 'print_tokens': args.print_tokens} elif args.access_token and args.access_token_secret and args.consumer_key and args.key_cert: oauth = { 'access_token': args.access_token, 'oauth_dance': False, 'access_token_secret': args.access_token_secret, 'consumer_key': args.consumer_key, 'key_cert': key_cert_data} return options, basic_auth, oauth def get_config(): options, basic_auth, oauth = process_config() cmd_options, cmd_basic_auth, cmd_oauth = process_command_line() options.update(cmd_options) basic_auth.update(cmd_basic_auth) oauth.update(cmd_oauth) return options, basic_auth, oauth def main(): try: get_ipython except NameError: pass else: exit("Running ipython inside ipython isn't supported. :(") options, basic_auth, oauth = get_config() if basic_auth: basic_auth = (basic_auth['username'], basic_auth['password']) if oauth.get('oauth_dance') is True: oauth = oauth_dance( options['server'], oauth['consumer_key'], oauth['key_cert'], oauth['print_tokens'], options['verify']) elif not all((oauth.get('access_token'), oauth.get('access_token_secret'), oauth.get('consumer_key'), oauth.get('key_cert'))): oauth = None jira = JIRA(options=options, basic_auth=basic_auth, oauth=oauth) import IPython # The top-level `frontend` package has been deprecated since IPython 1.0. if IPython.version_info[0] >= 1: from IPython.terminal.embed import InteractiveShellEmbed else: from IPython.frontend.terminal.embed import InteractiveShellEmbed ipshell = InteractiveShellEmbed( banner1='') ipshell("*** JIRA shell active; client is in 'jira'." ' Press Ctrl-D to exit.') if __name__ == '__main__': status = main() exit(status)