Change optparse for argparse. (#238)

This commit is contained in:
Froilan Irizarry 2017-05-18 23:29:36 -04:00 committed by José Padilla
parent 1f1d185727
commit 328b3d8566
4 changed files with 246 additions and 92 deletions

View File

@ -4,4 +4,4 @@ omit =
.tox/*
setup.py
*.egg/*
*/__main__.py

View File

@ -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

View File

@ -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.')
usage = '''Encodes or decodes JSON Web Tokens based on input.
# Build payload object to encode
payload = {}
%prog [options] input
for arg in args.payload:
k, v = arg.split('=', 1)
Decoding examples:
# exp +offset special case?
if k == 'exp' and v[0] == '+' and len(v) > 1:
v = str(int(time.time()+int(v[1:])))
%prog --key=secret json.web.token
%prog --no-verify json.web.token
# Cast to integer?
if v.isdigit():
v = int(v)
else:
# Cast to float?
try:
v = float(v)
except ValueError:
pass
Encoding requires the key option and takes space separated key/value pairs
separated by equals (=) as input. Examples:
# Cast to true, false, or null?
constants = {'true': True, 'false': False, 'null': None}
%prog --key=secret iss=me exp=1302049071
%prog --key=secret foo=bar exp=+10
if v in constants:
v = constants[v]
The exp key is special and can take an offset to current Unix time.\
'''
p = optparse.OptionParser(
usage=usage,
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)
def build_argparser():
usage = '''
Encodes or decodes JSON Web Tokens based on input.
%(prog)s [options] <command> [options] input
Decoding examples:
%(prog)s --key=secret decode json.web.token
%(prog)s decode --no-verify json.web.token
Encoding requires the key option and takes space separated key/value pairs
separated by equals (=) as input. Examples:
%(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)
# 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
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()
return arg_parser
if __name__ == '__main__':
main()
def main():
arg_parser = build_argparser()
try:
arguments = arg_parser.parse_args(sys.argv[1:])
output = arguments.func(arguments)
print(output)
except Exception as e:
print('There was an unforseen error: ', e)
arg_parser.print_help()

127
tests/test_cli.py Normal file
View File

@ -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