flask-jwt-simple-0.0.3/0000775000000000000000000000000013160237734013411 5ustar rootrootflask-jwt-simple-0.0.3/setup.py0000664000000000000000000000264713160237734015134 0ustar rootroot""" Flask-JWT-Simple ------------------- Flask-JWT-Simple provides barebones jwt endpoint protection for Flask. """ from setuptools import setup setup(name='Flask-JWT-Simple', version='0.0.3', url='https://github.com/vimalloc/flask-jwt-simple', license='MIT', author='Landon Gilbert-Bland', author_email='landogbland@gmail.com', description='Simple JWT integration with Flask', long_description='Simple JWT integration with Flask', keywords=['flask', 'jwt', 'json web token'], packages=['flask_jwt_simple'], zip_safe=False, platforms='any', install_requires=['Flask', 'PyJWT'], extras_require={ 'asymmetric_crypto': ["cryptography"] }, classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 'Topic :: Software Development :: Libraries :: Python Modules' ]) flask-jwt-simple-0.0.3/setup.cfg0000664000000000000000000000005013160237734015225 0ustar rootroot[metadata] description-file = README.md flask-jwt-simple-0.0.3/README.md0000664000000000000000000000414513160237734014674 0ustar rootroot# Flask-JWT-Simple [![Build Status](https://travis-ci.org/vimalloc/flask-jwt-simple.svg?branch=master)](https://travis-ci.org/vimalloc/flask-jwt-simple) [![Coverage Status](https://coveralls.io/repos/github/vimalloc/flask-jwt-simple/badge.svg)](https://coveralls.io/github/vimalloc/flask-jwt-simple) [![PyPI version](https://badge.fury.io/py/Flask-JWT-Simple.svg)](https://badge.fury.io/py/Flask-JWT-Simple) [![Documentation Status](https://readthedocs.org/projects/flask-jwt-simple/badge/)](http://flask-jwt-simple.readthedocs.io/en/latest/) ### When to use Flask-JWT-Simple? Flask-JWT-Simple adds barebones support for protecting flask endpoints with JSON Web Tokens. It is particularly good for fast prototyping or consuming/producing JWTs that work with other providers and consumers. ### When *not* to use Flask-JWT-Simple? If you are using JWTs with just your flask application, it may make more sense to use the sister extension [Flask-JWT-Extended](https://github.com/vimalloc/flask-jwt-extended). It provides several built in features to make working with JSON Web Tokens easier. These include refresh tokens, fresh/unfresh tokens, tokens in cookies, csrf protection when using cookies, and token revoking. The drawback is that extension is a more opinionated on what needs to be in the JWT in order to get all those extra features to work. ### Installation [View Installation Instructions](http://flask-jwt-simple.readthedocs.io/en/latest/installation.html) ### Usage [View the documentation online](http://flask-jwt-simple.readthedocs.io/en/latest/) ### Chatting We are on irc! You can come chat with us in the ```#flask-jwt-extended``` channel on ```freenode```. ### Testing and Code Coverage We require 100% code coverage in our unit tests. You can run the tests locally with `tox` which will print out a code coverage report. Creating a pull request will run the tests against python 2.7, 3.3, 3,4, 3,5, 3,6, and PyPy. ``` $ tox ``` ### Generating Documentation You can generate a local copy of the documentation. After installing the requirements, go to the `docs` directory and run: ``` $ make clean && make html ``` flask-jwt-simple-0.0.3/tests/0000775000000000000000000000000013160237734014553 5ustar rootrootflask-jwt-simple-0.0.3/tests/test_protected_endpoints.py0000664000000000000000000001606113160237734022244 0ustar rootrootimport pytest import datetime from flask import Flask, jsonify, json from flask_jwt_simple.utils import get_jwt_identity, create_jwt from flask_jwt_simple import JWTManager, jwt_required, jwt_optional RSA_PRIVATE = """ -----BEGIN RSA PRIVATE KEY----- MIICXgIBAAKBgQDN+p9a9oMyqRzkae8yLdJcEK0O0WesH6JiMz+KDrpUwAoAM/KP DnxFnROJDSBHyHEmPVn5x8GqV5lQ9+6l97jdEEcPo6wkshycM82fgcxOmvtAy4Uo xq/AeplYqplhcUTGVuo4ZldOLmN8ksGmzhWpsOdT0bkYipHCn5sWZxd21QIDAQAB AoGBAMJ0++KVXXEDZMpjFDWsOq898xNNMHG3/8ZzmWXN161RC1/7qt/RjhLuYtX9 NV9vZRrzyrDcHAKj5pMhLgUzpColKzvdG2vKCldUs2b0c8HEGmjsmpmgoI1Tdf9D G1QK+q9pKHlbj/MLr4vZPX6xEwAFeqRKlzL30JPD+O6mOXs1AkEA8UDzfadH1Y+H bcNN2COvCqzqJMwLNRMXHDmUsjHfR2gtzk6D5dDyEaL+O4FLiQCaNXGWWoDTy/HJ Clh1Z0+KYwJBANqRtJ+RvdgHMq0Yd45MMyy0ODGr1B3PoRbUK8EdXpyUNMi1g3iJ tXMbLywNkTfcEXZTlbbkVYwrEl6P2N1r42cCQQDb9UQLBEFSTRJE2RRYQ/CL4yt3 cTGmqkkfyr/v19ii2jEpMBzBo8eQnPL+fdvIhWwT3gQfb+WqxD9v10bzcmnRAkEA mzTgeHd7wg3KdJRtQYTmyhXn2Y3VAJ5SG+3qbCW466NqoCQVCeFwEh75rmSr/Giv lcDhDZCzFuf3EWNAcmuMfQJARsWfM6q7v2p6vkYLLJ7+VvIwookkr6wymF5Zgb9d E6oTM2EeUPSyyrj5IdsU2JCNBH1m3JnUflz8p8/NYCoOZg== -----END RSA PRIVATE KEY----- """ RSA_PUBLIC = """ -----BEGIN RSA PUBLIC KEY----- MIGJAoGBAM36n1r2gzKpHORp7zIt0lwQrQ7RZ6wfomIzP4oOulTACgAz8o8OfEWd E4kNIEfIcSY9WfnHwapXmVD37qX3uN0QRw+jrCSyHJwzzZ+BzE6a+0DLhSjGr8B6 mViqmWFxRMZW6jhmV04uY3ySwabOFamw51PRuRiKkcKfmxZnF3bVAgMBAAE= -----END RSA PUBLIC KEY----- """ # Slightly modifed version of above to test invalid jwts BAD_RSA_PUBLIC = """ -----BEGIN RSA PUBLIC KEY----- MIGJAoGBAM36n1r2gzKpHORp8zIt0lwQrQ7RZ6wfomIzP4oOulTACgAz8o8OfEWd E4kNIEfIcSY9WfnHwapXmVD37qX3uN0QRw+jrCSyHJwzzZ+BzE6a+0DLhSjGr8B6 mViqmWFxRMZW6jhmV04uY3ySwabOFamw51PRuRiKkcKfmxZnF3bVAgMBAAE= -----END RSA PUBLIC KEY----- """ def cartesian_product_configs(): jwt_identity_claims = ['identity', 'sub'] configs = [] for identity in jwt_identity_claims: configs.append({ 'JWT_SECRET_KEY': 'testing_secret_key', 'JWT_ALGORITHM': 'HS256', 'JWT_IDENTITY_CLAIM': identity }) configs.append({ 'JWT_PUBLIC_KEY': RSA_PUBLIC, 'JWT_PRIVATE_KEY': RSA_PRIVATE, 'JWT_ALGORITHM': 'RS256', 'JWT_IDENTITY_CLAIM': identity }) return configs CONFIG_COMBINATIONS = cartesian_product_configs() @pytest.fixture(scope='function', params=CONFIG_COMBINATIONS) def app(request): app = Flask(__name__) for key, value in request.param.items(): app.config[key] = value JWTManager(app) @app.route('/jwt', methods=['POST']) def fresh_access_jwt(): access_token = create_jwt('username') return jsonify(jwt=access_token) @app.route('/protected') @jwt_required def protected(): return jsonify(foo='bar') @app.route('/optional') @jwt_optional def optional(): if get_jwt_identity(): return jsonify(foo='baz') else: return jsonify(foo='bar') return app def _make_jwt_request(test_client, jwt, request_url): app = test_client.application header_name = app.config['JWT_HEADER_NAME'] header_type = app.config['JWT_HEADER_TYPE'] return test_client.get( request_url, content_type='application/json', headers={header_name: '{} {}'.format(header_type, jwt).strip()} ) def _get_jwt(test_client): response = test_client.post('/jwt') json_data = json.loads(response.get_data(as_text=True)) assert response.status_code == 200 assert 'jwt' in json_data return json_data['jwt'] def test_protected_without_jwt(app): test_client = app.test_client() response = test_client.get('/protected') json_data = json.loads(response.get_data(as_text=True)) assert response.status_code == 401 assert json_data == {'msg': 'Missing Authorization Header'} def test_protected_with_jwt(app): test_client = app.test_client() jwt = _get_jwt(test_client) response = _make_jwt_request(test_client, jwt, '/protected') json_data = json.loads(response.get_data(as_text=True)) assert response.status_code == 200 assert json_data == {'foo': 'bar'} def test_optional_without_jwt(app): test_client = app.test_client() response = test_client.get('/optional') json_data = json.loads(response.get_data(as_text=True)) assert response.status_code == 200 assert json_data == {'foo': 'bar'} def test_optional_with_jwt(app): test_client = app.test_client() jwt = _get_jwt(test_client) response = _make_jwt_request(test_client, jwt, '/optional') json_data = json.loads(response.get_data(as_text=True)) assert response.status_code == 200 assert json_data == {'foo': 'baz'} @pytest.mark.parametrize("header_name", ['Authorization', 'Foo']) @pytest.mark.parametrize("header_type", ['Bearer', 'JWT', '']) def test_with_custom_headers(app, header_name, header_type): app.config['JWT_HEADER_NAME'] = header_name app.config['JWT_HEADER_TYPE'] = header_type test_client = app.test_client() jwt = _get_jwt(test_client) response = _make_jwt_request(test_client, jwt, '/protected') json_data = json.loads(response.get_data(as_text=True)) assert response.status_code == 200 assert json_data == {'foo': 'bar'} @pytest.mark.parametrize("endpoint", [ '/protected', '/optional', ]) @pytest.mark.parametrize("header_type", ['Foo', '']) def test_with_bad_header(app, endpoint, header_type): app.config['JWT_HEADER_TYPE'] = header_type test_client = app.test_client() jwt = _get_jwt(test_client) headers = {'Authorization': 'Bearer {}'.format(jwt)} response = test_client.get( endpoint, content_type='application/json', headers=headers ) json_data = json.loads(response.get_data(as_text=True)) expected_results = ( (422, {'msg': "Bad Authorization header. Expected value ''"}), (422, {'msg': "Bad Authorization header. Expected value 'Foo '"}), (200, {'foo': "bar"}) # Returns this if unauthorized in jwt_optional test endpoint ) assert (response.status_code, json_data) in expected_results @pytest.mark.parametrize("endpoint", [ '/protected', '/optional', ]) def test_with_bad_token(app, endpoint): test_client = app.test_client() jwt = _get_jwt(test_client) # change teh secret key here to make the token we just got invalid app.config['JWT_SECRET_KEY'] = 'something_different' app.config['JWT_PUBLIC_KEY'] = BAD_RSA_PUBLIC response = _make_jwt_request(test_client, jwt, endpoint) json_data = json.loads(response.get_data(as_text=True)) assert json_data == {'msg': 'Signature verification failed'} assert response.status_code == 422 @pytest.mark.parametrize("endpoint", [ '/protected', '/optional', ]) def test_expired_token(app, endpoint): app.config['JWT_EXPIRES'] = datetime.timedelta(hours=-1) test_client = app.test_client() jwt = _get_jwt(test_client) response = _make_jwt_request(test_client, jwt, endpoint) json_data = json.loads(response.get_data(as_text=True)) assert json_data == {'msg': 'Token has expired'} assert response.status_code == 401 flask-jwt-simple-0.0.3/tests/test_jwt_manager.py0000664000000000000000000000663513160237734020474 0ustar rootrootimport json import pytest from flask import Flask, jsonify from flask_jwt_simple import JWTManager @pytest.fixture(scope='function') def app(): app = Flask(__name__) return app def _parse_callback(result): response = result[0] status_code = result[1] data = json.loads(response.get_data(as_text=True)) return status_code, data def test_manual_init_app(app): jwt_manager = JWTManager() jwt_manager.init_app(app) assert jwt_manager == app.extensions['flask-jwt-simple'] def test_class_init_app(app): jwt_manager = JWTManager(app) assert jwt_manager == app.extensions['flask-jwt-simple'] def test_default_expired_token_callback(app): jwt_manager = JWTManager(app) with app.test_request_context(): result = jwt_manager._expired_token_callback() status_code, data = _parse_callback(result) assert status_code == 401 assert data == {'msg': 'Token has expired'} def test_custom_expired_token_callback(app): jwt_manager = JWTManager(app) @jwt_manager.expired_token_loader def custom(): return jsonify({"foo": "bar"}), 200 with app.test_request_context(): result = jwt_manager._expired_token_callback() status_code, data = _parse_callback(result) assert status_code == 200 assert data == {'foo': 'bar'} def test_default_invalid_token_callback(app): jwt_manager = JWTManager(app) with app.test_request_context(): err = "Test error" result = jwt_manager._invalid_token_callback(err) status_code, data = _parse_callback(result) assert status_code == 422 assert data == {'msg': err} def test_custom_invalid_token_callback(app): jwt_manager = JWTManager(app) @jwt_manager.invalid_token_loader def custom(err): return jsonify({"foo": "bar"}), 200 with app.test_request_context(): err = "Test error" result = jwt_manager._invalid_token_callback(err) status_code, data = _parse_callback(result) assert status_code == 200 assert data == {'foo': 'bar'} def test_default_unauthorized_callback(app): jwt_manager = JWTManager(app) with app.test_request_context(): err = "Test error" result = jwt_manager._unauthorized_callback(err) status_code, data = _parse_callback(result) assert status_code == 401 assert data == {'msg': err} def test_custom_unauthorized_callback(app): jwt_manager = JWTManager(app) @jwt_manager.unauthorized_loader def custom(err): return jsonify({"foo": "bar"}), 200 with app.test_request_context(): err = "Test error" result = jwt_manager._unauthorized_callback(err) status_code, data = _parse_callback(result) assert status_code == 200 assert data == {'foo': 'bar'} def test_default_get_jwt_data_callback(app): jwt_manager = JWTManager(app) with app.test_request_context(): result = jwt_manager._get_jwt_data(identity='foo') assert 'exp' in result assert 'iat' in result assert 'nbf' in result assert result['sub'] == 'foo' def test_custom_get_jwt_data_callback(app): jwt_manager = JWTManager(app) @jwt_manager.jwt_data_loader def custom(identity): return {"foo": "bar"} with app.test_request_context(): result = jwt_manager._get_jwt_data(identity='foo') assert result == {"foo": "bar"} flask-jwt-simple-0.0.3/tests/test_config.py0000664000000000000000000000622313160237734017434 0ustar rootrootimport datetime import pytest from flask import Flask from flask_jwt_simple import JWTManager from flask_jwt_simple.config import config @pytest.fixture(scope='function') def app(): app = Flask(__name__) JWTManager(app) return app # noinspection PyStatementEffect def test_default_configs(app): with app.test_request_context(): assert config.is_asymmetric is False assert config.header_name == 'Authorization' assert config.header_type == 'Bearer' assert config.jwt_expires == datetime.timedelta(hours=1) assert config.algorithm == 'HS256' assert config.identity_claim == 'sub' assert config.audience is None with pytest.raises(RuntimeError): config.encode_key with pytest.raises(RuntimeError): config.decode_key def test_default_with_symetric_secret_key(app): app.config['JWT_SECRET_KEY'] = 'foobarbaz' with app.test_request_context(): assert config.encode_key == 'foobarbaz' assert config.decode_key == 'foobarbaz' def test_default_with_assymetric_secret_key(app): app.config['JWT_PUBLIC_KEY'] = 'foo' app.config['JWT_PRIVATE_KEY'] = 'bar' app.config['JWT_ALGORITHM'] = 'RS256' with app.test_request_context(): assert config.decode_key == 'foo' assert config.encode_key == 'bar' def test_config_overrides(app): with app.test_request_context(): app.config['JWT_EXPIRES'] = datetime.timedelta(hours=2) assert config.jwt_expires == datetime.timedelta(hours=2) app.config['JWT_IDENTITY_CLAIM'] = 'identity' assert config.identity_claim == 'identity' app.config['JWT_HEADER_NAME'] = 'banana' assert config.header_name == 'banana' app.config['JWT_HEADER_TYPE'] = 'banana' assert config.header_type == 'banana' app.config['JWT_HEADER_TYPE'] = '' assert config.header_type == '' app.config['JWT_ALGORITHM'] = 'HS512' assert config.algorithm == 'HS512' assert config.is_asymmetric is False app.config['JWT_ALGORITHM'] = 'RS256' assert config.algorithm == 'RS256' assert config.is_asymmetric is True app.config['JWT_DECODE_AUDIENCE'] = 'foobar' assert config.audience == 'foobar' # noinspection PyStatementEffect def test_config_invalid_options(app): with app.test_request_context(): app.config['JWT_SECRET_KEY'] = 'foobarbaz' app.config['JWT_ALGORITHM'] = 'RS256' with pytest.raises(RuntimeError): config.encode_key with pytest.raises(RuntimeError): config.decode_key app.config['JWT_SECRET_KEY'] = None app.config['JWT_PUBLIC_KEY'] = 'foo' app.config['JWT_PRIVATE_KEY'] = 'bar' app.config['JWT_ALGORITHM'] = 'HS256' with pytest.raises(RuntimeError): config.encode_key with pytest.raises(RuntimeError): config.decode_key app.config['JWT_HEADER_NAME'] = '' with pytest.raises(RuntimeError): config.header_name app.config['JWT_EXPIRES'] = 'banana' with pytest.raises(RuntimeError): config.jwt_expires flask-jwt-simple-0.0.3/requirements.txt0000664000000000000000000000070213160237734016674 0ustar rootrootalabaster==0.7.10 Babel==2.5.0 certifi==2017.7.27.1 chardet==3.0.4 click==6.7 docutils==0.14 Flask==0.12.2 Flask-Sphinx-Themes==1.0.1 idna==2.6 imagesize==0.7.1 itsdangerous==0.24 Jinja2==2.9.6 MarkupSafe==1.0 pluggy==0.4.0 py==1.4.34 pyasn1==0.3.2 Pygments==2.2.0 PyJWT==1.5.2 pytz==2017.2 requests==2.18.4 six==1.10.0 snowballstemmer==1.2.1 Sphinx==1.6.3 sphinxcontrib-websupport==1.0.1 tox==2.7.0 urllib3==1.22 virtualenv==15.1.0 Werkzeug==0.12.2 flask-jwt-simple-0.0.3/docs/0000775000000000000000000000000013160237734014341 5ustar rootrootflask-jwt-simple-0.0.3/docs/__init__.py0000664000000000000000000000000013160237734016440 0ustar rootrootflask-jwt-simple-0.0.3/docs/conf.py0000664000000000000000000002366013160237734015647 0ustar rootroot# -*- coding: utf-8 -*- # import sys, os # flask-jwt-simple documentation build configuration file, created by # sphinx-quickstart on Thu Oct 6 13:07:36 2016. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('../../..')) sys.path.insert(0, os.path.abspath('..')) sys.path.insert(0, os.path.abspath('../flask_jwt_simple/')) sys.path.append(os.path.join(os.path.dirname(__file__), '_themes')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode', 'sphinx.ext.autosummary' ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The encoding of source files. # # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'flask-jwt-simple' author = u'Landon Gilbert-Bland' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = u'0.0.3' # The full version, including alpha/beta/rc tags. release = u'0.0.3' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # # today = '' # # Else, today_fmt is used as the format for a strftime call. # # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # # show_authors = False # The name of the Pygments (syntax highlighting) style to use. # pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # #html_theme = 'sphinx_rtd_theme' html_theme = 'flask' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. html_theme_path = ["_themes", ] # The name for this set of Sphinx documents. # " v documentation" by default. # # html_title = u'flask-jwt-simple v0.0.1' # A shorter title for the navigation bar. Default is the same as html_title. # # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # # html_logo = None # The name of an image file (relative to this directory) to use as a favicon of # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". #html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # # html_extra_path = [] # If not None, a 'Last updated on:' timestamp is inserted at every page # bottom, using the given strftime format. # The empty string is equivalent to '%b %d, %Y'. # # html_last_updated_fmt = None # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # # html_additional_pages = {} # If false, no module index is generated. # # html_domain_indices = True # If false, no index is generated. # # html_use_index = True # If true, the index is split into individual pages for each letter. # # html_split_index = False # If true, links to the reST sources are added to the pages. # # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' # # html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # 'ja' uses this config value. # 'zh' user can custom change `jieba` dictionary path. # # html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. # # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = 'flask-jwt-simpledoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'flask-jwt-simple.tex', u'flask-jwt-simple Documentation', u'vimalloc rlam3', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # # latex_use_parts = False # If true, show page references after internal links. # # latex_show_pagerefs = False # If true, show URL addresses after external links. # # latex_show_urls = False # Documents to append as an appendix to all manuals. # # latex_appendices = [] # It false, will not define \strong, \code, itleref, \crossref ... but only # \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added # packages. # # latex_keep_old_macro_names = True # If false, no module index is generated. # # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'flask-jwt-simple', u'flask-jwt-simple Documentation', [author], 1) ] # If true, show URL addresses after external links. # # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'flask-jwt-simple', u'flask-jwt-simple Documentation', author, 'flask-jwt-simple', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # # texinfo_appendices = [] # If false, no module index is generated. # # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # # texinfo_no_detailmenu = False flask-jwt-simple-0.0.3/docs/Makefile0000664000000000000000000001673613160237734016016 0ustar rootroot# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " applehelp to make an Apple Help Book" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " epub3 to make an epub3" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " coverage to run coverage check of the documentation (if enabled)" @echo " dummy to check syntax errors of document sources" .PHONY: clean clean: rm -rf $(BUILDDIR)/* .PHONY: html html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." .PHONY: dirhtml dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." .PHONY: singlehtml singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." .PHONY: pickle pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." .PHONY: json json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." .PHONY: htmlhelp htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." .PHONY: qthelp qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/flask-jwt-simple.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/flask-jwt-simple.qhc" .PHONY: applehelp applehelp: $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp @echo @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." @echo "N.B. You won't be able to view it unless you put it in" \ "~/Library/Documentation/Help or install it in your application" \ "bundle." .PHONY: devhelp devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/flask-jwt-simple" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/flask-jwt-simple" @echo "# devhelp" .PHONY: epub epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." .PHONY: epub3 epub3: $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 @echo @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." .PHONY: latex latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." .PHONY: latexpdf latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: latexpdfja latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: text text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." .PHONY: man man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." .PHONY: texinfo texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." .PHONY: info info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." .PHONY: gettext gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." .PHONY: changes changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." .PHONY: linkcheck linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." .PHONY: doctest doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." .PHONY: coverage coverage: $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage @echo "Testing of coverage in the sources finished, look at the " \ "results in $(BUILDDIR)/coverage/python.txt." .PHONY: xml xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." .PHONY: pseudoxml pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." .PHONY: dummy dummy: $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy @echo @echo "Build finished. Dummy builder generates no files." flask-jwt-simple-0.0.3/docs/installation.rst0000664000000000000000000000121513160237734017573 0ustar rootrootInstallation ============== The easiest way to start working with this extension with pip: .. code-block:: bash $ pip install flask-jwt-simple If you want to use asymmetric (public/private key) key signing algorithms, include the ``asymmetric_crypto`` extra requirements. .. code-block:: bash $ pip install flask-jwt-simple[asymmetric_crypto] Note that if you are using ZSH (possibly other shells too), you will need to escape the brackets .. code-block:: bash $ pip install flask-jwt-simple\[asymmetric_crypto\] If you prefer to install from source, you can clone this repo and run .. code-block:: bash $ python setup.py install flask-jwt-simple-0.0.3/docs/change_jwt_claims.rst0000664000000000000000000000100213160237734020525 0ustar rootrootChanging JWT Claims =================== You may want to change the claims that are stored in the created JWTs. This can be done with the `@jwt.jwt_data_loader` decorator, and the jwt can be accessed in your protected endpoints with the `get_jwt()` function. .. literalinclude:: ../examples/change_token_data.py Note: be careful of what you what data you put in the JWT. Any data in the JWT can be easily viewed with anyone who has access to the token. Make sure you don't put any sensitive information in them! flask-jwt-simple-0.0.3/docs/basic_usage.rst0000664000000000000000000000256713160237734017352 0ustar rootrootBasic Usage =========== In its simplest form, there is not much to using flask_jwt_simple. .. literalinclude:: ../examples/simple.py To access a jwt_required protected view, all we have to do is send in the JWT with the request. By default, this is done with an authorization header that looks like: .. code-block :: bash Authorization: Bearer We can see this in action using CURL: .. code-block :: bash $ curl http://localhost:5000/protected { "msg": "Missing Authorization Header" } $ curl -H "Content-Type: application/json" -X POST \ -d '{"username":"test","password":"test"}' http://localhost:5000/login { "jwt": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MDM1OTk3MTgsImlhdCI6MTUwMzU5NjExOCwibmJmIjoxNTAzNTk2MTE4LCJzdWIiOiJ0ZXN0In0.G2GnN9NgvvmSKgRDGok0OjAyDWkG_qCn4FTxSfPUXDY" } $ export ACCESS="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MDM1OTk3MTgsImlhdCI6MTUwMzU5NjExOCwibmJmIjoxNTAzNTk2MTE4LCJzdWIiOiJ0ZXN0In0.G2GnN9NgvvmSKgRDGok0OjAyDWkG_qCn4FTxSfPUXDY" $ curl -H "Authorization: Bearer $ACCESS" http://localhost:5000/protected { "hello_from": "test" } NOTE: Remember to change the JWT_SECRET_KEY on your application, and insure that no one is able to view it. The json web tokens are signed with the secret key, so if someone gets that, they can create arbitrary tokens, and in essence log in as any user. flask-jwt-simple-0.0.3/docs/index.rst0000664000000000000000000000040613160237734016202 0ustar rootrootFlask JWT Simple's documentation ================================== In here you will find examples of how to use Flask JWT Simple. .. toctree:: :maxdepth: 2 installation basic_usage change_jwt_claims changing_default_behavior options api flask-jwt-simple-0.0.3/docs/options.rst0000664000000000000000000000375313160237734016576 0ustar rootrootConfiguration Options ===================== You can change many options for how this extension works via .. code-block:: python app.config['OPTION_NAME'] = new_option_value .. tabularcolumns:: |p{6.5cm}|p{8.5cm}| ================================= ========================================= ``JWT_HEADER_NAME`` What header to look for the JWT in a request. Defaults to ``'Authorization'`` ``JWT_HEADER_TYPE`` What type of header the JWT is in. Defaults to ``'Bearer'``. This can be an empty string, in which case the header contains only the JWT (instead of something like ``Authorization: Bearer ``) ``JWT_EXPIRES`` How long a JWT created with `create_jwt()` should live before it expires. This takes a ``datetime.timedelta``, and defaults to 1 hour ``JWT_ALGORITHM`` Which algorithm to sign the JWT with. `See here `_ for the options. Defaults to ``'HS256'``. ``JWT_SECRET_KEY`` The secret key needed for symmetric based signing algorithms, such as ``HS*``. ``JWT_PUBLIC_KEY`` The public key needed for asymmetric based signing algorithms, such as ``RS*`` or ``ES*``. PEM format expected. ``JWT_PRIVATE_KEY`` The private key needed for asymmetric based signing algorithms, such as ``RS*`` or ``ES*``. PEM format expected. ``JWT_IDENTITY_CLAIM`` Which claim the `get_jwt_identity()` function will use to get the identity out of a JWT. Defaults to ``'sub'``. ``JWT_DECODE_AUDIENCE`` The audience expected to be set in the JWT token when it is decoded. ================================= ========================================= flask-jwt-simple-0.0.3/docs/changing_default_behavior.rst0000664000000000000000000000232013160237734022231 0ustar rootrootChanging Default Behaviors ========================== We provide what we think are sensible behaviors when attempting to access a protected endpoint. If the JWT is not valid for any reason (missing, expired, tampered with, etc) we will return json in the format of {'msg': 'why accessing endpoint failed'} along with an appropriate http status code (401 or 422). However, you may want to customize what you return in some situations. We can do that with the jwt_manager loader functions. An example of this looks like: .. literalinclude:: ../examples/change_behaviors.py Possible loader functions are: .. list-table:: :header-rows: 1 * - Loader Decorator - Description - Function Arguments * - **expired_token_loader** - Function to call when an expired token accesses a protected endpoint - None * - **invalid_token_loader** - Function to call when an invalid token accesses a protected endpoint - Takes one argument - an error string indicating why the token is invalid * - **unauthorized_loader** - Function to call when a request with no JWT accesses a protected endpoint - Takes one argument - an error string indicating why the request in unauthorized flask-jwt-simple-0.0.3/docs/api.rst0000664000000000000000000000132013160237734015640 0ustar rootrootAPI Documentation ================= In here you will find the API for everything exposed in this extension. Configuring JWT Options ~~~~~~~~~~~~~~~~~~~~~~~ .. currentmodule:: flask_jwt_simple .. module:: flask_jwt_simple .. autoclass:: JWTManager .. automethod:: __init__ .. automethod:: init_app .. automethod:: expired_token_loader .. automethod:: invalid_token_loader .. automethod:: unauthorized_loader .. automethod:: jwt_data_loader Protected endpoint decorators ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. autofunction:: jwt_required .. autofunction:: jwt_optional Utilities ~~~~~~~~~ .. autofunction:: get_jwt .. autofunction:: get_jwt_identity .. autofunction:: create_jwt .. autofunction:: decode_jwt flask-jwt-simple-0.0.3/LICENSE0000664000000000000000000000204713160237734014421 0ustar rootrootMIT License Copyright (c) 2016 Landon Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. flask-jwt-simple-0.0.3/.gitignore0000664000000000000000000000211013160237734015373 0ustar rootroot# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *,cover .hypothesis/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # IPython Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # dotenv .env # virtualenv venv/ ENV/ # Spyder project settings .spyderproject # Rope project settings .ropeproject # pycharm .idea/ # MacOS specific crap .DS_Store flask-jwt-simple-0.0.3/tox.ini0000664000000000000000000000075613160237734014734 0ustar rootroot# Tox (http://tox.testrun.org/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. [tox] envlist = py27, py33, py34, py35, py36 [testenv] commands = coverage run --source flask_jwt_simple/ -m pytest tests coverage report -m deps = coverage cryptography pytest # TODO why does this not work? # extras = # asymmetric_crypto flask-jwt-simple-0.0.3/examples/0000775000000000000000000000000013160237734015227 5ustar rootrootflask-jwt-simple-0.0.3/examples/change_token_data.py0000664000000000000000000000315713160237734021225 0ustar rootrootfrom datetime import datetime from flask import Flask, jsonify, request, current_app from flask_jwt_simple import ( JWTManager, jwt_required, create_jwt, get_jwt ) app = Flask(__name__) app.config['JWT_SECRET_KEY'] = 'super-secret' # Change this! jwt = JWTManager(app) # Using the jwt_data_loader, we can change the values that # will be present in the JWTs (that are made by the # `create_jwt()` function). This will override everything # currently in the token, so you will need to re-add # the default claims (exp, iat, nbt, sub) if you still # want them. @jwt.jwt_data_loader def add_claims_to_access_token(identity): if identity == 'admin': roles = 'admin' else: roles = 'peasant' now = datetime.utcnow() return { 'exp': now + current_app.config['JWT_EXPIRES'], 'iat': now, 'nbf': now, 'sub': identity, 'roles': roles } @app.route('/login', methods=['POST']) def login(): username = request.json.get('username', None) password = request.json.get('password', None) if username != 'test' or password != 'test': return jsonify({"msg": "Bad username or password"}), 401 ret = {'jwt': create_jwt(username)} return jsonify(ret), 200 # In a protected view, you can get the full data encoded in the # jwt with the `get_jwt()` function. @app.route('/protected', methods=['GET']) @jwt_required def protected(): jwt_data = get_jwt() if jwt_data['roles'] != 'admin': return jsonify(msg="Permission denied"), 403 return jsonify(msg="Do not forget to drink your ovaltine") if __name__ == '__main__': app.run() flask-jwt-simple-0.0.3/examples/simple.py0000664000000000000000000000262313160237734017075 0ustar rootrootfrom flask import Flask, jsonify, request from flask_jwt_simple import ( JWTManager, jwt_required, create_jwt, get_jwt_identity ) app = Flask(__name__) # Setup the Flask-JWT-Simple extension app.config['JWT_SECRET_KEY'] = 'super-secret' # Change this! jwt = JWTManager(app) # Provide a method to create access tokens. The create_jwt() # function is used to actually generate the token @app.route('/login', methods=['POST']) def login(): if not request.is_json: return jsonify({"msg": "Missing JSON in request"}), 400 params = request.get_json() username = params.get('username', None) password = params.get('password', None) if not username: return jsonify({"msg": "Missing username parameter"}), 400 if not password: return jsonify({"msg": "Missing password parameter"}), 400 if username != 'test' or password != 'test': return jsonify({"msg": "Bad username or password"}), 401 # Identity can be any data that is json serializable ret = {'jwt': create_jwt(identity=username)} return jsonify(ret), 200 # Protect a view with jwt_required, which requires a valid jwt # to be present in the headers. @app.route('/protected', methods=['GET']) @jwt_required def protected(): # Access the identity of the current user with get_jwt_identity return jsonify({'hello_from': get_jwt_identity()}), 200 if __name__ == '__main__': app.run() flask-jwt-simple-0.0.3/examples/change_behaviors.py0000664000000000000000000000227313160237734021074 0ustar rootrootfrom flask import Flask, jsonify, request from flask_jwt_simple import JWTManager, jwt_required, create_jwt app = Flask(__name__) app.config['JWT_SECRET_KEY'] = 'super-secret' # Change this! jwt = JWTManager(app) # Using the expired_token_loader decorator, we will now call # this function whenever an expired but otherwise valid access # token attempts to access an endpoint. There are other # behaviors tht can be changed with these loader functions. # Check the docs for a full list. @jwt.expired_token_loader def my_expired_token_callback(): err_json = { "status": 401, "title": "Expired JWT", "detail": "The JWT has expired" } return jsonify(err_json), 401 @app.route('/login', methods=['POST']) def login(): username = request.json.get('username', None) password = request.json.get('password', None) if username != 'test' or password != 'test': return jsonify({"msg": "Bad username or password"}), 401 ret = {'access_token': create_jwt(username)} return jsonify(ret), 200 @app.route('/protected', methods=['GET']) @jwt_required def protected(): return jsonify({'hello': 'world'}), 200 if __name__ == '__main__': app.run() flask-jwt-simple-0.0.3/flask_jwt_simple/0000775000000000000000000000000013160237734016746 5ustar rootrootflask-jwt-simple-0.0.3/flask_jwt_simple/__init__.py0000664000000000000000000000024113160237734021054 0ustar rootrootfrom .jwt_manager import JWTManager from .view_decorators import jwt_required, jwt_optional from .utils import get_jwt, get_jwt_identity, decode_jwt, create_jwt flask-jwt-simple-0.0.3/flask_jwt_simple/config.py0000664000000000000000000000633713160237734020576 0ustar rootrootimport datetime from flask import current_app # Older versions of pyjwt do not have the requires_cryptography set. Also, # older versions will not be adding new algorithms to them, so I can hard code # the default version here and be safe. If there is a newer algorithm someone # wants to use, they will need newer versions of pyjwt and it will be included # in their requires_cryptography set, and if they attempt to use it in older # versions of pyjwt, it will kick it out as an unrecognized algorithm. try: from jwt.algorithms import requires_cryptography except ImportError: # pragma: no cover requires_cryptography = {'RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES521', 'ES512', 'PS256', 'PS384', 'PS512'} class _Config(object): """ Helper object for accessing and verifying options in this extension. This is meant for internal use of the application; modifying config options should be done with flasks ```app.config```. Default values for the configuration options are set in the jwt_manager object. All of these values are read only. """ @property def is_asymmetric(self): return self.algorithm in requires_cryptography @property def encode_key(self): return self._private_key if self.is_asymmetric else self._secret_key @property def decode_key(self): return self._public_key if self.is_asymmetric else self._secret_key @property def header_name(self): name = current_app.config['JWT_HEADER_NAME'] if not name: raise RuntimeError("JWT_HEADER_NAME cannot be empty") return name @property def header_type(self): return current_app.config['JWT_HEADER_TYPE'] @property def jwt_expires(self): delta = current_app.config['JWT_EXPIRES'] if not isinstance(delta, datetime.timedelta): raise RuntimeError('JWT_EXPIRES must be a datetime.timedelta') return delta @property def algorithm(self): return current_app.config['JWT_ALGORITHM'] @property def audience(self): return current_app.config['JWT_DECODE_AUDIENCE'] @property def _secret_key(self): key = current_app.config['JWT_SECRET_KEY'] if not key: raise RuntimeError('JWT_SECRET_KEY must be set to use ' 'symmetric cryptography algorithm ' '"{}"'.format(self.algorithm)) return key @property def _public_key(self): key = current_app.config['JWT_PUBLIC_KEY'] if not key: raise RuntimeError('JWT_PUBLIC_KEY must be set to use ' 'asymmetric cryptography algorithm ' '"{}"'.format(self.algorithm)) return key @property def _private_key(self): key = current_app.config['JWT_PRIVATE_KEY'] if not key: raise RuntimeError('JWT_PRIVATE_KEY must be set to use ' 'asymmetric cryptography algorithm ' '"{}"'.format(self.algorithm)) return key @property def identity_claim(self): return current_app.config['JWT_IDENTITY_CLAIM'] config = _Config() flask-jwt-simple-0.0.3/flask_jwt_simple/view_decorators.py0000664000000000000000000000472013160237734022522 0ustar rootrootfrom functools import wraps from flask import request try: from flask import _app_ctx_stack as ctx_stack except ImportError: # pragma: no cover from flask import _request_ctx_stack as ctx_stack from flask_jwt_simple.utils import decode_jwt from flask_jwt_simple.config import config from flask_jwt_simple.exceptions import InvalidHeaderError, NoAuthorizationError def jwt_required(fn): """ If you decorate a view with this, it will ensure that the requester has a valid JWT before calling the actual view. :param fn: The view function to decorate """ @wraps(fn) def wrapper(*args, **kwargs): jwt_data = _decode_jwt_from_headers() ctx_stack.top.jwt = jwt_data return fn(*args, **kwargs) return wrapper def jwt_optional(fn): """ If you decorate a view with this, it will check the request for a valid JWT and put it into the Flask application context before calling the view. If no authorization header is present, the view will be called without the application context being changed. Other authentication errors are not affected. For example, if an expired JWT is passed in, it will still not be able to access an endpoint protected by this decorator. :param fn: The view function to decorate """ @wraps(fn) def wrapper(*args, **kwargs): try: jwt_data = _decode_jwt_from_headers() ctx_stack.top.jwt = jwt_data except (NoAuthorizationError, InvalidHeaderError): pass return fn(*args, **kwargs) return wrapper def _decode_jwt_from_headers(): header_name = config.header_name header_type = config.header_type # Verify we have the auth header jwt_header = request.headers.get(header_name, None) if not jwt_header: raise NoAuthorizationError("Missing {} Header".format(header_name)) # Make sure the header is in a valid format that we are expecting, ie # : parts = jwt_header.split() if not header_type: if len(parts) != 1: msg = "Bad {} header. Expected value ''".format(header_name) raise InvalidHeaderError(msg) token = parts[0] else: if parts[0] != header_type or len(parts) != 2: msg = "Bad {} header. Expected value '{} '".format(header_name, header_type) raise InvalidHeaderError(msg) token = parts[1] return decode_jwt(encoded_token=token) flask-jwt-simple-0.0.3/flask_jwt_simple/default_callbacks.py0000664000000000000000000000237213160237734022747 0ustar rootrootimport datetime from flask import jsonify from flask_jwt_simple.config import config def default_jwt_data_callback(identity): now = datetime.datetime.utcnow() identity_claim = config.identity_claim return { 'exp': now + config.jwt_expires, 'iat': now, 'nbf': now, identity_claim: identity } def default_expired_token_callback(): """ By default, if an expired token attempts to access a protected endpoint, we return a generic error message with a 401 status """ return jsonify({'msg': 'Token has expired'}), 401 def default_invalid_token_callback(error_string): """ By default, if an invalid token attempts to access a protected endpoint, we return the error string for why it is not valid with a 422 status code :param error_string: String indicating why the token is invalid """ return jsonify({'msg': error_string}), 422 def default_unauthorized_callback(error_string): """ By default, if a protected endpoint is accessed without a JWT, we return the error string indicating why this is unauthorized, with a 401 status code :param error_string: String indicating why this request is unauthorized """ return jsonify({'msg': error_string}), 401 flask-jwt-simple-0.0.3/flask_jwt_simple/jwt_manager.py0000664000000000000000000001453113160237734021622 0ustar rootrootimport datetime import jwt from flask_jwt_simple.config import config from flask_jwt_simple.exceptions import NoAuthorizationError, InvalidHeaderError from flask_jwt_simple.default_callbacks import ( default_expired_token_callback, default_invalid_token_callback, default_unauthorized_callback, default_jwt_data_callback ) class JWTManager(object): """ This object is used to hold the JWT settings and callback functions. Instances :class:`JWTManager` are *not* bound to specific apps, so you can create one in the main body of your code and then bind it to your app in a factory function. """ def __init__(self, app=None): """ Create the JWTManager instance. You can either pass a flask application in directly here to register this extension with the flask app, or call init_app after creating this object :param app: A flask application """ # Register the default error handler callback methods. These can be # overridden with the appropriate loader decorators. self._expired_token_callback = default_expired_token_callback self._invalid_token_callback = default_invalid_token_callback self._unauthorized_callback = default_unauthorized_callback self._get_jwt_data = default_jwt_data_callback # Register this extension with the flask app now (if it is provided) if app is not None: self.init_app(app) def init_app(self, app): """ Register this extension with the flask app :param app: A flask application """ # Save this so we can use it later in the extension if not hasattr(app, 'extensions'): # pragma: no cover app.extensions = {} app.extensions['flask-jwt-simple'] = self # Set all the default configurations for this extension self._set_default_configuration_options(app) self._set_error_handler_callbacks(app) # Set propagate exceptions, so all of our error handlers properly # work in production app.config['PROPAGATE_EXCEPTIONS'] = True def _set_error_handler_callbacks(self, app): """ Sets the error handler callbacks used by this extension """ @app.errorhandler(NoAuthorizationError) def handle_no_auth_error(e): return self._unauthorized_callback(str(e)) @app.errorhandler(InvalidHeaderError) def handle_invalid_header_error(e): return self._invalid_token_callback(str(e)) @app.errorhandler(jwt.ExpiredSignatureError) def handle_expired_error(e): return self._expired_token_callback() @app.errorhandler(jwt.InvalidTokenError) def handle_invalid_token_error(e): return self._invalid_token_callback(str(e)) @staticmethod def _set_default_configuration_options(app): """ Sets the default configuration options used by this extension """ # Options for JWTs when the TOKEN_LOCATION is headers app.config.setdefault('JWT_HEADER_NAME', 'Authorization') app.config.setdefault('JWT_HEADER_TYPE', 'Bearer') # How long an a token created with 'create_jwt' will last before # it expires (when using the default jwt_data_callback function). app.config.setdefault('JWT_EXPIRES', datetime.timedelta(hours=1)) # What algorithm to use to sign the token. See here for a list of options: # https://github.com/jpadilla/pyjwt/blob/master/jwt/api_jwt.py app.config.setdefault('JWT_ALGORITHM', 'HS256') # Key that acts as the identity for the JWT app.config.setdefault('JWT_IDENTITY_CLAIM', 'sub') # Expected value of the audience claim app.config.setdefault('JWT_DECODE_AUDIENCE', None) # Secret key to sign JWTs with. Only used if a symmetric algorithm is # used (such as the HS* algorithms). app.config.setdefault('JWT_SECRET_KEY', None) # Keys to sign JWTs with when use when using an asymmetric # (public/private key) algorithms, such as RS* or EC* app.config.setdefault('JWT_PRIVATE_KEY', None) app.config.setdefault('JWT_PUBLIC_KEY', None) def expired_token_loader(self, callback): """ Sets the callback method to be called if an expired JWT is received The default implementation will return json '{"msg": "Token has expired"}' with a 401 status code. Callback must be a function that takes zero arguments. """ self._expired_token_callback = callback return callback def invalid_token_loader(self, callback): """ Sets the callback method to be called if an invalid JWT is received. The default implementation will return json '{"msg": }' with a 401 status code. Callback must be a function that takes only one argument, which is the error message of why the token is invalid. """ self._invalid_token_callback = callback return callback def unauthorized_loader(self, callback): """ Sets the callback method to be called if no JWT is received The default implementation will return '{"msg": "Missing Authorization Header"}' json with a 401 status code. Callback must be a function that takes only one argument, which is the error message of why the token is invalid. """ self._unauthorized_callback = callback return callback def jwt_data_loader(self, callback): """ Sets the callback method to be called for what data should be included in a JWT (with the create_jwt() function). The default implementation will return the following data. .. code-block:: python { 'exp': now + current_app.config['JWT_EXPIRES'], 'iat': now, 'nbf': now, 'sub': identity } Callback must be a function that takes only one argument, which is the identity of the user this JWT is for. """ self._get_jwt_data = callback return callback def _create_jwt(self, identity): jwt_data = self._get_jwt_data(identity) secret = config.encode_key algorithm = config.algorithm return jwt.encode(jwt_data, secret, algorithm).decode('utf-8') flask-jwt-simple-0.0.3/flask_jwt_simple/utils.py0000664000000000000000000000307713160237734020467 0ustar rootrootimport jwt from flask import current_app try: from flask import _app_ctx_stack as ctx_stack except ImportError: # pragma: no cover from flask import _request_ctx_stack as ctx_stack from flask_jwt_simple.config import config def _get_jwt_manager(): try: return current_app.extensions['flask-jwt-simple'] except KeyError: # pragma: no cover raise RuntimeError("You must initialize a JWTManager with this flask " "application before using this method") def get_jwt(): """ Returns the python dictionary which has all of the data in this JWT. If no JWT is currently present, and empty dict is returned """ return getattr(ctx_stack.top, 'jwt', {}) def get_jwt_identity(): """ Returns the identity of the JWT in this context. If no JWT is present, None is returned. """ return get_jwt().get(config.identity_claim, None) def decode_jwt(encoded_token): """ Returns the decoded token from an encoded one. This does all the checks to insure that the decoded token is valid before returning it. """ secret = config.decode_key algorithm = config.algorithm audience = config.audience return jwt.decode(encoded_token, secret, algorithms=[algorithm], audience=audience) def create_jwt(identity): """ Creates a new JWT. :param identity: The identity of this token. This can be anything that is json serializable. :return: A utf-8 encoded jwt. """ jwt_manager = _get_jwt_manager() return jwt_manager._create_jwt(identity) flask-jwt-simple-0.0.3/flask_jwt_simple/exceptions.py0000664000000000000000000000065613160237734021510 0ustar rootrootclass FlaskJWTException(Exception): """ Base except which all flask_jwt_simple errors extend """ pass class InvalidHeaderError(FlaskJWTException): """ An error raised when the expected header format does not match what was received """ pass class NoAuthorizationError(FlaskJWTException): """ An error raised when no JWT was found when a protected endpoint was accessed """ pass flask-jwt-simple-0.0.3/.travis.yml0000664000000000000000000000063213160237734015523 0ustar rootrootlanguage: python matrix: include: - python: 3.6 env: TOXENV=py - python: 3.5 env: TOXENV=py - python: 3.4 env: TOXENV=py - python: 3.3 env: TOXENV=py - python: 2.7 env: TOXENV=py - python: pypy env: TOXENV=py sudo: false install: - pip install -U pip - pip install -U tox coverage coveralls script: - tox after_success: - coveralls