summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFroilan Irizarry <froilan@froilanirizarry.me>2017-05-18 23:29:36 -0400
committerJosé Padilla <jpadilla@webapplicate.com>2017-05-18 23:29:36 -0400
commit328b3d8566fdf18da7c6d9a92e7d6e86d057f6f8 (patch)
treea45eb398fa2ac107131c819e61f6471e4d3ae238
parent1f1d185727c70c00d0e7de7c7274985b17744f4a (diff)
downloadpyjwt-328b3d8566fdf18da7c6d9a92e7d6e86d057f6f8.tar.gz
Change optparse for argparse. (#238)
-rw-r--r--.coveragerc2
-rw-r--r--.travis.yml1
-rw-r--r--jwt/__main__.py204
-rw-r--r--tests/test_cli.py127
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