diff options
author | Froilan Irizarry <froilan@froilanirizarry.me> | 2017-05-18 23:29:36 -0400 |
---|---|---|
committer | José Padilla <jpadilla@webapplicate.com> | 2017-05-18 23:29:36 -0400 |
commit | 328b3d8566fdf18da7c6d9a92e7d6e86d057f6f8 (patch) | |
tree | a45eb398fa2ac107131c819e61f6471e4d3ae238 | |
parent | 1f1d185727c70c00d0e7de7c7274985b17744f4a (diff) | |
download | pyjwt-328b3d8566fdf18da7c6d9a92e7d6e86d057f6f8.tar.gz |
Change optparse for argparse. (#238)
-rw-r--r-- | .coveragerc | 2 | ||||
-rw-r--r-- | .travis.yml | 1 | ||||
-rw-r--r-- | jwt/__main__.py | 204 | ||||
-rw-r--r-- | tests/test_cli.py | 127 |
4 files changed, 244 insertions, 90 deletions
diff --git a/.coveragerc b/.coveragerc index fc37be8..886e322 100644 --- a/.coveragerc +++ b/.coveragerc @@ -4,4 +4,4 @@ omit = .tox/* setup.py *.egg/* - */__main__.py + diff --git a/.travis.yml b/.travis.yml index d465e95..b985141 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,6 +23,7 @@ env: - TOXENV=py35-contrib_crypto - TOXENV=py36-contrib_crypto - TOXENV=py27-contrib_crypto + install: - pip install -U pip - pip install -U tox coveralls diff --git a/jwt/__main__.py b/jwt/__main__.py index 6a36953..52e7abf 100644 --- a/jwt/__main__.py +++ b/jwt/__main__.py @@ -2,48 +2,105 @@ from __future__ import absolute_import, print_function
+import argparse
import json
-import optparse
import sys
import time
-from . import DecodeError, __package__, __version__, decode, encode
+from . import DecodeError, __version__, decode, encode
-def main():
+def encode_payload(args):
+ # Try to encode
+ if args.key is None:
+ raise ValueError('Key is required when encoding. See --help for usage.')
+
+ # Build payload object to encode
+ payload = {}
+
+ for arg in args.payload:
+ k, v = arg.split('=', 1)
+
+ # exp +offset special case?
+ if k == 'exp' and v[0] == '+' and len(v) > 1:
+ v = str(int(time.time()+int(v[1:])))
+
+ # Cast to integer?
+ if v.isdigit():
+ v = int(v)
+ else:
+ # Cast to float?
+ try:
+ v = float(v)
+ except ValueError:
+ pass
+
+ # Cast to true, false, or null?
+ constants = {'true': True, 'false': False, 'null': None}
+
+ if v in constants:
+ v = constants[v]
+
+ payload[k] = v
+
+ token = encode(
+ payload,
+ key=args.key,
+ algorithm=args.algorithm
+ )
+
+ return token.decode('utf-8')
+
+
+def decode_payload(args):
+ try:
+ if sys.stdin.isatty():
+ token = sys.stdin.read()
+ else:
+ token = args.token
+
+ token = token.encode('utf-8')
+ data = decode(token, key=args.key, verify=args.verify)
+
+ return json.dumps(data)
+
+ except DecodeError as e:
+ raise DecodeError('There was an error decoding the token: %s' % e)
+
- usage = '''Encodes or decodes JSON Web Tokens based on input.
+def build_argparser():
- %prog [options] input
+ usage = '''
+ Encodes or decodes JSON Web Tokens based on input.
-Decoding examples:
+ %(prog)s [options] <command> [options] input
- %prog --key=secret json.web.token
- %prog --no-verify json.web.token
+ Decoding examples:
-Encoding requires the key option and takes space separated key/value pairs
-separated by equals (=) as input. Examples:
+ %(prog)s --key=secret decode json.web.token
+ %(prog)s decode --no-verify json.web.token
- %prog --key=secret iss=me exp=1302049071
- %prog --key=secret foo=bar exp=+10
+ Encoding requires the key option and takes space separated key/value pairs
+ separated by equals (=) as input. Examples:
-The exp key is special and can take an offset to current Unix time.\
-'''
- p = optparse.OptionParser(
- usage=usage,
+ %(prog)s --key=secret encode iss=me exp=1302049071
+ %(prog)s --key=secret encode foo=bar exp=+10
+
+ The exp key is special and can take an offset to current Unix time.
+ '''
+
+ arg_parser = argparse.ArgumentParser(
prog='pyjwt',
- version='%s %s' % (__package__, __version__),
+ usage=usage
)
- p.add_option(
- '-n', '--no-verify',
- action='store_false',
- dest='verify',
- default=True,
- help='ignore signature and claims verification on decode'
+ arg_parser.add_argument(
+ '-v', '--version',
+ action='version',
+ version='%(prog)s ' + __version__
)
- p.add_option(
+ arg_parser.add_argument(
'--key',
dest='key',
metavar='KEY',
@@ -51,7 +108,7 @@ The exp key is special and can take an offset to current Unix time.\ help='set the secret key to sign with'
)
- p.add_option(
+ arg_parser.add_argument(
'--alg',
dest='algorithm',
metavar='ALG',
@@ -59,78 +116,47 @@ The exp key is special and can take an offset to current Unix time.\ help='set crypto algorithm to sign with. default=HS256'
)
- options, arguments = p.parse_args()
+ subparsers = arg_parser.add_subparsers(
+ title='PyJWT subcommands',
+ description='valid subcommands',
+ help='additional help'
+ )
- if len(arguments) > 0 or not sys.stdin.isatty():
- if len(arguments) == 1 and (not options.verify or options.key):
- # Try to decode
- try:
- if not sys.stdin.isatty():
- token = sys.stdin.read()
- else:
- token = arguments[0]
+ # Encode subcommand
+ encode_parser = subparsers.add_parser('encode', help='use to encode a supplied payload')
- token = token.encode('utf-8')
- data = decode(token, key=options.key, verify=options.verify)
+ payload_help = """Payload to encode. Must be a space separated list of key/value
+ pairs separated by equals (=) sign."""
- print(json.dumps(data))
- sys.exit(0)
- except DecodeError as e:
- print(e)
- sys.exit(1)
+ encode_parser.add_argument('payload', nargs='+', help=payload_help)
+ encode_parser.set_defaults(func=encode_payload)
- # Try to encode
- if options.key is None:
- print('Key is required when encoding. See --help for usage.')
- sys.exit(1)
+ # Decode subcommand
+ decode_parser = subparsers.add_parser('decode', help='use to decode a supplied JSON web token')
+ decode_parser.add_argument('token', help='JSON web token to decode.')
- # Build payload object to encode
- payload = {}
+ decode_parser.add_argument(
+ '-n', '--no-verify',
+ action='store_false',
+ dest='verify',
+ default=True,
+ help='ignore signature and claims verification on decode'
+ )
- for arg in arguments:
- try:
- k, v = arg.split('=', 1)
+ decode_parser.set_defaults(func=decode_payload)
+
+ return arg_parser
- # exp +offset special case?
- if k == 'exp' and v[0] == '+' and len(v) > 1:
- v = str(int(time.time()+int(v[1:])))
- # Cast to integer?
- if v.isdigit():
- v = int(v)
- else:
- # Cast to float?
- try:
- v = float(v)
- except ValueError:
- pass
+def main():
+ arg_parser = build_argparser()
- # Cast to true, false, or null?
- constants = {'true': True, 'false': False, 'null': None}
+ try:
+ arguments = arg_parser.parse_args(sys.argv[1:])
- if v in constants:
- v = constants[v]
+ output = arguments.func(arguments)
- payload[k] = v
- except ValueError:
- print('Invalid encoding input at {}'.format(arg))
- sys.exit(1)
-
- try:
- token = encode(
- payload,
- key=options.key,
- algorithm=options.algorithm
- )
-
- print(token)
- sys.exit(0)
- except Exception as e:
- print(e)
- sys.exit(1)
- else:
- p.print_help()
-
-
-if __name__ == '__main__':
- main()
+ print(output)
+ except Exception as e:
+ print('There was an unforseen error: ', e)
+ arg_parser.print_help()
diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..f08cf6b --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,127 @@ + +import argparse +import json +import sys + +import jwt +from jwt.__main__ import build_argparser, decode_payload, encode_payload, main + +import pytest + + +class TestCli: + + def test_build_argparse(self): + args = ['--key', '1234', 'encode', 'name=Vader'] + parser = build_argparser() + parsed_args = parser.parse_args(args) + + assert parsed_args.key == '1234' + + def test_encode_payload_raises_value_error_key_is_required(self): + encode_args = ['encode', 'name=Vader', 'job=Sith'] + parser = build_argparser() + + args = parser.parse_args(encode_args) + + with pytest.raises(ValueError) as excinfo: + encode_payload(args) + + assert 'Key is required when encoding' in str(excinfo.value) + + def test_decode_payload_raises_decoded_error(self): + decode_args = ['--key', '1234', 'decode', 'wrong-token'] + parser = build_argparser() + + args = parser.parse_args(decode_args) + + with pytest.raises(jwt.DecodeError) as excinfo: + decode_payload(args) + + assert 'There was an error decoding the token' in str(excinfo.value) + + def test_decode_payload_raises_decoded_error_isatty(self, monkeypatch): + def patched_sys_stdin_read(): + raise jwt.DecodeError() + + decode_args = ['--key', '1234', 'decode', 'wrong-token'] + parser = build_argparser() + + args = parser.parse_args(decode_args) + + monkeypatch.setattr(sys.stdin, 'isatty', lambda: True) + monkeypatch.setattr(sys.stdin, 'read', patched_sys_stdin_read) + + with pytest.raises(jwt.DecodeError) as excinfo: + decode_payload(args) + + assert 'There was an error decoding the token' in str(excinfo.value) + + @pytest.mark.parametrize('key,name,job,exp,verify', [ + ('1234', 'Vader', 'Sith', None, None), + ('4567', 'Anakin', 'Jedi', '+1', None), + ('4321', 'Padme', 'Queen', '4070926800', 'true'), + ]) + def test_encode_decode(self, key, name, job, exp, verify): + encode_args = [ + '--key={0}'.format(key), + 'encode', + 'name={0}'.format(name), + 'job={0}'.format(job), + ] + if exp: + encode_args.append('exp={0}'.format(exp)) + if verify: + encode_args.append('verify={0}'.format(verify)) + + parser = build_argparser() + parsed_encode_args = parser.parse_args(encode_args) + token = encode_payload(parsed_encode_args) + assert token is not None + assert token is not '' + + decode_args = [ + '--key={0}'.format(key), + 'decode', + token + ] + parser = build_argparser() + parsed_decode_args = parser.parse_args(decode_args) + + actual = json.loads(decode_payload(parsed_decode_args)) + expected = { + 'job': job, + 'name': name, + } + assert actual['name'] == expected['name'] + assert actual['job'] == expected['job'] + + @pytest.mark.parametrize('key,name,job,exp,verify', [ + ('1234', 'Vader', 'Sith', None, None), + ('4567', 'Anakin', 'Jedi', '+1', None), + ('4321', 'Padme', 'Queen', '4070926800', 'true'), + ]) + def test_main(self, monkeypatch, key, name, job, exp, verify): + args = [ + 'test_cli.py', + '--key={0}'.format(key), + 'encode', + 'name={0}'.format(name), + 'job={0}'.format(job), + ] + if exp: + args.append('exp={0}'.format(exp)) + if verify: + args.append('verify={0}'.format(verify)) + monkeypatch.setattr(sys, 'argv', args) + main() + + def test_main_throw_exception(self, monkeypatch, capsys): + def patched_argparser_parse_args(self, args): + raise Exception('NOOOOOOOOOOO!') + + monkeypatch.setattr(argparse.ArgumentParser, 'parse_args', patched_argparser_parse_args) + main() + out, _ = capsys.readouterr() + + assert 'NOOOOOOOOOOO!' in out |