Change optparse for argparse. (#238)
This commit is contained in:
parent
1f1d185727
commit
328b3d8566
|
@ -4,4 +4,4 @@ omit =
|
||||||
.tox/*
|
.tox/*
|
||||||
setup.py
|
setup.py
|
||||||
*.egg/*
|
*.egg/*
|
||||||
*/__main__.py
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ env:
|
||||||
- TOXENV=py35-contrib_crypto
|
- TOXENV=py35-contrib_crypto
|
||||||
- TOXENV=py36-contrib_crypto
|
- TOXENV=py36-contrib_crypto
|
||||||
- TOXENV=py27-contrib_crypto
|
- TOXENV=py27-contrib_crypto
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- pip install -U pip
|
- pip install -U pip
|
||||||
- pip install -U tox coveralls
|
- pip install -U tox coveralls
|
||||||
|
|
208
jwt/__main__.py
208
jwt/__main__.py
|
@ -2,48 +2,105 @@
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function
|
from __future__ import absolute_import, print_function
|
||||||
|
|
||||||
|
import argparse
|
||||||
import json
|
import json
|
||||||
import optparse
|
|
||||||
import sys
|
import sys
|
||||||
import time
|
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
|
# Cast to integer?
|
||||||
%prog --no-verify json.web.token
|
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
|
# Cast to true, false, or null?
|
||||||
separated by equals (=) as input. Examples:
|
constants = {'true': True, 'false': False, 'null': None}
|
||||||
|
|
||||||
%prog --key=secret iss=me exp=1302049071
|
if v in constants:
|
||||||
%prog --key=secret foo=bar exp=+10
|
v = constants[v]
|
||||||
|
|
||||||
The exp key is special and can take an offset to current Unix time.\
|
payload[k] = v
|
||||||
'''
|
|
||||||
p = optparse.OptionParser(
|
token = encode(
|
||||||
usage=usage,
|
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',
|
prog='pyjwt',
|
||||||
version='%s %s' % (__package__, __version__),
|
usage=usage
|
||||||
)
|
)
|
||||||
|
|
||||||
p.add_option(
|
arg_parser.add_argument(
|
||||||
'-n', '--no-verify',
|
'-v', '--version',
|
||||||
action='store_false',
|
action='version',
|
||||||
dest='verify',
|
version='%(prog)s ' + __version__
|
||||||
default=True,
|
|
||||||
help='ignore signature and claims verification on decode'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
p.add_option(
|
arg_parser.add_argument(
|
||||||
'--key',
|
'--key',
|
||||||
dest='key',
|
dest='key',
|
||||||
metavar='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'
|
help='set the secret key to sign with'
|
||||||
)
|
)
|
||||||
|
|
||||||
p.add_option(
|
arg_parser.add_argument(
|
||||||
'--alg',
|
'--alg',
|
||||||
dest='algorithm',
|
dest='algorithm',
|
||||||
metavar='ALG',
|
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'
|
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():
|
# Encode subcommand
|
||||||
if len(arguments) == 1 and (not options.verify or options.key):
|
encode_parser = subparsers.add_parser('encode', help='use to encode a supplied payload')
|
||||||
# Try to decode
|
|
||||||
try:
|
|
||||||
if not sys.stdin.isatty():
|
|
||||||
token = sys.stdin.read()
|
|
||||||
else:
|
|
||||||
token = arguments[0]
|
|
||||||
|
|
||||||
token = token.encode('utf-8')
|
payload_help = """Payload to encode. Must be a space separated list of key/value
|
||||||
data = decode(token, key=options.key, verify=options.verify)
|
pairs separated by equals (=) sign."""
|
||||||
|
|
||||||
print(json.dumps(data))
|
encode_parser.add_argument('payload', nargs='+', help=payload_help)
|
||||||
sys.exit(0)
|
encode_parser.set_defaults(func=encode_payload)
|
||||||
except DecodeError as e:
|
|
||||||
print(e)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Try to encode
|
# Decode subcommand
|
||||||
if options.key is None:
|
decode_parser = subparsers.add_parser('decode', help='use to decode a supplied JSON web token')
|
||||||
print('Key is required when encoding. See --help for usage.')
|
decode_parser.add_argument('token', help='JSON web token to decode.')
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Build payload object to encode
|
decode_parser.add_argument(
|
||||||
payload = {}
|
'-n', '--no-verify',
|
||||||
|
action='store_false',
|
||||||
|
dest='verify',
|
||||||
|
default=True,
|
||||||
|
help='ignore signature and claims verification on decode'
|
||||||
|
)
|
||||||
|
|
||||||
for arg in arguments:
|
decode_parser.set_defaults(func=decode_payload)
|
||||||
try:
|
|
||||||
k, v = arg.split('=', 1)
|
|
||||||
|
|
||||||
# exp +offset special case?
|
return arg_parser
|
||||||
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()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
def main():
|
||||||
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()
|
||||||
|
|
|
@ -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
|
Loading…
Reference in New Issue