浏览代码

[libs] Adding PyJWT 1.7.1

Romain Rigaux 4 年之前
父节点
当前提交
d9884595a8
共有 49 个文件被更改,包括 4580 次插入0 次删除
  1. 29 0
      desktop/core/ext-py/PyJWT-1.7.1/AUTHORS
  2. 292 0
      desktop/core/ext-py/PyJWT-1.7.1/CHANGELOG.md
  3. 21 0
      desktop/core/ext-py/PyJWT-1.7.1/LICENSE
  4. 8 0
      desktop/core/ext-py/PyJWT-1.7.1/MANIFEST.in
  5. 106 0
      desktop/core/ext-py/PyJWT-1.7.1/PKG-INFO
  6. 81 0
      desktop/core/ext-py/PyJWT-1.7.1/README.rst
  7. 31 0
      desktop/core/ext-py/PyJWT-1.7.1/jwt/__init__.py
  8. 168 0
      desktop/core/ext-py/PyJWT-1.7.1/jwt/__main__.py
  9. 403 0
      desktop/core/ext-py/PyJWT-1.7.1/jwt/algorithms.py
  10. 242 0
      desktop/core/ext-py/PyJWT-1.7.1/jwt/api_jws.py
  11. 222 0
      desktop/core/ext-py/PyJWT-1.7.1/jwt/api_jwt.py
  12. 68 0
      desktop/core/ext-py/PyJWT-1.7.1/jwt/compat.py
  13. 0 0
      desktop/core/ext-py/PyJWT-1.7.1/jwt/contrib/__init__.py
  14. 0 0
      desktop/core/ext-py/PyJWT-1.7.1/jwt/contrib/algorithms/__init__.py
  15. 60 0
      desktop/core/ext-py/PyJWT-1.7.1/jwt/contrib/algorithms/py_ecdsa.py
  16. 46 0
      desktop/core/ext-py/PyJWT-1.7.1/jwt/contrib/algorithms/pycrypto.py
  17. 59 0
      desktop/core/ext-py/PyJWT-1.7.1/jwt/exceptions.py
  18. 61 0
      desktop/core/ext-py/PyJWT-1.7.1/jwt/help.py
  19. 113 0
      desktop/core/ext-py/PyJWT-1.7.1/jwt/utils.py
  20. 19 0
      desktop/core/ext-py/PyJWT-1.7.1/setup.cfg
  21. 87 0
      desktop/core/ext-py/PyJWT-1.7.1/setup.py
  22. 0 0
      desktop/core/ext-py/PyJWT-1.7.1/tests/__init__.py
  23. 12 0
      desktop/core/ext-py/PyJWT-1.7.1/tests/compat.py
  24. 0 0
      desktop/core/ext-py/PyJWT-1.7.1/tests/contrib/__init__.py
  25. 190 0
      desktop/core/ext-py/PyJWT-1.7.1/tests/contrib/test_algorithms.py
  26. 57 0
      desktop/core/ext-py/PyJWT-1.7.1/tests/keys/__init__.py
  27. 9 0
      desktop/core/ext-py/PyJWT-1.7.1/tests/keys/jwk_ec_key.json
  28. 8 0
      desktop/core/ext-py/PyJWT-1.7.1/tests/keys/jwk_ec_pub.json
  29. 7 0
      desktop/core/ext-py/PyJWT-1.7.1/tests/keys/jwk_hmac.json
  30. 13 0
      desktop/core/ext-py/PyJWT-1.7.1/tests/keys/jwk_rsa_key.json
  31. 7 0
      desktop/core/ext-py/PyJWT-1.7.1/tests/keys/jwk_rsa_pub.json
  32. 9 0
      desktop/core/ext-py/PyJWT-1.7.1/tests/keys/testkey2_rsa.pub.pem
  33. 7 0
      desktop/core/ext-py/PyJWT-1.7.1/tests/keys/testkey_ec
  34. 6 0
      desktop/core/ext-py/PyJWT-1.7.1/tests/keys/testkey_ec.pub
  35. 1 0
      desktop/core/ext-py/PyJWT-1.7.1/tests/keys/testkey_ec_ssh.pub
  36. 5 0
      desktop/core/ext-py/PyJWT-1.7.1/tests/keys/testkey_pkcs1.pub.pem
  37. 27 0
      desktop/core/ext-py/PyJWT-1.7.1/tests/keys/testkey_rsa
  38. 21 0
      desktop/core/ext-py/PyJWT-1.7.1/tests/keys/testkey_rsa.cer
  39. 1 0
      desktop/core/ext-py/PyJWT-1.7.1/tests/keys/testkey_rsa.pub
  40. 582 0
      desktop/core/ext-py/PyJWT-1.7.1/tests/test_algorithms.py
  41. 681 0
      desktop/core/ext-py/PyJWT-1.7.1/tests/test_api_jws.py
  42. 522 0
      desktop/core/ext-py/PyJWT-1.7.1/tests/test_api_jwt.py
  43. 158 0
      desktop/core/ext-py/PyJWT-1.7.1/tests/test_cli.py
  44. 19 0
      desktop/core/ext-py/PyJWT-1.7.1/tests/test_compat.py
  45. 7 0
      desktop/core/ext-py/PyJWT-1.7.1/tests/test_exceptions.py
  46. 23 0
      desktop/core/ext-py/PyJWT-1.7.1/tests/test_jwt.py
  47. 40 0
      desktop/core/ext-py/PyJWT-1.7.1/tests/test_utils.py
  48. 34 0
      desktop/core/ext-py/PyJWT-1.7.1/tests/utils.py
  49. 18 0
      desktop/core/ext-py/PyJWT-1.7.1/tox.ini

+ 29 - 0
desktop/core/ext-py/PyJWT-1.7.1/AUTHORS

@@ -0,0 +1,29 @@
+PyJWT lead developer
+---------------------
+
+ - jpadilla <hello@jpadilla.com>
+
+
+Original author
+------------------
+
+- progrium <progrium@gmail.com>
+
+
+Patches and Suggestions
+-----------------------
+
+ - Boris Feld <boris.feld@novapost.fr> <lothiraldan@gmail.com>
+
+ - Åsmund Ødegård <asmund@xal.no> <ao@mcash.no>
+   Adding support for RSA-SHA256 privat/public signature.
+
+ - Mark Adams <mark@markadams.me>
+
+ - Wouter Bolsterlee <uws@xs4all.nl>
+
+ - Michael Davis <mike.philip.davis@gmail.com> <mike.davis@workiva.com>
+
+ - Vinod Gupta <codervinod@gmail.com>
+ 
+ - Derek Weitzel <djw8605@gmail.com>

+ 292 - 0
desktop/core/ext-py/PyJWT-1.7.1/CHANGELOG.md

@@ -0,0 +1,292 @@
+Change Log
+=========================================================================
+
+All notable changes to this project will be documented in this file.
+This project adheres to [Semantic Versioning](http://semver.org/).
+
+[Unreleased][unreleased]
+-------------------------------------------------------------------------
+### Changed
+
+### Fixed
+
+### Added
+
+[v1.7.1][1.7.1]
+-------------------------------------------------------------------------
+
+### Fixed
+
+- Update test dependencies with pinned ranges
+- Fix pytest deprecation warnings
+
+[v1.7.0][1.7.0]
+-------------------------------------------------------------------------
+
+### Changed
+
+- Remove CRLF line endings [#353][353]
+
+### Fixed
+
+- Update usage.rst [#360][360]
+
+### Added
+
+- Support for Python 3.7 [#375][375] [#379][379] [#384][384]
+
+[v1.6.4][1.6.4]
+-------------------------------------------------------------------------
+### Fixed
+
+- Reverse an unintentional breaking API change to .decode() [#352][352]
+
+[v1.6.3][1.6.3]
+-------------------------------------------------------------------------
+### Changed
+
+- All exceptions inherit from PyJWTError [#340][340]
+
+### Added
+
+- Add type hints [#344][344]
+- Add help module [7ca41e][7ca41e]
+
+### Docs
+
+- Added section to usage docs for jwt.get_unverified_header() [#350][350]
+- Update legacy instructions for using pycrypto [#337][337]
+
+[v1.6.1][1.6.1]
+-------------------------------------------------------------------------
+### Fixed
+
+- Audience parameter throws `InvalidAudienceError` when application does not specify an audience, but the token does. [#336][336]
+
+[v1.6.0][1.6.0]
+-------------------------------------------------------------------------
+### Changed
+
+- Dropped support for python 2.6 and 3.3 [#301][301]
+- An invalid signature now raises an `InvalidSignatureError` instead of `DecodeError` [#316][316]
+
+### Fixed
+
+- Fix over-eager fallback to stdin [#304][304]
+
+### Added
+
+- Audience parameter now supports iterables [#306][306]
+
+[v1.5.3][1.5.3]
+-------------------------------------------------------------------------
+### Changed
+
+- Increase required version of the cryptography package to >=1.4.0.
+
+### Fixed
+
+- Remove uses of deprecated functions from the cryptography package.
+- Warn about missing `algorithms` param to `decode()` only when `verify` param is `True` [#281][281]
+
+[v1.5.2][1.5.2]
+-------------------------------------------------------------------------
+### Fixed
+
+- Ensure correct arguments order in decode super call [7c1e61d][7c1e61d]
+
+[v1.5.1][1.5.1]
+-------------------------------------------------------------------------
+### Changed
+
+- Change optparse for argparse. [#238][238]
+
+### Fixed
+
+- Guard against PKCS1 PEM encododed public keys [#277][277]
+- Add deprecation warning when decoding without specifying `algorithms` [#277][277]
+- Improve deprecation messages [#270][270]
+- PyJWT.decode: move verify param into options [#271][271]
+
+### Added
+
+- Support for Python 3.6 [#262][262]
+- Expose jwt.InvalidAlgorithmError [#264][264]
+
+[v1.5.0][1.5.0]
+-------------------------------------------------------------------------
+### Changed
+- Add support for ECDSA public keys in RFC 4253 (OpenSSH) format [#244][244]
+- Renamed commandline script `jwt` to `jwt-cli` to avoid issues with the script clobbering the `jwt` module in some circumstances. [#187][187]
+- Better error messages when using an algorithm that requires the cryptography package, but it isn't available [#230][230]
+- Tokens with future 'iat' values are no longer rejected [#190][190]
+- Non-numeric 'iat' values now raise InvalidIssuedAtError instead of DecodeError
+- Remove rejection of future 'iat' claims [#252][252]
+
+### Fixed
+- Add back 'ES512' for backward compatibility (for now) [#225][225]
+- Fix incorrectly named ECDSA algorithm [#219][219]
+- Fix rpm build [#196][196]
+
+### Added
+- Add JWK support for HMAC and RSA keys [#202][202]
+
+[v1.4.2][1.4.2]
+-------------------------------------------------------------------------
+### Fixed
+- A PEM-formatted key encoded as bytes could cause a `TypeError` to be raised [#213][213]
+
+[v1.4.1][1.4.1]
+-------------------------------------------------------------------------
+### Fixed
+- Newer versions of Pytest could not detect warnings properly [#182][182]
+- Non-string 'kid' value now raises `InvalidTokenError` [#174][174]
+- `jwt.decode(None)` now gracefully fails with `InvalidTokenError` [#183][183]
+
+[v1.4][1.4.0]
+-------------------------------------------------------------------------
+### Fixed
+- Exclude Python cache files from PyPI releases.
+
+### Added
+- Added new options to require certain claims
+  (require_nbf, require_iat, require_exp) and raise `MissingRequiredClaimError`
+  if they are not present.
+- If `audience=` or `issuer=` is specified but the claim is not present,
+  `MissingRequiredClaimError` is now raised instead of `InvalidAudienceError`
+  and `InvalidIssuerError`
+
+[v1.3][1.3.0]
+-------------------------------------------------------------------------
+### Fixed
+- ECDSA (ES256, ES384, ES512) signatures are now being properly serialized [#158][158]
+- RSA-PSS (PS256, PS384, PS512) signatures now use the proper salt length for PSS padding. [#163][163]
+
+### Added
+- Added a new `jwt.get_unverified_header()` to parse and return the header portion of a token prior to signature verification.
+
+### Removed
+- Python 3.2 is no longer a supported platform. This version of Python is
+rarely used. Users affected by this should upgrade to 3.3+.
+
+[v1.2.0][1.2.0]
+-------------------------------------------------------------------------
+### Fixed
+- Added back `verify_expiration=` argument to `jwt.decode()` that was erroneously removed in [v1.1.0][1.1.0].
+
+
+### Changed
+- Refactored JWS-specific logic out of PyJWT and into PyJWS superclass. [#141][141]
+
+### Deprecated
+- `verify_expiration=` argument to `jwt.decode()` is now deprecated and will be removed in a future version. Use the `option=` argument instead.
+
+[v1.1.0][1.1.0]
+-------------------------------------------------------------------------
+### Added
+- Added support for PS256, PS384, and PS512 algorithms. [#132][132]
+- Added flexible and complete verification options during decode. [#131][131]
+- Added this CHANGELOG.md file.
+
+
+### Deprecated
+- Deprecated usage of the .decode(..., verify=False) parameter.
+
+
+### Fixed
+- Fixed command line encoding. [#128][128]
+
+[v1.0.1][1.0.1]
+-------------------------------------------------------------------------
+### Fixed
+- Include jwt/contrib' and jwt/contrib/algorithms` in setup.py so that they will
+  actually be included when installing. [882524d][882524d]
+- Fix bin/jwt after removing jwt.header(). [bd57b02][bd57b02]
+
+[v1.0.0][1.0.0]
+-------------------------------------------------------------------------
+### Changed
+- Moved `jwt.api.header` out of the public API. [#85][85]
+- Added README details how to extract public / private keys from an x509 certificate. [#100][100]
+- Refactor api.py functions into an object (`PyJWT`). [#101][101]
+- Added support for PyCrypto and ecdsa when cryptography isn't available. [#101][103]
+
+### Fixed
+- Fixed a security vulnerability where `alg=None` header could bypass signature verification. [#109][109]
+- Fixed a security vulnerability by adding support for a whitelist of allowed `alg` values `jwt.decode(algorithms=[])`. [#110][110]
+
+
+[unreleased]: https://github.com/jpadilla/pyjwt/compare/1.4.2...HEAD
+[1.0.0]: https://github.com/jpadilla/pyjwt/compare/0.4.3...1.0.0
+[1.0.1]: https://github.com/jpadilla/pyjwt/compare/1.0.0...1.0.1
+[1.0.1]: https://github.com/jpadilla/pyjwt/compare/1.0.0...1.0.1
+[1.0.1]: https://github.com/jpadilla/pyjwt/compare/1.0.0...1.0.1
+[1.1.0]: https://github.com/jpadilla/pyjwt/compare/1.0.1...1.1.0
+[1.2.0]: https://github.com/jpadilla/pyjwt/compare/1.1.0...1.2.0
+[1.3.0]: https://github.com/jpadilla/pyjwt/compare/1.2.0...1.3.0
+[1.4.0]: https://github.com/jpadilla/pyjwt/compare/1.3.0...1.4.0
+[1.4.1]: https://github.com/jpadilla/pyjwt/compare/1.4.0...1.4.1
+[1.4.2]: https://github.com/jpadilla/pyjwt/compare/1.4.1...1.4.2
+[1.5.0]: https://github.com/jpadilla/pyjwt/compare/1.4.2...1.5.0
+[1.5.1]: https://github.com/jpadilla/pyjwt/compare/1.5.0...1.5.1
+[1.5.2]: https://github.com/jpadilla/pyjwt/compare/1.5.1...1.5.2
+[1.5.3]: https://github.com/jpadilla/pyjwt/compare/1.5.2...1.5.3
+[1.6.0]: https://github.com/jpadilla/pyjwt/compare/1.5.3...1.6.0
+[1.6.1]: https://github.com/jpadilla/pyjwt/compare/1.6.0...1.6.1
+[1.6.3]: https://github.com/jpadilla/pyjwt/compare/1.6.1...1.6.3
+[1.6.4]: https://github.com/jpadilla/pyjwt/compare/1.6.3...1.6.4
+[1.7.0]: https://github.com/jpadilla/pyjwt/compare/1.6.4...1.7.0
+[1.7.1]: https://github.com/jpadilla/pyjwt/compare/1.7.0...1.7.1
+
+[109]: https://github.com/jpadilla/pyjwt/pull/109
+[110]: https://github.com/jpadilla/pyjwt/pull/110
+[100]: https://github.com/jpadilla/pyjwt/pull/100
+[101]: https://github.com/jpadilla/pyjwt/pull/101
+[103]: https://github.com/jpadilla/pyjwt/pull/103
+[85]: https://github.com/jpadilla/pyjwt/pull/85
+[882524d]: https://github.com/jpadilla/pyjwt/commit/882524d
+[bd57b02]: https://github.com/jpadilla/pyjwt/commit/bd57b02
+[131]: https://github.com/jpadilla/pyjwt/pull/131
+[132]: https://github.com/jpadilla/pyjwt/pull/132
+[128]: https://github.com/jpadilla/pyjwt/pull/128
+[141]: https://github.com/jpadilla/pyjwt/pull/141
+[158]: https://github.com/jpadilla/pyjwt/pull/158
+[163]: https://github.com/jpadilla/pyjwt/pull/163
+[174]: https://github.com/jpadilla/pyjwt/pull/174
+[182]: https://github.com/jpadilla/pyjwt/pull/182
+[183]: https://github.com/jpadilla/pyjwt/pull/183
+[190]: https://github.com/jpadilla/pyjwt/pull/190
+[213]: https://github.com/jpadilla/pyjwt/pull/214
+[244]: https://github.com/jpadilla/pyjwt/pull/244
+[202]: https://github.com/jpadilla/pyjwt/pull/202
+[252]: https://github.com/jpadilla/pyjwt/pull/252
+[225]: https://github.com/jpadilla/pyjwt/pull/225
+[219]: https://github.com/jpadilla/pyjwt/pull/219
+[196]: https://github.com/jpadilla/pyjwt/pull/196
+[187]: https://github.com/jpadilla/pyjwt/pull/187
+[230]: https://github.com/jpadilla/pyjwt/pull/230
+[238]: https://github.com/jpadilla/pyjwt/pull/238
+[262]: https://github.com/jpadilla/pyjwt/pull/262
+[264]: https://github.com/jpadilla/pyjwt/pull/264
+[270]: https://github.com/jpadilla/pyjwt/pull/270
+[271]: https://github.com/jpadilla/pyjwt/pull/271
+[277]: https://github.com/jpadilla/pyjwt/pull/277
+[281]: https://github.com/jpadilla/pyjwt/pull/281
+[301]: https://github.com/jpadilla/pyjwt/pull/301
+[304]: https://github.com/jpadilla/pyjwt/pull/304
+[306]: https://github.com/jpadilla/pyjwt/pull/306
+[315]: https://github.com/jpadilla/pyjwt/pull/315
+[316]: https://github.com/jpadilla/pyjwt/pull/316
+[336]: https://github.com/jpadilla/pyjwt/pull/336
+[337]: https://github.com/jpadilla/pyjwt/pull/337
+[340]: https://github.com/jpadilla/pyjwt/pull/340
+[344]: https://github.com/jpadilla/pyjwt/pull/344
+[350]: https://github.com/jpadilla/pyjwt/pull/350
+[352]: https://github.com/jpadilla/pyjwt/pull/352
+[353]: https://github.com/jpadilla/pyjwt/pull/353
+[360]: https://github.com/jpadilla/pyjwt/pull/360
+[375]: https://github.com/jpadilla/pyjwt/pull/375
+[379]: https://github.com/jpadilla/pyjwt/pull/379
+[384]: https://github.com/jpadilla/pyjwt/pull/384
+[7c1e61d]: https://github.com/jpadilla/pyjwt/commit/7c1e61dde27bafe16e7d1bb6e35199e778962742
+[7ca41e]: https://github.com/jpadilla/pyjwt/commit/7ca41e53b3d7d9f5cd31bdd8a2b832d192006239

+ 21 - 0
desktop/core/ext-py/PyJWT-1.7.1/LICENSE

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 José Padilla
+
+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.

+ 8 - 0
desktop/core/ext-py/PyJWT-1.7.1/MANIFEST.in

@@ -0,0 +1,8 @@
+include README.rst
+include CHANGELOG.md
+include LICENSE
+include AUTHORS
+include tox.ini
+recursive-exclude * __pycache__
+recursive-exclude * *.py[co]
+graft tests

+ 106 - 0
desktop/core/ext-py/PyJWT-1.7.1/PKG-INFO

@@ -0,0 +1,106 @@
+Metadata-Version: 2.1
+Name: PyJWT
+Version: 1.7.1
+Summary: JSON Web Token implementation in Python
+Home-page: http://github.com/jpadilla/pyjwt
+Author: Jose Padilla
+Author-email: hello@jpadilla.com
+License: MIT
+Description: PyJWT
+        =====
+        
+        .. image:: https://travis-ci.com/jpadilla/pyjwt.svg?branch=master
+           :target: http://travis-ci.com/jpadilla/pyjwt?branch=master
+        
+        .. image:: https://ci.appveyor.com/api/projects/status/h8nt70aqtwhht39t?svg=true
+           :target: https://ci.appveyor.com/project/jpadilla/pyjwt
+        
+        .. image:: https://img.shields.io/pypi/v/pyjwt.svg
+           :target: https://pypi.python.org/pypi/pyjwt
+        
+        .. image:: https://coveralls.io/repos/jpadilla/pyjwt/badge.svg?branch=master
+           :target: https://coveralls.io/r/jpadilla/pyjwt?branch=master
+        
+        .. image:: https://readthedocs.org/projects/pyjwt/badge/?version=latest
+           :target: https://pyjwt.readthedocs.io
+        
+        A Python implementation of `RFC 7519 <https://tools.ietf.org/html/rfc7519>`_. Original implementation was written by `@progrium <https://github.com/progrium>`_.
+        
+        Sponsor
+        -------
+        
+        +--------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+        | |auth0-logo| | If you want to quickly add secure token-based authentication to Python projects, feel free to check Auth0's Python SDK and free plan at `auth0.com/overview <https://auth0.com/overview?utm_source=GHsponsor&utm_medium=GHsponsor&utm_campaign=pyjwt&utm_content=auth>`_. |
+        +--------------+-----------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+        
+        .. |auth0-logo| image:: https://user-images.githubusercontent.com/83319/31722733-de95bbde-b3ea-11e7-96bf-4f4e8f915588.png
+        
+        Installing
+        ----------
+        
+        Install with **pip**:
+        
+        .. code-block:: sh
+        
+            $ pip install PyJWT
+        
+        
+        Usage
+        -----
+        
+        .. code:: python
+        
+            >>> import jwt
+            >>> encoded = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256')
+            'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg'
+        
+            >>> jwt.decode(encoded, 'secret', algorithms=['HS256'])
+            {'some': 'payload'}
+        
+        
+        Command line
+        ------------
+        
+        Usage::
+        
+            pyjwt [options] INPUT
+        
+        Decoding examples::
+        
+            pyjwt --key=secret decode TOKEN
+            pyjwt decode --no-verify TOKEN
+        
+        See more options executing ``pyjwt --help``.
+        
+        
+        Documentation
+        -------------
+        
+        View the full docs online at https://pyjwt.readthedocs.io/en/latest/
+        
+        
+        Tests
+        -----
+        
+        You can run tests from the project root after cloning with:
+        
+        .. code-block:: sh
+        
+            $ python setup.py test
+        
+Keywords: jwt json web token security signing
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Natural Language :: English
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Topic :: Utilities
+Provides-Extra: crypto
+Provides-Extra: flake8
+Provides-Extra: test

+ 81 - 0
desktop/core/ext-py/PyJWT-1.7.1/README.rst

@@ -0,0 +1,81 @@
+PyJWT
+=====
+
+.. image:: https://travis-ci.com/jpadilla/pyjwt.svg?branch=master
+   :target: http://travis-ci.com/jpadilla/pyjwt?branch=master
+
+.. image:: https://ci.appveyor.com/api/projects/status/h8nt70aqtwhht39t?svg=true
+   :target: https://ci.appveyor.com/project/jpadilla/pyjwt
+
+.. image:: https://img.shields.io/pypi/v/pyjwt.svg
+   :target: https://pypi.python.org/pypi/pyjwt
+
+.. image:: https://coveralls.io/repos/jpadilla/pyjwt/badge.svg?branch=master
+   :target: https://coveralls.io/r/jpadilla/pyjwt?branch=master
+
+.. image:: https://readthedocs.org/projects/pyjwt/badge/?version=latest
+   :target: https://pyjwt.readthedocs.io
+
+A Python implementation of `RFC 7519 <https://tools.ietf.org/html/rfc7519>`_. Original implementation was written by `@progrium <https://github.com/progrium>`_.
+
+Sponsor
+-------
+
++--------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+| |auth0-logo| | If you want to quickly add secure token-based authentication to Python projects, feel free to check Auth0's Python SDK and free plan at `auth0.com/overview <https://auth0.com/overview?utm_source=GHsponsor&utm_medium=GHsponsor&utm_campaign=pyjwt&utm_content=auth>`_. |
++--------------+-----------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+
+.. |auth0-logo| image:: https://user-images.githubusercontent.com/83319/31722733-de95bbde-b3ea-11e7-96bf-4f4e8f915588.png
+
+Installing
+----------
+
+Install with **pip**:
+
+.. code-block:: sh
+
+    $ pip install PyJWT
+
+
+Usage
+-----
+
+.. code:: python
+
+    >>> import jwt
+    >>> encoded = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256')
+    'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg'
+
+    >>> jwt.decode(encoded, 'secret', algorithms=['HS256'])
+    {'some': 'payload'}
+
+
+Command line
+------------
+
+Usage::
+
+    pyjwt [options] INPUT
+
+Decoding examples::
+
+    pyjwt --key=secret decode TOKEN
+    pyjwt decode --no-verify TOKEN
+
+See more options executing ``pyjwt --help``.
+
+
+Documentation
+-------------
+
+View the full docs online at https://pyjwt.readthedocs.io/en/latest/
+
+
+Tests
+-----
+
+You can run tests from the project root after cloning with:
+
+.. code-block:: sh
+
+    $ python setup.py test

+ 31 - 0
desktop/core/ext-py/PyJWT-1.7.1/jwt/__init__.py

@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+# flake8: noqa
+
+"""
+JSON Web Token implementation
+
+Minimum implementation based on this spec:
+http://self-issued.info/docs/draft-jones-json-web-token-01.html
+"""
+
+
+__title__ = 'pyjwt'
+__version__ = '1.7.1'
+__author__ = 'José Padilla'
+__license__ = 'MIT'
+__copyright__ = 'Copyright 2015-2018 José Padilla'
+
+
+from .api_jwt import (
+    encode, decode, register_algorithm, unregister_algorithm,
+    get_unverified_header, PyJWT
+)
+from .api_jws import PyJWS
+from .exceptions import (
+    InvalidTokenError, DecodeError, InvalidAlgorithmError,
+    InvalidAudienceError, ExpiredSignatureError, ImmatureSignatureError,
+    InvalidIssuedAtError, InvalidIssuerError, ExpiredSignature,
+    InvalidAudience, InvalidIssuer, MissingRequiredClaimError,
+    InvalidSignatureError,
+    PyJWTError,
+)

+ 168 - 0
desktop/core/ext-py/PyJWT-1.7.1/jwt/__main__.py

@@ -0,0 +1,168 @@
+#!/usr/bin/env python
+
+from __future__ import absolute_import, print_function
+
+import argparse
+import json
+import sys
+import time
+
+from . import DecodeError, __version__, decode, encode
+
+
+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 args.token:
+            token = args.token
+        else:
+            if sys.stdin.isatty():
+                token = sys.stdin.readline().strip()
+            else:
+                raise IOError('Cannot read from stdin: terminal not a TTY')
+
+        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',
+        usage=usage
+    )
+
+    arg_parser.add_argument(
+        '-v', '--version',
+        action='version',
+        version='%(prog)s ' + __version__
+    )
+
+    arg_parser.add_argument(
+        '--key',
+        dest='key',
+        metavar='KEY',
+        default=None,
+        help='set the secret key to sign with'
+    )
+
+    arg_parser.add_argument(
+        '--alg',
+        dest='algorithm',
+        metavar='ALG',
+        default='HS256',
+        help='set crypto algorithm to sign with. default=HS256'
+    )
+
+    subparsers = arg_parser.add_subparsers(
+        title='PyJWT subcommands',
+        description='valid subcommands',
+        help='additional help'
+    )
+
+    # Encode subcommand
+    encode_parser = subparsers.add_parser('encode', help='use to encode a supplied payload')
+
+    payload_help = """Payload to encode. Must be a space separated list of key/value
+    pairs separated by equals (=) sign."""
+
+    encode_parser.add_argument('payload', nargs='+', help=payload_help)
+    encode_parser.set_defaults(func=encode_payload)
+
+    # 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.',
+        nargs='?')
+
+    decode_parser.add_argument(
+        '-n', '--no-verify',
+        action='store_false',
+        dest='verify',
+        default=True,
+        help='ignore signature and claims verification on decode'
+    )
+
+    decode_parser.set_defaults(func=decode_payload)
+
+    return arg_parser
+
+
+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()

+ 403 - 0
desktop/core/ext-py/PyJWT-1.7.1/jwt/algorithms.py

@@ -0,0 +1,403 @@
+import hashlib
+import hmac
+import json
+
+
+from .compat import constant_time_compare, string_types
+from .exceptions import InvalidKeyError
+from .utils import (
+    base64url_decode, base64url_encode, der_to_raw_signature,
+    force_bytes, force_unicode, from_base64url_uint, raw_to_der_signature,
+    to_base64url_uint
+)
+
+try:
+    from cryptography.hazmat.primitives import hashes
+    from cryptography.hazmat.primitives.serialization import (
+        load_pem_private_key, load_pem_public_key, load_ssh_public_key
+    )
+    from cryptography.hazmat.primitives.asymmetric.rsa import (
+        RSAPrivateKey, RSAPublicKey, RSAPrivateNumbers, RSAPublicNumbers,
+        rsa_recover_prime_factors, rsa_crt_dmp1, rsa_crt_dmq1, rsa_crt_iqmp
+    )
+    from cryptography.hazmat.primitives.asymmetric.ec import (
+        EllipticCurvePrivateKey, EllipticCurvePublicKey
+    )
+    from cryptography.hazmat.primitives.asymmetric import ec, padding
+    from cryptography.hazmat.backends import default_backend
+    from cryptography.exceptions import InvalidSignature
+
+    has_crypto = True
+except ImportError:
+    has_crypto = False
+
+requires_cryptography = set(['RS256', 'RS384', 'RS512', 'ES256', 'ES384',
+                             'ES521', 'ES512', 'PS256', 'PS384', 'PS512'])
+
+
+def get_default_algorithms():
+    """
+    Returns the algorithms that are implemented by the library.
+    """
+    default_algorithms = {
+        'none': NoneAlgorithm(),
+        'HS256': HMACAlgorithm(HMACAlgorithm.SHA256),
+        'HS384': HMACAlgorithm(HMACAlgorithm.SHA384),
+        'HS512': HMACAlgorithm(HMACAlgorithm.SHA512)
+    }
+
+    if has_crypto:
+        default_algorithms.update({
+            'RS256': RSAAlgorithm(RSAAlgorithm.SHA256),
+            'RS384': RSAAlgorithm(RSAAlgorithm.SHA384),
+            'RS512': RSAAlgorithm(RSAAlgorithm.SHA512),
+            'ES256': ECAlgorithm(ECAlgorithm.SHA256),
+            'ES384': ECAlgorithm(ECAlgorithm.SHA384),
+            'ES521': ECAlgorithm(ECAlgorithm.SHA512),
+            'ES512': ECAlgorithm(ECAlgorithm.SHA512),  # Backward compat for #219 fix
+            'PS256': RSAPSSAlgorithm(RSAPSSAlgorithm.SHA256),
+            'PS384': RSAPSSAlgorithm(RSAPSSAlgorithm.SHA384),
+            'PS512': RSAPSSAlgorithm(RSAPSSAlgorithm.SHA512)
+        })
+
+    return default_algorithms
+
+
+class Algorithm(object):
+    """
+    The interface for an algorithm used to sign and verify tokens.
+    """
+    def prepare_key(self, key):
+        """
+        Performs necessary validation and conversions on the key and returns
+        the key value in the proper format for sign() and verify().
+        """
+        raise NotImplementedError
+
+    def sign(self, msg, key):
+        """
+        Returns a digital signature for the specified message
+        using the specified key value.
+        """
+        raise NotImplementedError
+
+    def verify(self, msg, key, sig):
+        """
+        Verifies that the specified digital signature is valid
+        for the specified message and key values.
+        """
+        raise NotImplementedError
+
+    @staticmethod
+    def to_jwk(key_obj):
+        """
+        Serializes a given RSA key into a JWK
+        """
+        raise NotImplementedError
+
+    @staticmethod
+    def from_jwk(jwk):
+        """
+        Deserializes a given RSA key from JWK back into a PublicKey or PrivateKey object
+        """
+        raise NotImplementedError
+
+
+class NoneAlgorithm(Algorithm):
+    """
+    Placeholder for use when no signing or verification
+    operations are required.
+    """
+    def prepare_key(self, key):
+        if key == '':
+            key = None
+
+        if key is not None:
+            raise InvalidKeyError('When alg = "none", key value must be None.')
+
+        return key
+
+    def sign(self, msg, key):
+        return b''
+
+    def verify(self, msg, key, sig):
+        return False
+
+
+class HMACAlgorithm(Algorithm):
+    """
+    Performs signing and verification operations using HMAC
+    and the specified hash function.
+    """
+    SHA256 = hashlib.sha256
+    SHA384 = hashlib.sha384
+    SHA512 = hashlib.sha512
+
+    def __init__(self, hash_alg):
+        self.hash_alg = hash_alg
+
+    def prepare_key(self, key):
+        key = force_bytes(key)
+
+        invalid_strings = [
+            b'-----BEGIN PUBLIC KEY-----',
+            b'-----BEGIN CERTIFICATE-----',
+            b'-----BEGIN RSA PUBLIC KEY-----',
+            b'ssh-rsa'
+        ]
+
+        if any([string_value in key for string_value in invalid_strings]):
+            raise InvalidKeyError(
+                'The specified key is an asymmetric key or x509 certificate and'
+                ' should not be used as an HMAC secret.')
+
+        return key
+
+    @staticmethod
+    def to_jwk(key_obj):
+        return json.dumps({
+            'k': force_unicode(base64url_encode(force_bytes(key_obj))),
+            'kty': 'oct'
+        })
+
+    @staticmethod
+    def from_jwk(jwk):
+        obj = json.loads(jwk)
+
+        if obj.get('kty') != 'oct':
+            raise InvalidKeyError('Not an HMAC key')
+
+        return base64url_decode(obj['k'])
+
+    def sign(self, msg, key):
+        return hmac.new(key, msg, self.hash_alg).digest()
+
+    def verify(self, msg, key, sig):
+        return constant_time_compare(sig, self.sign(msg, key))
+
+
+if has_crypto:
+
+    class RSAAlgorithm(Algorithm):
+        """
+        Performs signing and verification operations using
+        RSASSA-PKCS-v1_5 and the specified hash function.
+        """
+        SHA256 = hashes.SHA256
+        SHA384 = hashes.SHA384
+        SHA512 = hashes.SHA512
+
+        def __init__(self, hash_alg):
+            self.hash_alg = hash_alg
+
+        def prepare_key(self, key):
+            if isinstance(key, RSAPrivateKey) or \
+               isinstance(key, RSAPublicKey):
+                return key
+
+            if isinstance(key, string_types):
+                key = force_bytes(key)
+
+                try:
+                    if key.startswith(b'ssh-rsa'):
+                        key = load_ssh_public_key(key, backend=default_backend())
+                    else:
+                        key = load_pem_private_key(key, password=None, backend=default_backend())
+                except ValueError:
+                    key = load_pem_public_key(key, backend=default_backend())
+            else:
+                raise TypeError('Expecting a PEM-formatted key.')
+
+            return key
+
+        @staticmethod
+        def to_jwk(key_obj):
+            obj = None
+
+            if getattr(key_obj, 'private_numbers', None):
+                # Private key
+                numbers = key_obj.private_numbers()
+
+                obj = {
+                    'kty': 'RSA',
+                    'key_ops': ['sign'],
+                    'n': force_unicode(to_base64url_uint(numbers.public_numbers.n)),
+                    'e': force_unicode(to_base64url_uint(numbers.public_numbers.e)),
+                    'd': force_unicode(to_base64url_uint(numbers.d)),
+                    'p': force_unicode(to_base64url_uint(numbers.p)),
+                    'q': force_unicode(to_base64url_uint(numbers.q)),
+                    'dp': force_unicode(to_base64url_uint(numbers.dmp1)),
+                    'dq': force_unicode(to_base64url_uint(numbers.dmq1)),
+                    'qi': force_unicode(to_base64url_uint(numbers.iqmp))
+                }
+
+            elif getattr(key_obj, 'verify', None):
+                # Public key
+                numbers = key_obj.public_numbers()
+
+                obj = {
+                    'kty': 'RSA',
+                    'key_ops': ['verify'],
+                    'n': force_unicode(to_base64url_uint(numbers.n)),
+                    'e': force_unicode(to_base64url_uint(numbers.e))
+                }
+            else:
+                raise InvalidKeyError('Not a public or private key')
+
+            return json.dumps(obj)
+
+        @staticmethod
+        def from_jwk(jwk):
+            try:
+                obj = json.loads(jwk)
+            except ValueError:
+                raise InvalidKeyError('Key is not valid JSON')
+
+            if obj.get('kty') != 'RSA':
+                raise InvalidKeyError('Not an RSA key')
+
+            if 'd' in obj and 'e' in obj and 'n' in obj:
+                # Private key
+                if 'oth' in obj:
+                    raise InvalidKeyError('Unsupported RSA private key: > 2 primes not supported')
+
+                other_props = ['p', 'q', 'dp', 'dq', 'qi']
+                props_found = [prop in obj for prop in other_props]
+                any_props_found = any(props_found)
+
+                if any_props_found and not all(props_found):
+                    raise InvalidKeyError('RSA key must include all parameters if any are present besides d')
+
+                public_numbers = RSAPublicNumbers(
+                    from_base64url_uint(obj['e']), from_base64url_uint(obj['n'])
+                )
+
+                if any_props_found:
+                    numbers = RSAPrivateNumbers(
+                        d=from_base64url_uint(obj['d']),
+                        p=from_base64url_uint(obj['p']),
+                        q=from_base64url_uint(obj['q']),
+                        dmp1=from_base64url_uint(obj['dp']),
+                        dmq1=from_base64url_uint(obj['dq']),
+                        iqmp=from_base64url_uint(obj['qi']),
+                        public_numbers=public_numbers
+                    )
+                else:
+                    d = from_base64url_uint(obj['d'])
+                    p, q = rsa_recover_prime_factors(
+                        public_numbers.n, d, public_numbers.e
+                    )
+
+                    numbers = RSAPrivateNumbers(
+                        d=d,
+                        p=p,
+                        q=q,
+                        dmp1=rsa_crt_dmp1(d, p),
+                        dmq1=rsa_crt_dmq1(d, q),
+                        iqmp=rsa_crt_iqmp(p, q),
+                        public_numbers=public_numbers
+                    )
+
+                return numbers.private_key(default_backend())
+            elif 'n' in obj and 'e' in obj:
+                # Public key
+                numbers = RSAPublicNumbers(
+                    from_base64url_uint(obj['e']), from_base64url_uint(obj['n'])
+                )
+
+                return numbers.public_key(default_backend())
+            else:
+                raise InvalidKeyError('Not a public or private key')
+
+        def sign(self, msg, key):
+            return key.sign(msg, padding.PKCS1v15(), self.hash_alg())
+
+        def verify(self, msg, key, sig):
+            try:
+                key.verify(sig, msg, padding.PKCS1v15(), self.hash_alg())
+                return True
+            except InvalidSignature:
+                return False
+
+    class ECAlgorithm(Algorithm):
+        """
+        Performs signing and verification operations using
+        ECDSA and the specified hash function
+        """
+        SHA256 = hashes.SHA256
+        SHA384 = hashes.SHA384
+        SHA512 = hashes.SHA512
+
+        def __init__(self, hash_alg):
+            self.hash_alg = hash_alg
+
+        def prepare_key(self, key):
+            if isinstance(key, EllipticCurvePrivateKey) or \
+               isinstance(key, EllipticCurvePublicKey):
+                return key
+
+            if isinstance(key, string_types):
+                key = force_bytes(key)
+
+                # Attempt to load key. We don't know if it's
+                # a Signing Key or a Verifying Key, so we try
+                # the Verifying Key first.
+                try:
+                    if key.startswith(b'ecdsa-sha2-'):
+                        key = load_ssh_public_key(key, backend=default_backend())
+                    else:
+                        key = load_pem_public_key(key, backend=default_backend())
+                except ValueError:
+                    key = load_pem_private_key(key, password=None, backend=default_backend())
+
+            else:
+                raise TypeError('Expecting a PEM-formatted key.')
+
+            return key
+
+        def sign(self, msg, key):
+            der_sig = key.sign(msg, ec.ECDSA(self.hash_alg()))
+
+            return der_to_raw_signature(der_sig, key.curve)
+
+        def verify(self, msg, key, sig):
+            try:
+                der_sig = raw_to_der_signature(sig, key.curve)
+            except ValueError:
+                return False
+
+            try:
+                key.verify(der_sig, msg, ec.ECDSA(self.hash_alg()))
+                return True
+            except InvalidSignature:
+                return False
+
+    class RSAPSSAlgorithm(RSAAlgorithm):
+        """
+        Performs a signature using RSASSA-PSS with MGF1
+        """
+
+        def sign(self, msg, key):
+            return key.sign(
+                msg,
+                padding.PSS(
+                    mgf=padding.MGF1(self.hash_alg()),
+                    salt_length=self.hash_alg.digest_size
+                ),
+                self.hash_alg()
+            )
+
+        def verify(self, msg, key, sig):
+            try:
+                key.verify(
+                    sig,
+                    msg,
+                    padding.PSS(
+                        mgf=padding.MGF1(self.hash_alg()),
+                        salt_length=self.hash_alg.digest_size
+                    ),
+                    self.hash_alg()
+                )
+                return True
+            except InvalidSignature:
+                return False

+ 242 - 0
desktop/core/ext-py/PyJWT-1.7.1/jwt/api_jws.py

@@ -0,0 +1,242 @@
+import binascii
+import json
+import warnings
+try:
+    # import required by mypy to perform type checking, not used for normal execution
+    from typing import Callable, Dict, List, Optional, Union # NOQA
+except ImportError:
+    pass
+
+from .algorithms import (
+    Algorithm, get_default_algorithms, has_crypto, requires_cryptography  # NOQA
+)
+from .compat import Mapping, binary_type, string_types, text_type
+from .exceptions import (
+    DecodeError, InvalidAlgorithmError, InvalidSignatureError,
+    InvalidTokenError
+)
+from .utils import base64url_decode, base64url_encode, force_bytes, merge_dict
+
+
+class PyJWS(object):
+    header_typ = 'JWT'
+
+    def __init__(self, algorithms=None, options=None):
+        self._algorithms = get_default_algorithms()
+        self._valid_algs = (set(algorithms) if algorithms is not None
+                            else set(self._algorithms))
+
+        # Remove algorithms that aren't on the whitelist
+        for key in list(self._algorithms.keys()):
+            if key not in self._valid_algs:
+                del self._algorithms[key]
+
+        if not options:
+            options = {}
+
+        self.options = merge_dict(self._get_default_options(), options)
+
+    @staticmethod
+    def _get_default_options():
+        return {
+            'verify_signature': True
+        }
+
+    def register_algorithm(self, alg_id, alg_obj):
+        """
+        Registers a new Algorithm for use when creating and verifying tokens.
+        """
+        if alg_id in self._algorithms:
+            raise ValueError('Algorithm already has a handler.')
+
+        if not isinstance(alg_obj, Algorithm):
+            raise TypeError('Object is not of type `Algorithm`')
+
+        self._algorithms[alg_id] = alg_obj
+        self._valid_algs.add(alg_id)
+
+    def unregister_algorithm(self, alg_id):
+        """
+        Unregisters an Algorithm for use when creating and verifying tokens
+        Throws KeyError if algorithm is not registered.
+        """
+        if alg_id not in self._algorithms:
+            raise KeyError('The specified algorithm could not be removed'
+                           ' because it is not registered.')
+
+        del self._algorithms[alg_id]
+        self._valid_algs.remove(alg_id)
+
+    def get_algorithms(self):
+        """
+        Returns a list of supported values for the 'alg' parameter.
+        """
+        return list(self._valid_algs)
+
+    def encode(self,
+               payload,  # type: Union[Dict, bytes]
+               key,  # type: str
+               algorithm='HS256',  # type: str
+               headers=None,  # type: Optional[Dict]
+               json_encoder=None  # type: Optional[Callable]
+               ):
+        segments = []
+
+        if algorithm is None:
+            algorithm = 'none'
+
+        if algorithm not in self._valid_algs:
+            pass
+
+        # Header
+        header = {'typ': self.header_typ, 'alg': algorithm}
+
+        if headers:
+            self._validate_headers(headers)
+            header.update(headers)
+
+        json_header = force_bytes(
+            json.dumps(
+                header,
+                separators=(',', ':'),
+                cls=json_encoder
+            )
+        )
+
+        segments.append(base64url_encode(json_header))
+        segments.append(base64url_encode(payload))
+
+        # Segments
+        signing_input = b'.'.join(segments)
+        try:
+            alg_obj = self._algorithms[algorithm]
+            key = alg_obj.prepare_key(key)
+            signature = alg_obj.sign(signing_input, key)
+
+        except KeyError:
+            if not has_crypto and algorithm in requires_cryptography:
+                raise NotImplementedError(
+                    "Algorithm '%s' could not be found. Do you have cryptography "
+                    "installed?" % algorithm
+                )
+            else:
+                raise NotImplementedError('Algorithm not supported')
+
+        segments.append(base64url_encode(signature))
+
+        return b'.'.join(segments)
+
+    def decode(self,
+               jwt,  # type: str
+               key='',   # type: str
+               verify=True,  # type: bool
+               algorithms=None,  # type: List[str]
+               options=None,  # type: Dict
+               **kwargs):
+
+        merged_options = merge_dict(self.options, options)
+        verify_signature = merged_options['verify_signature']
+
+        if verify_signature and not algorithms:
+            warnings.warn(
+                'It is strongly recommended that you pass in a ' +
+                'value for the "algorithms" argument when calling decode(). ' +
+                'This argument will be mandatory in a future version.',
+                DeprecationWarning
+            )
+
+        payload, signing_input, header, signature = self._load(jwt)
+
+        if not verify:
+            warnings.warn('The verify parameter is deprecated. '
+                          'Please use verify_signature in options instead.',
+                          DeprecationWarning, stacklevel=2)
+        elif verify_signature:
+            self._verify_signature(payload, signing_input, header, signature,
+                                   key, algorithms)
+
+        return payload
+
+    def get_unverified_header(self, jwt):
+        """Returns back the JWT header parameters as a dict()
+
+        Note: The signature is not verified so the header parameters
+        should not be fully trusted until signature verification is complete
+        """
+        headers = self._load(jwt)[2]
+        self._validate_headers(headers)
+
+        return headers
+
+    def _load(self, jwt):
+        if isinstance(jwt, text_type):
+            jwt = jwt.encode('utf-8')
+
+        if not issubclass(type(jwt), binary_type):
+            raise DecodeError("Invalid token type. Token must be a {0}".format(
+                binary_type))
+
+        try:
+            signing_input, crypto_segment = jwt.rsplit(b'.', 1)
+            header_segment, payload_segment = signing_input.split(b'.', 1)
+        except ValueError:
+            raise DecodeError('Not enough segments')
+
+        try:
+            header_data = base64url_decode(header_segment)
+        except (TypeError, binascii.Error):
+            raise DecodeError('Invalid header padding')
+
+        try:
+            header = json.loads(header_data.decode('utf-8'))
+        except ValueError as e:
+            raise DecodeError('Invalid header string: %s' % e)
+
+        if not isinstance(header, Mapping):
+            raise DecodeError('Invalid header string: must be a json object')
+
+        try:
+            payload = base64url_decode(payload_segment)
+        except (TypeError, binascii.Error):
+            raise DecodeError('Invalid payload padding')
+
+        try:
+            signature = base64url_decode(crypto_segment)
+        except (TypeError, binascii.Error):
+            raise DecodeError('Invalid crypto padding')
+
+        return (payload, signing_input, header, signature)
+
+    def _verify_signature(self, payload, signing_input, header, signature,
+                          key='', algorithms=None):
+
+        alg = header.get('alg')
+
+        if algorithms is not None and alg not in algorithms:
+            raise InvalidAlgorithmError('The specified alg value is not allowed')
+
+        try:
+            alg_obj = self._algorithms[alg]
+            key = alg_obj.prepare_key(key)
+
+            if not alg_obj.verify(signing_input, key, signature):
+                raise InvalidSignatureError('Signature verification failed')
+
+        except KeyError:
+            raise InvalidAlgorithmError('Algorithm not supported')
+
+    def _validate_headers(self, headers):
+        if 'kid' in headers:
+            self._validate_kid(headers['kid'])
+
+    def _validate_kid(self, kid):
+        if not isinstance(kid, string_types):
+            raise InvalidTokenError('Key ID header parameter must be a string')
+
+
+_jws_global_obj = PyJWS()
+encode = _jws_global_obj.encode
+decode = _jws_global_obj.decode
+register_algorithm = _jws_global_obj.register_algorithm
+unregister_algorithm = _jws_global_obj.unregister_algorithm
+get_unverified_header = _jws_global_obj.get_unverified_header

+ 222 - 0
desktop/core/ext-py/PyJWT-1.7.1/jwt/api_jwt.py

@@ -0,0 +1,222 @@
+import json
+import warnings
+from calendar import timegm
+from datetime import datetime, timedelta
+try:
+    # import required by mypy to perform type checking, not used for normal execution
+    from typing import Callable, Dict, List, Optional, Union # NOQA
+except ImportError:
+    pass
+
+from .api_jws import PyJWS
+from .algorithms import Algorithm, get_default_algorithms  # NOQA
+from .compat import Iterable, Mapping, string_types
+from .exceptions import (
+    DecodeError, ExpiredSignatureError, ImmatureSignatureError,
+    InvalidAudienceError, InvalidIssuedAtError,
+    InvalidIssuerError, MissingRequiredClaimError
+)
+from .utils import merge_dict
+
+
+class PyJWT(PyJWS):
+    header_type = 'JWT'
+
+    @staticmethod
+    def _get_default_options():
+        # type: () -> Dict[str, bool]
+        return {
+            'verify_signature': True,
+            'verify_exp': True,
+            'verify_nbf': True,
+            'verify_iat': True,
+            'verify_aud': True,
+            'verify_iss': True,
+            'require_exp': False,
+            'require_iat': False,
+            'require_nbf': False
+        }
+
+    def encode(self,
+               payload,  # type: Union[Dict, bytes]
+               key,  # type: str
+               algorithm='HS256',  # type: str
+               headers=None,  # type: Optional[Dict]
+               json_encoder=None  # type: Optional[Callable]
+               ):
+        # Check that we get a mapping
+        if not isinstance(payload, Mapping):
+            raise TypeError('Expecting a mapping object, as JWT only supports '
+                            'JSON objects as payloads.')
+
+        # Payload
+        for time_claim in ['exp', 'iat', 'nbf']:
+            # Convert datetime to a intDate value in known time-format claims
+            if isinstance(payload.get(time_claim), datetime):
+                payload[time_claim] = timegm(payload[time_claim].utctimetuple())  # type: ignore
+
+        json_payload = json.dumps(
+            payload,
+            separators=(',', ':'),
+            cls=json_encoder
+        ).encode('utf-8')
+
+        return super(PyJWT, self).encode(
+            json_payload, key, algorithm, headers, json_encoder
+        )
+
+    def decode(self,
+               jwt,  # type: str
+               key='',   # type: str
+               verify=True,  # type: bool
+               algorithms=None,  # type: List[str]
+               options=None,  # type: Dict
+               **kwargs):
+
+        if verify and not algorithms:
+            warnings.warn(
+                'It is strongly recommended that you pass in a ' +
+                'value for the "algorithms" argument when calling decode(). ' +
+                'This argument will be mandatory in a future version.',
+                DeprecationWarning
+            )
+
+        payload, _, _, _ = self._load(jwt)
+
+        if options is None:
+            options = {'verify_signature': verify}
+        else:
+            options.setdefault('verify_signature', verify)
+
+        decoded = super(PyJWT, self).decode(
+            jwt, key=key, algorithms=algorithms, options=options, **kwargs
+        )
+
+        try:
+            payload = json.loads(decoded.decode('utf-8'))
+        except ValueError as e:
+            raise DecodeError('Invalid payload string: %s' % e)
+        if not isinstance(payload, Mapping):
+            raise DecodeError('Invalid payload string: must be a json object')
+
+        if verify:
+            merged_options = merge_dict(self.options, options)
+            self._validate_claims(payload, merged_options, **kwargs)
+
+        return payload
+
+    def _validate_claims(self, payload, options, audience=None, issuer=None,
+                         leeway=0, **kwargs):
+
+        if 'verify_expiration' in kwargs:
+            options['verify_exp'] = kwargs.get('verify_expiration', True)
+            warnings.warn('The verify_expiration parameter is deprecated. '
+                          'Please use verify_exp in options instead.',
+                          DeprecationWarning)
+
+        if isinstance(leeway, timedelta):
+            leeway = leeway.total_seconds()
+
+        if not isinstance(audience, (string_types, type(None), Iterable)):
+            raise TypeError('audience must be a string, iterable, or None')
+
+        self._validate_required_claims(payload, options)
+
+        now = timegm(datetime.utcnow().utctimetuple())
+
+        if 'iat' in payload and options.get('verify_iat'):
+            self._validate_iat(payload, now, leeway)
+
+        if 'nbf' in payload and options.get('verify_nbf'):
+            self._validate_nbf(payload, now, leeway)
+
+        if 'exp' in payload and options.get('verify_exp'):
+            self._validate_exp(payload, now, leeway)
+
+        if options.get('verify_iss'):
+            self._validate_iss(payload, issuer)
+
+        if options.get('verify_aud'):
+            self._validate_aud(payload, audience)
+
+    def _validate_required_claims(self, payload, options):
+        if options.get('require_exp') and payload.get('exp') is None:
+            raise MissingRequiredClaimError('exp')
+
+        if options.get('require_iat') and payload.get('iat') is None:
+            raise MissingRequiredClaimError('iat')
+
+        if options.get('require_nbf') and payload.get('nbf') is None:
+            raise MissingRequiredClaimError('nbf')
+
+    def _validate_iat(self, payload, now, leeway):
+        try:
+            int(payload['iat'])
+        except ValueError:
+            raise InvalidIssuedAtError('Issued At claim (iat) must be an integer.')
+
+    def _validate_nbf(self, payload, now, leeway):
+        try:
+            nbf = int(payload['nbf'])
+        except ValueError:
+            raise DecodeError('Not Before claim (nbf) must be an integer.')
+
+        if nbf > (now + leeway):
+            raise ImmatureSignatureError('The token is not yet valid (nbf)')
+
+    def _validate_exp(self, payload, now, leeway):
+        try:
+            exp = int(payload['exp'])
+        except ValueError:
+            raise DecodeError('Expiration Time claim (exp) must be an'
+                              ' integer.')
+
+        if exp < (now - leeway):
+            raise ExpiredSignatureError('Signature has expired')
+
+    def _validate_aud(self, payload, audience):
+        if audience is None and 'aud' not in payload:
+            return
+
+        if audience is not None and 'aud' not in payload:
+            # Application specified an audience, but it could not be
+            # verified since the token does not contain a claim.
+            raise MissingRequiredClaimError('aud')
+
+        if audience is None and 'aud' in payload:
+            # Application did not specify an audience, but
+            # the token has the 'aud' claim
+            raise InvalidAudienceError('Invalid audience')
+
+        audience_claims = payload['aud']
+
+        if isinstance(audience_claims, string_types):
+            audience_claims = [audience_claims]
+        if not isinstance(audience_claims, list):
+            raise InvalidAudienceError('Invalid claim format in token')
+        if any(not isinstance(c, string_types) for c in audience_claims):
+            raise InvalidAudienceError('Invalid claim format in token')
+
+        if isinstance(audience, string_types):
+            audience = [audience]
+
+        if not any(aud in audience_claims for aud in audience):
+            raise InvalidAudienceError('Invalid audience')
+
+    def _validate_iss(self, payload, issuer):
+        if issuer is None:
+            return
+
+        if 'iss' not in payload:
+            raise MissingRequiredClaimError('iss')
+
+        if payload['iss'] != issuer:
+            raise InvalidIssuerError('Invalid issuer')
+
+
+_jwt_global_obj = PyJWT()
+encode = _jwt_global_obj.encode
+decode = _jwt_global_obj.decode
+register_algorithm = _jwt_global_obj.register_algorithm
+unregister_algorithm = _jwt_global_obj.unregister_algorithm
+get_unverified_header = _jwt_global_obj.get_unverified_header

+ 68 - 0
desktop/core/ext-py/PyJWT-1.7.1/jwt/compat.py

@@ -0,0 +1,68 @@
+"""
+The `compat` module provides support for backwards compatibility with older
+versions of python, and compatibility wrappers around optional packages.
+"""
+# flake8: noqa
+import hmac
+import struct
+import sys
+
+
+PY3 = sys.version_info[0] == 3
+
+
+if PY3:
+    text_type = str
+    binary_type = bytes
+else:
+    text_type = unicode
+    binary_type = str
+
+string_types = (text_type, binary_type)
+
+try:
+    # Importing ABCs from collections will be removed in PY3.8
+    from collections.abc import Iterable, Mapping
+except ImportError:
+    from collections import Iterable, Mapping
+
+try:
+    constant_time_compare = hmac.compare_digest
+except AttributeError:
+    # Fallback for Python < 2.7
+    def constant_time_compare(val1, val2):
+        """
+        Returns True if the two strings are equal, False otherwise.
+
+        The time taken is independent of the number of characters that match.
+        """
+        if len(val1) != len(val2):
+            return False
+
+        result = 0
+
+        for x, y in zip(val1, val2):
+            result |= ord(x) ^ ord(y)
+
+        return result == 0
+
+# Use int.to_bytes if it exists (Python 3)
+if getattr(int, 'to_bytes', None):
+    def bytes_from_int(val):
+        remaining = val
+        byte_length = 0
+
+        while remaining != 0:
+            remaining = remaining >> 8
+            byte_length += 1
+
+        return val.to_bytes(byte_length, 'big', signed=False)
+else:
+    def bytes_from_int(val):
+        buf = []
+        while val:
+            val, remainder = divmod(val, 256)
+            buf.append(remainder)
+
+        buf.reverse()
+        return struct.pack('%sB' % len(buf), *buf)

+ 0 - 0
desktop/core/ext-py/PyJWT-1.7.1/jwt/contrib/__init__.py


+ 0 - 0
desktop/core/ext-py/PyJWT-1.7.1/jwt/contrib/algorithms/__init__.py


+ 60 - 0
desktop/core/ext-py/PyJWT-1.7.1/jwt/contrib/algorithms/py_ecdsa.py

@@ -0,0 +1,60 @@
+# Note: This file is named py_ecdsa.py because import behavior in Python 2
+# would cause ecdsa.py to squash the ecdsa library that it depends upon.
+
+import hashlib
+
+import ecdsa
+
+from jwt.algorithms import Algorithm
+from jwt.compat import string_types, text_type
+
+
+class ECAlgorithm(Algorithm):
+    """
+    Performs signing and verification operations using
+    ECDSA and the specified hash function
+
+    This class requires the ecdsa package to be installed.
+
+    This is based off of the implementation in PyJWT 0.3.2
+    """
+    SHA256 = hashlib.sha256
+    SHA384 = hashlib.sha384
+    SHA512 = hashlib.sha512
+
+    def __init__(self, hash_alg):
+        self.hash_alg = hash_alg
+
+    def prepare_key(self, key):
+
+        if isinstance(key, ecdsa.SigningKey) or \
+           isinstance(key, ecdsa.VerifyingKey):
+            return key
+
+        if isinstance(key, string_types):
+            if isinstance(key, text_type):
+                key = key.encode('utf-8')
+
+            # Attempt to load key. We don't know if it's
+            # a Signing Key or a Verifying Key, so we try
+            # the Verifying Key first.
+            try:
+                key = ecdsa.VerifyingKey.from_pem(key)
+            except ecdsa.der.UnexpectedDER:
+                key = ecdsa.SigningKey.from_pem(key)
+
+        else:
+            raise TypeError('Expecting a PEM-formatted key.')
+
+        return key
+
+    def sign(self, msg, key):
+        return key.sign(msg, hashfunc=self.hash_alg,
+                        sigencode=ecdsa.util.sigencode_string)
+
+    def verify(self, msg, key, sig):
+        try:
+            return key.verify(sig, msg, hashfunc=self.hash_alg,
+                              sigdecode=ecdsa.util.sigdecode_string)
+        except AssertionError:
+            return False

+ 46 - 0
desktop/core/ext-py/PyJWT-1.7.1/jwt/contrib/algorithms/pycrypto.py

@@ -0,0 +1,46 @@
+import Crypto.Hash.SHA256
+import Crypto.Hash.SHA384
+import Crypto.Hash.SHA512
+from Crypto.PublicKey import RSA
+from Crypto.Signature import PKCS1_v1_5
+
+from jwt.algorithms import Algorithm
+from jwt.compat import string_types, text_type
+
+
+class RSAAlgorithm(Algorithm):
+    """
+    Performs signing and verification operations using
+    RSASSA-PKCS-v1_5 and the specified hash function.
+
+    This class requires PyCrypto package to be installed.
+
+    This is based off of the implementation in PyJWT 0.3.2
+    """
+    SHA256 = Crypto.Hash.SHA256
+    SHA384 = Crypto.Hash.SHA384
+    SHA512 = Crypto.Hash.SHA512
+
+    def __init__(self, hash_alg):
+        self.hash_alg = hash_alg
+
+    def prepare_key(self, key):
+
+        if isinstance(key, RSA._RSAobj):
+            return key
+
+        if isinstance(key, string_types):
+            if isinstance(key, text_type):
+                key = key.encode('utf-8')
+
+            key = RSA.importKey(key)
+        else:
+            raise TypeError('Expecting a PEM- or RSA-formatted key.')
+
+        return key
+
+    def sign(self, msg, key):
+        return PKCS1_v1_5.new(key).sign(self.hash_alg.new(msg))
+
+    def verify(self, msg, key, sig):
+        return PKCS1_v1_5.new(key).verify(self.hash_alg.new(msg), sig)

+ 59 - 0
desktop/core/ext-py/PyJWT-1.7.1/jwt/exceptions.py

@@ -0,0 +1,59 @@
+class PyJWTError(Exception):
+    """
+    Base class for all exceptions
+    """
+    pass
+
+
+class InvalidTokenError(PyJWTError):
+    pass
+
+
+class DecodeError(InvalidTokenError):
+    pass
+
+
+class InvalidSignatureError(DecodeError):
+    pass
+
+
+class ExpiredSignatureError(InvalidTokenError):
+    pass
+
+
+class InvalidAudienceError(InvalidTokenError):
+    pass
+
+
+class InvalidIssuerError(InvalidTokenError):
+    pass
+
+
+class InvalidIssuedAtError(InvalidTokenError):
+    pass
+
+
+class ImmatureSignatureError(InvalidTokenError):
+    pass
+
+
+class InvalidKeyError(PyJWTError):
+    pass
+
+
+class InvalidAlgorithmError(InvalidTokenError):
+    pass
+
+
+class MissingRequiredClaimError(InvalidTokenError):
+    def __init__(self, claim):
+        self.claim = claim
+
+    def __str__(self):
+        return 'Token is missing the "%s" claim' % self.claim
+
+
+# Compatibility aliases (deprecated)
+ExpiredSignature = ExpiredSignatureError
+InvalidAudience = InvalidAudienceError
+InvalidIssuer = InvalidIssuerError

+ 61 - 0
desktop/core/ext-py/PyJWT-1.7.1/jwt/help.py

@@ -0,0 +1,61 @@
+from __future__ import print_function
+
+import json
+import platform
+import sys
+
+from . import __version__ as pyjwt_version
+
+try:
+    import cryptography
+except ImportError:
+    cryptography = None
+
+try:
+    import ecdsa
+except ImportError:
+    ecdsa = None
+
+
+def info():
+    """
+    Generate information for a bug report.
+    Based on the requests package help utility module.
+    """
+    try:
+        platform_info = {"system": platform.system(), "release": platform.release()}
+    except IOError:
+        platform_info = {"system": "Unknown", "release": "Unknown"}
+
+    implementation = platform.python_implementation()
+
+    if implementation == "CPython":
+        implementation_version = platform.python_version()
+    elif implementation == "PyPy":
+        implementation_version = "%s.%s.%s" % (
+            sys.pypy_version_info.major,
+            sys.pypy_version_info.minor,
+            sys.pypy_version_info.micro,
+        )
+        if sys.pypy_version_info.releaselevel != "final":
+            implementation_version = "".join(
+                [implementation_version, sys.pypy_version_info.releaselevel]
+            )
+    else:
+        implementation_version = "Unknown"
+
+    return {
+        "platform": platform_info,
+        "implementation": {"name": implementation, "version": implementation_version},
+        "cryptography": {"version": getattr(cryptography, "__version__", "")},
+        "pyjwt": {"version": pyjwt_version},
+    }
+
+
+def main():
+    """Pretty-print the bug information as JSON."""
+    print(json.dumps(info(), sort_keys=True, indent=2))
+
+
+if __name__ == "__main__":
+    main()

+ 113 - 0
desktop/core/ext-py/PyJWT-1.7.1/jwt/utils.py

@@ -0,0 +1,113 @@
+import base64
+import binascii
+import struct
+
+from .compat import binary_type, bytes_from_int, text_type
+
+try:
+    from cryptography.hazmat.primitives.asymmetric.utils import (
+        decode_dss_signature, encode_dss_signature
+    )
+except ImportError:
+    pass
+
+
+def force_unicode(value):
+    if isinstance(value, binary_type):
+        return value.decode('utf-8')
+    elif isinstance(value, text_type):
+        return value
+    else:
+        raise TypeError('Expected a string value')
+
+
+def force_bytes(value):
+    if isinstance(value, text_type):
+        return value.encode('utf-8')
+    elif isinstance(value, binary_type):
+        return value
+    else:
+        raise TypeError('Expected a string value')
+
+
+def base64url_decode(input):
+    if isinstance(input, text_type):
+        input = input.encode('ascii')
+
+    rem = len(input) % 4
+
+    if rem > 0:
+        input += b'=' * (4 - rem)
+
+    return base64.urlsafe_b64decode(input)
+
+
+def base64url_encode(input):
+    return base64.urlsafe_b64encode(input).replace(b'=', b'')
+
+
+def to_base64url_uint(val):
+    if val < 0:
+        raise ValueError('Must be a positive integer')
+
+    int_bytes = bytes_from_int(val)
+
+    if len(int_bytes) == 0:
+        int_bytes = b'\x00'
+
+    return base64url_encode(int_bytes)
+
+
+def from_base64url_uint(val):
+    if isinstance(val, text_type):
+        val = val.encode('ascii')
+
+    data = base64url_decode(val)
+
+    buf = struct.unpack('%sB' % len(data), data)
+    return int(''.join(["%02x" % byte for byte in buf]), 16)
+
+
+def merge_dict(original, updates):
+    if not updates:
+        return original
+
+    try:
+        merged_options = original.copy()
+        merged_options.update(updates)
+    except (AttributeError, ValueError) as e:
+        raise TypeError('original and updates must be a dictionary: %s' % e)
+
+    return merged_options
+
+
+def number_to_bytes(num, num_bytes):
+    padded_hex = '%0*x' % (2 * num_bytes, num)
+    big_endian = binascii.a2b_hex(padded_hex.encode('ascii'))
+    return big_endian
+
+
+def bytes_to_number(string):
+    return int(binascii.b2a_hex(string), 16)
+
+
+def der_to_raw_signature(der_sig, curve):
+    num_bits = curve.key_size
+    num_bytes = (num_bits + 7) // 8
+
+    r, s = decode_dss_signature(der_sig)
+
+    return number_to_bytes(r, num_bytes) + number_to_bytes(s, num_bytes)
+
+
+def raw_to_der_signature(raw_sig, curve):
+    num_bits = curve.key_size
+    num_bytes = (num_bits + 7) // 8
+
+    if len(raw_sig) != 2 * num_bytes:
+        raise ValueError('Invalid signature')
+
+    r = bytes_to_number(raw_sig[:num_bytes])
+    s = bytes_to_number(raw_sig[num_bytes:])
+
+    return encode_dss_signature(r, s)

+ 19 - 0
desktop/core/ext-py/PyJWT-1.7.1/setup.cfg

@@ -0,0 +1,19 @@
+[flake8]
+max-line-length = 119
+exclude = 
+	docs/,
+	.tox/
+
+[wheel]
+universal = 1
+
+[tool:pytest]
+addopts = --cov-report term-missing --cov-config=.coveragerc --cov .
+
+[aliases]
+test = pytest
+
+[egg_info]
+tag_build = 
+tag_date = 0
+

+ 87 - 0
desktop/core/ext-py/PyJWT-1.7.1/setup.py

@@ -0,0 +1,87 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+import os
+import re
+import sys
+
+from setuptools import find_packages, setup
+
+
+def get_version(package):
+    """
+    Return package version as listed in `__version__` in `init.py`.
+    """
+    with open(os.path.join(package, '__init__.py'), 'rb') as init_py:
+        src = init_py.read().decode('utf-8')
+        return re.search("__version__ = ['\"]([^'\"]+)['\"]", src).group(1)
+
+
+version = get_version('jwt')
+
+with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as readme:
+    long_description = readme.read()
+
+if sys.argv[-1] == 'publish':
+    if os.system("pip freeze | grep twine"):
+        print("twine not installed.\nUse `pip install twine`.\nExiting.")
+        sys.exit()
+    os.system("python setup.py sdist bdist_wheel")
+    os.system("twine upload dist/*")
+    print('You probably want to also tag the version now:')
+    print(" git tag -a {0} -m 'version {0}'".format(version))
+    print(' git push --tags')
+    sys.exit()
+
+tests_require = [
+    'pytest>=4.0.1,<5.0.0',
+    'pytest-cov>=2.6.0,<3.0.0',
+    'pytest-runner>=4.2,<5.0.0',
+]
+
+needs_pytest = set(('pytest', 'test', 'ptr')).intersection(sys.argv)
+pytest_runner = ['pytest-runner'] if needs_pytest else []
+
+setup(
+    name='PyJWT',
+    version=version,
+    author='Jose Padilla',
+    author_email='hello@jpadilla.com',
+    description='JSON Web Token implementation in Python',
+    license='MIT',
+    keywords='jwt json web token security signing',
+    url='http://github.com/jpadilla/pyjwt',
+    packages=find_packages(
+        exclude=["*.tests", "*.tests.*", "tests.*", "tests"]
+    ),
+    long_description=long_description,
+    classifiers=[
+        'Development Status :: 5 - Production/Stable',
+        'Intended Audience :: Developers',
+        'Natural Language :: English',
+        'License :: OSI Approved :: MIT License',
+        'Programming Language :: Python',
+        'Programming Language :: Python :: 2.7',
+        'Programming Language :: Python :: 3.4',
+        'Programming Language :: Python :: 3.5',
+        'Programming Language :: Python :: 3.6',
+        'Programming Language :: Python :: 3.7',
+        'Topic :: Utilities',
+    ],
+    test_suite='tests',
+    setup_requires=pytest_runner,
+    tests_require=tests_require,
+    extras_require=dict(
+        test=tests_require,
+        crypto=['cryptography >= 1.4'],
+        flake8=[
+            'flake8',
+            'flake8-import-order',
+            'pep8-naming'
+        ]
+    ),
+    entry_points={
+        'console_scripts': [
+            'pyjwt = jwt.__main__:main'
+        ]
+    }
+)

+ 0 - 0
desktop/core/ext-py/PyJWT-1.7.1/tests/__init__.py


+ 12 - 0
desktop/core/ext-py/PyJWT-1.7.1/tests/compat.py

@@ -0,0 +1,12 @@
+# flake8: noqa
+
+import sys
+
+PY3 = sys.version_info[0] == 3
+
+if PY3:
+    string_types = str,
+    text_type = str
+else:
+    string_types = basestring,
+    text_type = unicode

+ 0 - 0
desktop/core/ext-py/PyJWT-1.7.1/tests/contrib/__init__.py


+ 190 - 0
desktop/core/ext-py/PyJWT-1.7.1/tests/contrib/test_algorithms.py

@@ -0,0 +1,190 @@
+import base64
+
+from jwt.utils import force_bytes, force_unicode
+
+import pytest
+
+from ..utils import key_path
+
+try:
+    from jwt.contrib.algorithms.pycrypto import RSAAlgorithm
+    has_pycrypto = True
+except ImportError:
+    has_pycrypto = False
+
+try:
+    from jwt.contrib.algorithms.py_ecdsa import ECAlgorithm
+    has_ecdsa = True
+except ImportError:
+    has_ecdsa = False
+
+
+@pytest.mark.skipif(not has_pycrypto, reason='Not supported without PyCrypto library')
+class TestPycryptoAlgorithms:
+    def test_rsa_should_parse_pem_public_key(self):
+        algo = RSAAlgorithm(RSAAlgorithm.SHA256)
+
+        with open(key_path('testkey2_rsa.pub.pem'), 'r') as pem_key:
+            algo.prepare_key(pem_key.read())
+
+    def test_rsa_should_accept_unicode_key(self):
+        algo = RSAAlgorithm(RSAAlgorithm.SHA256)
+
+        with open(key_path('testkey_rsa'), 'r') as rsa_key:
+            algo.prepare_key(force_unicode(rsa_key.read()))
+
+    def test_rsa_should_reject_non_string_key(self):
+        algo = RSAAlgorithm(RSAAlgorithm.SHA256)
+
+        with pytest.raises(TypeError):
+            algo.prepare_key(None)
+
+    def test_rsa_sign_should_generate_correct_signature_value(self):
+        algo = RSAAlgorithm(RSAAlgorithm.SHA256)
+
+        jwt_message = force_bytes('Hello World!')
+
+        expected_sig = base64.b64decode(force_bytes(
+            'yS6zk9DBkuGTtcBzLUzSpo9gGJxJFOGvUqN01iLhWHrzBQ9ZEz3+Ae38AXp'
+            '10RWwscp42ySC85Z6zoN67yGkLNWnfmCZSEv+xqELGEvBJvciOKsrhiObUl'
+            '2mveSc1oeO/2ujkGDkkkJ2epn0YliacVjZF5+/uDmImUfAAj8lzjnHlzYix'
+            'sn5jGz1H07jYYbi9diixN8IUhXeTafwFg02IcONhum29V40Wu6O5tAKWlJX'
+            'fHJnNUzAEUOXS0WahHVb57D30pcgIji9z923q90p5c7E2cU8V+E1qe8NdCA'
+            'APCDzZZ9zQ/dgcMVaBrGrgimrcLbPjueOKFgSO+SSjIElKA=='))
+
+        with open(key_path('testkey_rsa'), 'r') as keyfile:
+            jwt_key = algo.prepare_key(keyfile.read())
+
+        with open(key_path('testkey_rsa.pub'), 'r') as keyfile:
+            jwt_pub_key = algo.prepare_key(keyfile.read())
+
+        algo.sign(jwt_message, jwt_key)
+        result = algo.verify(jwt_message, jwt_pub_key, expected_sig)
+        assert result
+
+    def test_rsa_verify_should_return_false_if_signature_invalid(self):
+        algo = RSAAlgorithm(RSAAlgorithm.SHA256)
+
+        jwt_message = force_bytes('Hello World!')
+
+        jwt_sig = base64.b64decode(force_bytes(
+            'yS6zk9DBkuGTtcBzLUzSpo9gGJxJFOGvUqN01iLhWHrzBQ9ZEz3+Ae38AXp'
+            '10RWwscp42ySC85Z6zoN67yGkLNWnfmCZSEv+xqELGEvBJvciOKsrhiObUl'
+            '2mveSc1oeO/2ujkGDkkkJ2epn0YliacVjZF5+/uDmImUfAAj8lzjnHlzYix'
+            'sn5jGz1H07jYYbi9diixN8IUhXeTafwFg02IcONhum29V40Wu6O5tAKWlJX'
+            'fHJnNUzAEUOXS0WahHVb57D30pcgIji9z923q90p5c7E2cU8V+E1qe8NdCA'
+            'APCDzZZ9zQ/dgcMVaBrGrgimrcLbPjueOKFgSO+SSjIElKA=='))
+
+        jwt_sig += force_bytes('123')  # Signature is now invalid
+
+        with open(key_path('testkey_rsa.pub'), 'r') as keyfile:
+            jwt_pub_key = algo.prepare_key(keyfile.read())
+
+        result = algo.verify(jwt_message, jwt_pub_key, jwt_sig)
+        assert not result
+
+    def test_rsa_verify_should_return_true_if_signature_valid(self):
+        algo = RSAAlgorithm(RSAAlgorithm.SHA256)
+
+        jwt_message = force_bytes('Hello World!')
+
+        jwt_sig = base64.b64decode(force_bytes(
+            'yS6zk9DBkuGTtcBzLUzSpo9gGJxJFOGvUqN01iLhWHrzBQ9ZEz3+Ae38AXp'
+            '10RWwscp42ySC85Z6zoN67yGkLNWnfmCZSEv+xqELGEvBJvciOKsrhiObUl'
+            '2mveSc1oeO/2ujkGDkkkJ2epn0YliacVjZF5+/uDmImUfAAj8lzjnHlzYix'
+            'sn5jGz1H07jYYbi9diixN8IUhXeTafwFg02IcONhum29V40Wu6O5tAKWlJX'
+            'fHJnNUzAEUOXS0WahHVb57D30pcgIji9z923q90p5c7E2cU8V+E1qe8NdCA'
+            'APCDzZZ9zQ/dgcMVaBrGrgimrcLbPjueOKFgSO+SSjIElKA=='))
+
+        with open(key_path('testkey_rsa.pub'), 'r') as keyfile:
+            jwt_pub_key = algo.prepare_key(keyfile.read())
+
+        result = algo.verify(jwt_message, jwt_pub_key, jwt_sig)
+        assert result
+
+    def test_rsa_prepare_key_should_be_idempotent(self):
+        algo = RSAAlgorithm(RSAAlgorithm.SHA256)
+
+        with open(key_path('testkey_rsa.pub'), 'r') as keyfile:
+            jwt_pub_key_first = algo.prepare_key(keyfile.read())
+            jwt_pub_key_second = algo.prepare_key(jwt_pub_key_first)
+
+        assert jwt_pub_key_first == jwt_pub_key_second
+
+
+@pytest.mark.skipif(not has_ecdsa, reason='Not supported without ecdsa library')
+class TestEcdsaAlgorithms:
+    def test_ec_should_reject_non_string_key(self):
+        algo = ECAlgorithm(ECAlgorithm.SHA256)
+
+        with pytest.raises(TypeError):
+            algo.prepare_key(None)
+
+    def test_ec_should_accept_unicode_key(self):
+        algo = ECAlgorithm(ECAlgorithm.SHA256)
+
+        with open(key_path('testkey_ec'), 'r') as ec_key:
+            algo.prepare_key(force_unicode(ec_key.read()))
+
+    def test_ec_sign_should_generate_correct_signature_value(self):
+        algo = ECAlgorithm(ECAlgorithm.SHA256)
+
+        jwt_message = force_bytes('Hello World!')
+
+        expected_sig = base64.b64decode(force_bytes(
+            'AC+m4Jf/xI3guAC6w0w37t5zRpSCF6F4udEz5LiMiTIjCS4vcVe6dDOxK+M'
+            'mvkF8PxJuvqxP2CO3TR3okDPCl/NjATTO1jE+qBZ966CRQSSzcCM+tzcHzw'
+            'LZS5kbvKu0Acd/K6Ol2/W3B1NeV5F/gjvZn/jOwaLgWEUYsg0o4XVrAg65'))
+
+        with open(key_path('testkey_ec'), 'r') as keyfile:
+            jwt_key = algo.prepare_key(keyfile.read())
+
+        with open(key_path('testkey_ec.pub'), 'r') as keyfile:
+            jwt_pub_key = algo.prepare_key(keyfile.read())
+
+        algo.sign(jwt_message, jwt_key)
+        result = algo.verify(jwt_message, jwt_pub_key, expected_sig)
+        assert result
+
+    def test_ec_verify_should_return_false_if_signature_invalid(self):
+        algo = ECAlgorithm(ECAlgorithm.SHA256)
+
+        jwt_message = force_bytes('Hello World!')
+
+        jwt_sig = base64.b64decode(force_bytes(
+            'AC+m4Jf/xI3guAC6w0w37t5zRpSCF6F4udEz5LiMiTIjCS4vcVe6dDOxK+M'
+            'mvkF8PxJuvqxP2CO3TR3okDPCl/NjATTO1jE+qBZ966CRQSSzcCM+tzcHzw'
+            'LZS5kbvKu0Acd/K6Ol2/W3B1NeV5F/gjvZn/jOwaLgWEUYsg0o4XVrAg65'))
+
+        jwt_sig += force_bytes('123')  # Signature is now invalid
+
+        with open(key_path('testkey_ec.pub'), 'r') as keyfile:
+            jwt_pub_key = algo.prepare_key(keyfile.read())
+
+        result = algo.verify(jwt_message, jwt_pub_key, jwt_sig)
+        assert not result
+
+    def test_ec_verify_should_return_true_if_signature_valid(self):
+        algo = ECAlgorithm(ECAlgorithm.SHA256)
+
+        jwt_message = force_bytes('Hello World!')
+
+        jwt_sig = base64.b64decode(force_bytes(
+            'AC+m4Jf/xI3guAC6w0w37t5zRpSCF6F4udEz5LiMiTIjCS4vcVe6dDOxK+M'
+            'mvkF8PxJuvqxP2CO3TR3okDPCl/NjATTO1jE+qBZ966CRQSSzcCM+tzcHzw'
+            'LZS5kbvKu0Acd/K6Ol2/W3B1NeV5F/gjvZn/jOwaLgWEUYsg0o4XVrAg65'))
+
+        with open(key_path('testkey_ec.pub'), 'r') as keyfile:
+            jwt_pub_key = algo.prepare_key(keyfile.read())
+
+        result = algo.verify(jwt_message, jwt_pub_key, jwt_sig)
+        assert result
+
+    def test_ec_prepare_key_should_be_idempotent(self):
+        algo = ECAlgorithm(ECAlgorithm.SHA256)
+
+        with open(key_path('testkey_ec.pub'), 'r') as keyfile:
+            jwt_pub_key_first = algo.prepare_key(keyfile.read())
+            jwt_pub_key_second = algo.prepare_key(jwt_pub_key_first)
+
+        assert jwt_pub_key_first == jwt_pub_key_second

+ 57 - 0
desktop/core/ext-py/PyJWT-1.7.1/tests/keys/__init__.py

@@ -0,0 +1,57 @@
+import json
+import os
+
+from jwt.utils import base64url_decode, force_bytes
+
+from tests.utils import int_from_bytes
+
+BASE_PATH = os.path.dirname(os.path.abspath(__file__))
+
+
+def decode_value(val):
+    decoded = base64url_decode(force_bytes(val))
+    return int_from_bytes(decoded, 'big')
+
+
+def load_hmac_key():
+    with open(os.path.join(BASE_PATH, 'jwk_hmac.json'), 'r') as infile:
+        keyobj = json.load(infile)
+
+    return base64url_decode(force_bytes(keyobj['k']))
+
+
+try:
+    from cryptography.hazmat.primitives.asymmetric import ec
+    from cryptography.hazmat.backends import default_backend
+    from jwt.algorithms import RSAAlgorithm
+    has_crypto = True
+except ImportError:
+    has_crypto = False
+
+if has_crypto:
+    def load_rsa_key():
+        with open(os.path.join(BASE_PATH, 'jwk_rsa_key.json'), 'r') as infile:
+            return RSAAlgorithm.from_jwk(infile.read())
+
+    def load_rsa_pub_key():
+        with open(os.path.join(BASE_PATH, 'jwk_rsa_pub.json'), 'r') as infile:
+            return RSAAlgorithm.from_jwk(infile.read())
+
+    def load_ec_key():
+        with open(os.path.join(BASE_PATH, 'jwk_ec_key.json'), 'r') as infile:
+            keyobj = json.load(infile)
+
+        return ec.EllipticCurvePrivateNumbers(
+            private_value=decode_value(keyobj['d']),
+            public_numbers=load_ec_pub_key().public_numbers()
+        )
+
+    def load_ec_pub_key():
+        with open(os.path.join(BASE_PATH, 'jwk_ec_pub.json'), 'r') as infile:
+            keyobj = json.load(infile)
+
+        return ec.EllipticCurvePublicNumbers(
+            x=decode_value(keyobj['x']),
+            y=decode_value(keyobj['y']),
+            curve=ec.SECP521R1()
+        ).public_key(default_backend())

+ 9 - 0
desktop/core/ext-py/PyJWT-1.7.1/tests/keys/jwk_ec_key.json

@@ -0,0 +1,9 @@
+{
+     "kty": "EC",
+     "kid": "bilbo.baggins@hobbiton.example",
+     "use": "sig",
+     "crv": "P-521",
+     "x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt",
+     "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1",
+     "d": "AAhRON2r9cqXX1hg-RoI6R1tX5p2rUAYdmpHZoC1XNM56KtscrX6zbKipQrCW9CGZH3T4ubpnoTKLDYJ_fF3_rJt"
+}

+ 8 - 0
desktop/core/ext-py/PyJWT-1.7.1/tests/keys/jwk_ec_pub.json

@@ -0,0 +1,8 @@
+{
+     "kty": "EC",
+     "kid": "bilbo.baggins@hobbiton.example",
+     "use": "sig",
+     "crv": "P-521",
+     "x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt",
+     "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1"
+}

+ 7 - 0
desktop/core/ext-py/PyJWT-1.7.1/tests/keys/jwk_hmac.json

@@ -0,0 +1,7 @@
+{
+     "kty": "oct",
+     "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037",
+     "use": "sig",
+     "alg": "HS256",
+     "k": "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg"
+}

+ 13 - 0
desktop/core/ext-py/PyJWT-1.7.1/tests/keys/jwk_rsa_key.json

@@ -0,0 +1,13 @@
+{
+     "kty": "RSA",
+     "kid": "bilbo.baggins@hobbiton.example",
+     "use": "sig",
+     "n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw",
+     "e": "AQAB",
+     "d": "bWUC9B-EFRIo8kpGfh0ZuyGPvMNKvYWNtB_ikiH9k20eT-O1q_I78eiZkpXxXQ0UTEs2LsNRS-8uJbvQ-A1irkwMSMkK1J3XTGgdrhCku9gRldY7sNA_AKZGh-Q661_42rINLRCe8W-nZ34ui_qOfkLnK9QWDDqpaIsA-bMwWWSDFu2MUBYwkHTMEzLYGqOe04noqeq1hExBTHBOBdkMXiuFhUq1BU6l-DqEiWxqg82sXt2h-LMnT3046AOYJoRioz75tSUQfGCshWTBnP5uDjd18kKhyv07lhfSJdrPdM5Plyl21hsFf4L_mHCuoFau7gdsPfHPxxjVOcOpBrQzwQ",
+     "p": "3Slxg_DwTXJcb6095RoXygQCAZ5RnAvZlno1yhHtnUex_fp7AZ_9nRaO7HX_-SFfGQeutao2TDjDAWU4Vupk8rw9JR0AzZ0N2fvuIAmr_WCsmGpeNqQnev1T7IyEsnh8UMt-n5CafhkikzhEsrmndH6LxOrvRJlsPp6Zv8bUq0k",
+     "q": "uKE2dh-cTf6ERF4k4e_jy78GfPYUIaUyoSSJuBzp3Cubk3OCqs6grT8bR_cu0Dm1MZwWmtdqDyI95HrUeq3MP15vMMON8lHTeZu2lmKvwqW7anV5UzhM1iZ7z4yMkuUwFWoBvyY898EXvRD-hdqRxHlSqAZ192zB3pVFJ0s7pFc",
+     "dp": "B8PVvXkvJrj2L-GYQ7v3y9r6Kw5g9SahXBwsWUzp19TVlgI-YV85q1NIb1rxQtD-IsXXR3-TanevuRPRt5OBOdiMGQp8pbt26gljYfKU_E9xn-RULHz0-ed9E9gXLKD4VGngpz-PfQ_q29pk5xWHoJp009Qf1HvChixRX59ehik",
+     "dq": "CLDmDGduhylc9o7r84rEUVn7pzQ6PF83Y-iBZx5NT-TpnOZKF1pErAMVeKzFEl41DlHHqqBLSM0W1sOFbwTxYWZDm6sI6og5iTbwQGIC3gnJKbi_7k_vJgGHwHxgPaX2PnvP-zyEkDERuf-ry4c_Z11Cq9AqC2yeL6kdKT1cYF8",
+     "qi": "3PiqvXQN0zwMeE-sBvZgi289XP9XCQF3VWqPzMKnIgQp7_Tugo6-NZBKCQsMf3HaEGBjTVJs_jcK8-TRXvaKe-7ZMaQj8VfBdYkssbu0NKDDhjJ-GtiseaDVWt7dcH0cfwxgFUHpQh7FoCrjFJ6h6ZEpMF6xmujs4qMpPz8aaI4"
+}

+ 7 - 0
desktop/core/ext-py/PyJWT-1.7.1/tests/keys/jwk_rsa_pub.json

@@ -0,0 +1,7 @@
+{
+     "kty": "RSA",
+     "kid": "bilbo.baggins@hobbiton.example",
+     "use": "sig",
+     "n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw",
+     "e": "AQAB"
+}

+ 9 - 0
desktop/core/ext-py/PyJWT-1.7.1/tests/keys/testkey2_rsa.pub.pem

@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1tUH3/0v8fvLensHO1g2
+6+U4r7jBg43DVOgqmXAWQa8ArAb4NfTrsYX8YkVhZZYwuLmKczRj0GhXUVY9iDbT
+sIGmgG+ySj6eiREz5VLqofFkAvRZ6y7yNv8PIGgXEhQTiDDNIkHGaFNMvn/eZ54H
+is70pdTjR5Ko+/y/wg71df1nb/5KwttSvy0YsTu/XpkduonPruYfAVRG3HK+3GZd
+xTygLcdamwe9jj+kjxtXRlrXVMQiXGFSU8U6bjafWnQiQ9XzjxvygBt0ZD0kRorr
+p74XGyQY5ThkN8DlpJbTTFsxOnBUAQz4zhohjobIGBRimi5yVlyLOwTlpaKGFC7O
+7wIDAQAB
+-----END PUBLIC KEY-----

+ 7 - 0
desktop/core/ext-py/PyJWT-1.7.1/tests/keys/testkey_ec

@@ -0,0 +1,7 @@
+-----BEGIN EC PRIVATE KEY-----
+MIHbAgEBBEG4xN/z6gk7bPkEzs1hHOsbs+Gi2lku8YH4LkS4E1q9U9jSOjvEcFNH
+m/CQjKi1rtpAb0/WL3p/wXsc26e7zmAA5KAHBgUrgQQAI6GBiQOBhgAEAVnCcDxA
+J0v5OJBYFIcTReydEkEIWRvpzYMvv5l8IUOT2SFJiHdWtU45DV4is7+g6bbQanbh
+28/1dBLR/kH1stAeAYWeTJ08gxo3M9Q0KinXsXm4c6G24UiGY6WHeWlOPKPa16fz
+pwJ62o3XaRrCdGzX+K7TCwahWCTeizrJQAe8UwUY
+-----END EC PRIVATE KEY-----

+ 6 - 0
desktop/core/ext-py/PyJWT-1.7.1/tests/keys/testkey_ec.pub

@@ -0,0 +1,6 @@
+-----BEGIN PUBLIC KEY-----
+MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBWcJwPEAnS/k4kFgUhxNF7J0SQQhZ
+G+nNgy+/mXwhQ5PZIUmId1a1TjkNXiKzv6DpttBqduHbz/V0EtH+QfWy0B4BhZ5M
+nTyDGjcz1DQqKdexebhzobbhSIZjpYd5aU48o9rXp/OnAnrajddpGsJ0bNf4rtML
+BqFYJN6LOslAB7xTBRg=
+-----END PUBLIC KEY-----

+ 1 - 0
desktop/core/ext-py/PyJWT-1.7.1/tests/keys/testkey_ec_ssh.pub

@@ -0,0 +1 @@
+ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAFZwnA8QCdL+TiQWBSHE0XsnRJBCFkb6c2DL7+ZfCFDk9khSYh3VrVOOQ1eIrO/oOm20Gp24dvP9XQS0f5B9bLQHgGFnkydPIMaNzPUNCop17F5uHOhtuFIhmOlh3lpTjyj2ten86cCetqN12kawnRs1/iu0wsGoVgk3os6yUAHvFMFGA==

+ 5 - 0
desktop/core/ext-py/PyJWT-1.7.1/tests/keys/testkey_pkcs1.pub.pem

@@ -0,0 +1,5 @@
+-----BEGIN RSA PUBLIC KEY-----
+MIGHAoGBAOV/0Vl/5VdHcYpnILYzBGWo5JQVzo9wBkbxzjAStcAnTwvv1ZJTMXs6
+fjz91f9hiMM4Z/5qNTE/EHlDWxVdj1pyRaQulZPUs0r9qJ02ogRRGLG3jjrzzbzF
+yj/pdNBwym0UJYC/Jmn/kMLwGiWI2nfa9vM5SovqZiAy2FD7eOtVAgED
+-----END RSA PUBLIC KEY-----

+ 27 - 0
desktop/core/ext-py/PyJWT-1.7.1/tests/keys/testkey_rsa

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA1HgzBfJv2cOjQryCwe8NEelriOTNFWKZUivevUrRhlqcmZJd
+CvuCJRr+xCN+OmO8qwgJJR98feNujxVg+J9Ls3/UOA4HcF9nYH6aqVXELAE8Hk/A
+Lvxi96ms1DDuAvQGaYZ+lANxlvxeQFOZSbjkz/9mh8aLeGKwqJLp3p+OhUBQpwvA
+UAPg82+OUtgTW3nSljjeFr14B8qAneGSc/wl0ni++1SRZUXFSovzcqQOkla3W27r
+rLfrD6LXgj/TsDs4vD1PnIm1zcVenKT7TfYI17bsG/O/Wecwz2Nl19pL7gDosNru
+F3ogJWNq1Lyn/ijPQnkPLpZHyhvuiycYcI3DiQIDAQABAoIBAQCt9uzwBZ0HVGQs
+lGULnUu6SsC9iXlR9TVMTpdFrij4NODb7Tc5cs0QzJWkytrjvB4Se7XhK3KnMLyp
+cvu/Fc7J3fRJIVN98t+V5pOD6rGAxlIPD4Vv8z6lQcw8wQNgb6WAaZriXh93XJNf
+YBO2hSj0FU5CBZLUsxmqLQBIQ6RR/OUGAvThShouE9K4N0vKB2UPOCu5U+d5zS3W
+44Q5uatxYiSHBTYIZDN4u27Nfo5WA+GTvFyeNsO6tNNWlYfRHSBtnm6SZDY/5i4J
+fxP2JY0waM81KRvuHTazY571lHM/TTvFDRUX5nvHIu7GToBKahfVLf26NJuTZYXR
+5c09GAXBAoGBAO7a9M/dvS6eDhyESYyCjP6w61jD7UYJ1fudaYFrDeqnaQ857Pz4
+BcKx3KMmLFiDvuMgnVVj8RToBGfMV0zP7sDnuFRJnWYcOeU8e2sWGbZmWGWzv0SD
++AhppSZThU4mJ8aa/tgsepCHkJnfoX+3wN7S9NfGhM8GDGxTHJwBpxINAoGBAOO4
+ZVtn9QEblmCX/Q5ejInl43Y9nRsfTy9lB9Lp1cyWCJ3eep6lzT60K3OZGVOuSgKQ
+vZ/aClMCMbqsAAG4fKBjREA6p7k4/qaMApHQum8APCh9WPsKLaavxko8ZDc41kZt
+hgKyUs2XOhW/BLjmzqwGryidvOfszDwhH7rNVmRtAoGBALYGdvrSaRHVsbtZtRM3
+imuuOCx1Y6U0abZOx9Cw3PIukongAxLlkL5G/XX36WOrQxWkDUK930OnbXQM7ZrD
++5dW/8p8L09Zw2VHKmb5eK7gYA1hZim4yJTgrdL/Y1+jBDz+cagcfWsXZMNfAZxr
+VLh628x0pVF/sof67pqVR9UhAoGBAMcQiLoQ9GJVhW1HMBYBnQVnCyJv1gjBo+0g
+emhrtVQ0y6+FrtdExVjNEzboXPWD5Hq9oKY+aswJnQM8HH1kkr16SU2EeN437pQU
+zKI/PtqN8AjNGp3JVgLioYp/pHOJofbLA10UGcJTMpmT9ELWsVA8P55X1a1AmYDu
+y9f2bFE5AoGAdjo95mB0LVYikNPa+NgyDwLotLqrueb9IviMmn6zKHCwiOXReqXD
+X9slB8RA15uv56bmN04O//NyVFcgJ2ef169GZHiRFIgIy0Pl8LYkMhCYKKhyqM7g
+xN+SqGqDTKDC22j00S7jcvCaa1qadn1qbdfukZ4NXv7E2d/LO0Y2Kkc=
+-----END RSA PRIVATE KEY-----

+ 21 - 0
desktop/core/ext-py/PyJWT-1.7.1/tests/keys/testkey_rsa.cer

@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDhTCCAm2gAwIBAgIJANE4sir3EkX8MA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNV
+BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEPMA0GA1UEBwwGQXVzdGluMQ4wDAYDVQQK
+DAVQeUpXVDEZMBcGA1UECwwQVGVzdCBDZXJ0aWZpY2F0ZTAeFw0xNTAzMTgwMTE2
+MTRaFw0xODAzMTcwMTE2MTRaMFkxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhh
+czEPMA0GA1UEBwwGQXVzdGluMQ4wDAYDVQQKDAVQeUpXVDEZMBcGA1UECwwQVGVz
+dCBDZXJ0aWZpY2F0ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANR4
+MwXyb9nDo0K8gsHvDRHpa4jkzRVimVIr3r1K0YZanJmSXQr7giUa/sQjfjpjvKsI
+CSUffH3jbo8VYPifS7N/1DgOB3BfZ2B+mqlVxCwBPB5PwC78YveprNQw7gL0BmmG
+fpQDcZb8XkBTmUm45M//ZofGi3hisKiS6d6fjoVAUKcLwFAD4PNvjlLYE1t50pY4
+3ha9eAfKgJ3hknP8JdJ4vvtUkWVFxUqL83KkDpJWt1tu66y36w+i14I/07A7OLw9
+T5yJtc3FXpyk+032CNe27Bvzv1nnMM9jZdfaS+4A6LDa7hd6ICVjatS8p/4oz0J5
+Dy6WR8ob7osnGHCNw4kCAwEAAaNQME4wHQYDVR0OBBYEFDR6fVdFxZED6YMmD62W
+LlBW+qEBMB8GA1UdIwQYMBaAFDR6fVdFxZED6YMmD62WLlBW+qEBMAwGA1UdEwQF
+MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFwDNwm+lU/kGfWwiWM0Lv2aosXotoiG
+TsBSWIn2iYphq0vzlgChcNocN9zkaOz3zc9pcREP6lyqHpE0OEbNucHHDdU1L2he
+lLFOLOmkpP5fyPDXs9nKYhO8ygMByEonHm3K/VvCgrsSgJ3JuxMLUxnE55jQXGWV
+OqYQNo2J5h93Zd2HTTe19jCz+bbWnRBP5VvLAAAo5YSmk3iroWSPWAKkWOOecJ2Q
+/xnRyuWERsfvZiF/m9q7yDJ55LXVVm3Rufmy76SoTnJ2acap+XQNXBH/AxayeLUS
+OYmHWH61dUcsQtwXYHYRB8TTtMIwUCXGmthXkDJydEfrGcD0y6APIh8=
+-----END CERTIFICATE-----

+ 1 - 0
desktop/core/ext-py/PyJWT-1.7.1/tests/keys/testkey_rsa.pub

@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDUeDMF8m/Zw6NCvILB7w0R6WuI5M0VYplSK969StGGWpyZkl0K+4IlGv7EI346Y7yrCAklH3x9426PFWD4n0uzf9Q4DgdwX2dgfpqpVcQsATweT8Au/GL3qazUMO4C9AZphn6UA3GW/F5AU5lJuOTP/2aHxot4YrCokunen46FQFCnC8BQA+Dzb45S2BNbedKWON4WvXgHyoCd4ZJz/CXSeL77VJFlRcVKi/NypA6SVrdbbuust+sPoteCP9OwOzi8PU+cibXNxV6cpPtN9gjXtuwb879Z5zDPY2XX2kvuAOiw2u4XeiAlY2rUvKf+KM9CeQ8ulkfKG+6LJxhwjcOJ aasmundo@mair.local

+ 582 - 0
desktop/core/ext-py/PyJWT-1.7.1/tests/test_algorithms.py

@@ -0,0 +1,582 @@
+import base64
+import json
+
+from jwt.algorithms import Algorithm, HMACAlgorithm, NoneAlgorithm
+from jwt.exceptions import InvalidKeyError
+from jwt.utils import base64url_decode, force_bytes, force_unicode
+
+import pytest
+
+from .keys import load_hmac_key
+from .utils import key_path
+
+try:
+    from jwt.algorithms import RSAAlgorithm, ECAlgorithm, RSAPSSAlgorithm
+    from .keys import load_rsa_pub_key, load_ec_pub_key
+    has_crypto = True
+except ImportError:
+    has_crypto = False
+
+
+class TestAlgorithms:
+    def test_algorithm_should_throw_exception_if_prepare_key_not_impl(self):
+        algo = Algorithm()
+
+        with pytest.raises(NotImplementedError):
+            algo.prepare_key('test')
+
+    def test_algorithm_should_throw_exception_if_sign_not_impl(self):
+        algo = Algorithm()
+
+        with pytest.raises(NotImplementedError):
+            algo.sign('message', 'key')
+
+    def test_algorithm_should_throw_exception_if_verify_not_impl(self):
+        algo = Algorithm()
+
+        with pytest.raises(NotImplementedError):
+            algo.verify('message', 'key', 'signature')
+
+    def test_algorithm_should_throw_exception_if_to_jwk_not_impl(self):
+        algo = Algorithm()
+
+        with pytest.raises(NotImplementedError):
+            algo.from_jwk('value')
+
+    def test_algorithm_should_throw_exception_if_from_jwk_not_impl(self):
+        algo = Algorithm()
+
+        with pytest.raises(NotImplementedError):
+            algo.to_jwk('value')
+
+    def test_none_algorithm_should_throw_exception_if_key_is_not_none(self):
+        algo = NoneAlgorithm()
+
+        with pytest.raises(InvalidKeyError):
+            algo.prepare_key('123')
+
+    def test_hmac_should_reject_nonstring_key(self):
+        algo = HMACAlgorithm(HMACAlgorithm.SHA256)
+
+        with pytest.raises(TypeError) as context:
+            algo.prepare_key(object())
+
+        exception = context.value
+        assert str(exception) == 'Expected a string value'
+
+    def test_hmac_should_accept_unicode_key(self):
+        algo = HMACAlgorithm(HMACAlgorithm.SHA256)
+
+        algo.prepare_key(force_unicode('awesome'))
+
+    def test_hmac_should_throw_exception_if_key_is_pem_public_key(self):
+        algo = HMACAlgorithm(HMACAlgorithm.SHA256)
+
+        with pytest.raises(InvalidKeyError):
+            with open(key_path('testkey2_rsa.pub.pem'), 'r') as keyfile:
+                algo.prepare_key(keyfile.read())
+
+    def test_hmac_should_throw_exception_if_key_is_x509_certificate(self):
+        algo = HMACAlgorithm(HMACAlgorithm.SHA256)
+
+        with pytest.raises(InvalidKeyError):
+            with open(key_path('testkey_rsa.cer'), 'r') as keyfile:
+                algo.prepare_key(keyfile.read())
+
+    def test_hmac_should_throw_exception_if_key_is_ssh_public_key(self):
+        algo = HMACAlgorithm(HMACAlgorithm.SHA256)
+
+        with pytest.raises(InvalidKeyError):
+            with open(key_path('testkey_rsa.pub'), 'r') as keyfile:
+                algo.prepare_key(keyfile.read())
+
+    def test_hmac_should_throw_exception_if_key_is_x509_cert(self):
+        algo = HMACAlgorithm(HMACAlgorithm.SHA256)
+
+        with pytest.raises(InvalidKeyError):
+            with open(key_path('testkey2_rsa.pub.pem'), 'r') as keyfile:
+                algo.prepare_key(keyfile.read())
+
+    def test_hmac_should_throw_exception_if_key_is_pkcs1_pem_public(self):
+        algo = HMACAlgorithm(HMACAlgorithm.SHA256)
+
+        with pytest.raises(InvalidKeyError):
+            with open(key_path('testkey_pkcs1.pub.pem'), 'r') as keyfile:
+                algo.prepare_key(keyfile.read())
+
+    def test_hmac_jwk_should_parse_and_verify(self):
+        algo = HMACAlgorithm(HMACAlgorithm.SHA256)
+
+        with open(key_path('jwk_hmac.json'), 'r') as keyfile:
+            key = algo.from_jwk(keyfile.read())
+
+        signature = algo.sign(b'Hello World!', key)
+        assert algo.verify(b'Hello World!', key, signature)
+
+    def test_hmac_to_jwk_returns_correct_values(self):
+        algo = HMACAlgorithm(HMACAlgorithm.SHA256)
+        key = algo.to_jwk('secret')
+
+        assert json.loads(key) == {'kty': 'oct', 'k': 'c2VjcmV0'}
+
+    def test_hmac_from_jwk_should_raise_exception_if_not_hmac_key(self):
+        algo = HMACAlgorithm(HMACAlgorithm.SHA256)
+
+        with open(key_path('jwk_rsa_pub.json'), 'r') as keyfile:
+            with pytest.raises(InvalidKeyError):
+                algo.from_jwk(keyfile.read())
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_rsa_should_parse_pem_public_key(self):
+        algo = RSAAlgorithm(RSAAlgorithm.SHA256)
+
+        with open(key_path('testkey2_rsa.pub.pem'), 'r') as pem_key:
+            algo.prepare_key(pem_key.read())
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_rsa_should_accept_pem_private_key_bytes(self):
+        algo = RSAAlgorithm(RSAAlgorithm.SHA256)
+
+        with open(key_path('testkey_rsa'), 'rb') as pem_key:
+            algo.prepare_key(pem_key.read())
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_rsa_should_accept_unicode_key(self):
+        algo = RSAAlgorithm(RSAAlgorithm.SHA256)
+
+        with open(key_path('testkey_rsa'), 'r') as rsa_key:
+            algo.prepare_key(force_unicode(rsa_key.read()))
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_rsa_should_reject_non_string_key(self):
+        algo = RSAAlgorithm(RSAAlgorithm.SHA256)
+
+        with pytest.raises(TypeError):
+            algo.prepare_key(None)
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_rsa_verify_should_return_false_if_signature_invalid(self):
+        algo = RSAAlgorithm(RSAAlgorithm.SHA256)
+
+        message = force_bytes('Hello World!')
+
+        sig = base64.b64decode(force_bytes(
+            'yS6zk9DBkuGTtcBzLUzSpo9gGJxJFOGvUqN01iLhWHrzBQ9ZEz3+Ae38AXp'
+            '10RWwscp42ySC85Z6zoN67yGkLNWnfmCZSEv+xqELGEvBJvciOKsrhiObUl'
+            '2mveSc1oeO/2ujkGDkkkJ2epn0YliacVjZF5+/uDmImUfAAj8lzjnHlzYix'
+            'sn5jGz1H07jYYbi9diixN8IUhXeTafwFg02IcONhum29V40Wu6O5tAKWlJX'
+            'fHJnNUzAEUOXS0WahHVb57D30pcgIji9z923q90p5c7E2cU8V+E1qe8NdCA'
+            'APCDzZZ9zQ/dgcMVaBrGrgimrcLbPjueOKFgSO+SSjIElKA=='))
+
+        sig += force_bytes('123')  # Signature is now invalid
+
+        with open(key_path('testkey_rsa.pub'), 'r') as keyfile:
+            pub_key = algo.prepare_key(keyfile.read())
+
+        result = algo.verify(message, pub_key, sig)
+        assert not result
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_rsa_jwk_public_and_private_keys_should_parse_and_verify(self):
+        algo = RSAAlgorithm(RSAAlgorithm.SHA256)
+
+        with open(key_path('jwk_rsa_pub.json'), 'r') as keyfile:
+            pub_key = algo.from_jwk(keyfile.read())
+
+        with open(key_path('jwk_rsa_key.json'), 'r') as keyfile:
+            priv_key = algo.from_jwk(keyfile.read())
+
+        signature = algo.sign(force_bytes('Hello World!'), priv_key)
+        assert algo.verify(force_bytes('Hello World!'), pub_key, signature)
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_rsa_private_key_to_jwk_works_with_from_jwk(self):
+        algo = RSAAlgorithm(RSAAlgorithm.SHA256)
+
+        with open(key_path('testkey_rsa'), 'r') as rsa_key:
+            orig_key = algo.prepare_key(force_unicode(rsa_key.read()))
+
+        parsed_key = algo.from_jwk(algo.to_jwk(orig_key))
+        assert parsed_key.private_numbers() == orig_key.private_numbers()
+        assert parsed_key.private_numbers().public_numbers == orig_key.private_numbers().public_numbers
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_rsa_public_key_to_jwk_works_with_from_jwk(self):
+        algo = RSAAlgorithm(RSAAlgorithm.SHA256)
+
+        with open(key_path('testkey_rsa.pub'), 'r') as rsa_key:
+            orig_key = algo.prepare_key(force_unicode(rsa_key.read()))
+
+        parsed_key = algo.from_jwk(algo.to_jwk(orig_key))
+        assert parsed_key.public_numbers() == orig_key.public_numbers()
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_rsa_jwk_private_key_with_other_primes_is_invalid(self):
+        algo = RSAAlgorithm(RSAAlgorithm.SHA256)
+
+        with open(key_path('jwk_rsa_key.json'), 'r') as keyfile:
+            with pytest.raises(InvalidKeyError):
+                keydata = json.loads(keyfile.read())
+                keydata['oth'] = []
+
+                algo.from_jwk(json.dumps(keydata))
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_rsa_jwk_private_key_with_missing_values_is_invalid(self):
+        algo = RSAAlgorithm(RSAAlgorithm.SHA256)
+
+        with open(key_path('jwk_rsa_key.json'), 'r') as keyfile:
+            with pytest.raises(InvalidKeyError):
+                keydata = json.loads(keyfile.read())
+                del keydata['p']
+
+                algo.from_jwk(json.dumps(keydata))
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_rsa_jwk_private_key_can_recover_prime_factors(self):
+        algo = RSAAlgorithm(RSAAlgorithm.SHA256)
+
+        with open(key_path('jwk_rsa_key.json'), 'r') as keyfile:
+            keybytes = keyfile.read()
+            control_key = algo.from_jwk(keybytes).private_numbers()
+
+            keydata = json.loads(keybytes)
+            delete_these = ['p', 'q', 'dp', 'dq', 'qi']
+            for field in delete_these:
+                del keydata[field]
+
+            parsed_key = algo.from_jwk(json.dumps(keydata)).private_numbers()
+
+        assert control_key.d == parsed_key.d
+        assert control_key.p == parsed_key.p
+        assert control_key.q == parsed_key.q
+        assert control_key.dmp1 == parsed_key.dmp1
+        assert control_key.dmq1 == parsed_key.dmq1
+        assert control_key.iqmp == parsed_key.iqmp
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_rsa_jwk_private_key_with_missing_required_values_is_invalid(self):
+        algo = RSAAlgorithm(RSAAlgorithm.SHA256)
+
+        with open(key_path('jwk_rsa_key.json'), 'r') as keyfile:
+            with pytest.raises(InvalidKeyError):
+                keydata = json.loads(keyfile.read())
+                del keydata['p']
+
+                algo.from_jwk(json.dumps(keydata))
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_rsa_jwk_raises_exception_if_not_a_valid_key(self):
+        algo = RSAAlgorithm(RSAAlgorithm.SHA256)
+
+        # Invalid JSON
+        with pytest.raises(InvalidKeyError):
+            algo.from_jwk('{not-a-real-key')
+
+        # Missing key parts
+        with pytest.raises(InvalidKeyError):
+            algo.from_jwk('{"kty": "RSA"}')
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_rsa_to_jwk_returns_correct_values_for_public_key(self):
+        algo = RSAAlgorithm(RSAAlgorithm.SHA256)
+
+        with open(key_path('testkey_rsa.pub'), 'r') as keyfile:
+            pub_key = algo.prepare_key(keyfile.read())
+
+        key = algo.to_jwk(pub_key)
+
+        expected = {
+            'e': 'AQAB',
+            'key_ops': ['verify'],
+            'kty': 'RSA',
+            'n': (
+                '1HgzBfJv2cOjQryCwe8NEelriOTNFWKZUivevUrRhlqcmZJdCvuCJRr-xCN-'
+                'OmO8qwgJJR98feNujxVg-J9Ls3_UOA4HcF9nYH6aqVXELAE8Hk_ALvxi96ms'
+                '1DDuAvQGaYZ-lANxlvxeQFOZSbjkz_9mh8aLeGKwqJLp3p-OhUBQpwvAUAPg'
+                '82-OUtgTW3nSljjeFr14B8qAneGSc_wl0ni--1SRZUXFSovzcqQOkla3W27r'
+                'rLfrD6LXgj_TsDs4vD1PnIm1zcVenKT7TfYI17bsG_O_Wecwz2Nl19pL7gDo'
+                'sNruF3ogJWNq1Lyn_ijPQnkPLpZHyhvuiycYcI3DiQ'
+            ),
+        }
+        assert json.loads(key) == expected
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_rsa_to_jwk_returns_correct_values_for_private_key(self):
+        algo = RSAAlgorithm(RSAAlgorithm.SHA256)
+
+        with open(key_path('testkey_rsa'), 'r') as keyfile:
+            priv_key = algo.prepare_key(keyfile.read())
+
+        key = algo.to_jwk(priv_key)
+
+        expected = {
+            'key_ops': [u'sign'],
+            'kty': 'RSA',
+            'e': 'AQAB',
+            'n': (
+                '1HgzBfJv2cOjQryCwe8NEelriOTNFWKZUivevUrRhlqcmZJdCvuCJRr-xCN-'
+                'OmO8qwgJJR98feNujxVg-J9Ls3_UOA4HcF9nYH6aqVXELAE8Hk_ALvxi96ms'
+                '1DDuAvQGaYZ-lANxlvxeQFOZSbjkz_9mh8aLeGKwqJLp3p-OhUBQpwvAUAPg'
+                '82-OUtgTW3nSljjeFr14B8qAneGSc_wl0ni--1SRZUXFSovzcqQOkla3W27r'
+                'rLfrD6LXgj_TsDs4vD1PnIm1zcVenKT7TfYI17bsG_O_Wecwz2Nl19pL7gDo'
+                'sNruF3ogJWNq1Lyn_ijPQnkPLpZHyhvuiycYcI3DiQ'
+            ),
+            'd': ('rfbs8AWdB1RkLJRlC51LukrAvYl5UfU1TE6XRa4o-DTg2-03OXLNEMyVpMr'
+                  'a47weEnu14StypzC8qXL7vxXOyd30SSFTffLfleaTg-qxgMZSDw-Fb_M-pU'
+                  'HMPMEDYG-lgGma4l4fd1yTX2ATtoUo9BVOQgWS1LMZqi0ASEOkUfzlBgL04'
+                  'UoaLhPSuDdLygdlDzgruVPnec0t1uOEObmrcWIkhwU2CGQzeLtuzX6OVgPh'
+                  'k7xcnjbDurTTVpWH0R0gbZ5ukmQ2P-YuCX8T9iWNMGjPNSkb7h02s2Oe9ZR'
+                  'zP007xQ0VF-Z7xyLuxk6ASmoX1S39ujSbk2WF0eXNPRgFwQ'),
+            'q': ('47hlW2f1ARuWYJf9Dl6MieXjdj2dGx9PL2UH0unVzJYInd56nqXNPrQrc5k'
+                  'ZU65KApC9n9oKUwIxuqwAAbh8oGNEQDqnuTj-powCkdC6bwA8KH1Y-wotpq'
+                  '_GSjxkNzjWRm2GArJSzZc6Fb8EuObOrAavKJ285-zMPCEfus1WZG0'),
+            'p': ('7tr0z929Lp4OHIRJjIKM_rDrWMPtRgnV-51pgWsN6qdpDzns_PgFwrHcoyY'
+                  'sWIO-4yCdVWPxFOgEZ8xXTM_uwOe4VEmdZhw55Tx7axYZtmZYZbO_RIP4CG'
+                  'mlJlOFTiYnxpr-2Cx6kIeQmd-hf7fA3tL018aEzwYMbFMcnAGnEg0'),
+            'qi': ('djo95mB0LVYikNPa-NgyDwLotLqrueb9IviMmn6zKHCwiOXReqXDX9slB8'
+                   'RA15uv56bmN04O__NyVFcgJ2ef169GZHiRFIgIy0Pl8LYkMhCYKKhyqM7g'
+                   'xN-SqGqDTKDC22j00S7jcvCaa1qadn1qbdfukZ4NXv7E2d_LO0Y2Kkc'),
+            'dp': ('tgZ2-tJpEdWxu1m1EzeKa644LHVjpTRptk7H0LDc8i6SieADEuWQvkb9df'
+                   'fpY6tDFaQNQr3fQ6dtdAztmsP7l1b_ynwvT1nDZUcqZvl4ruBgDWFmKbjI'
+                   'lOCt0v9jX6MEPP5xqBx9axdkw18BnGtUuHrbzHSlUX-yh_rumpVH1SE'),
+            'dq': ('xxCIuhD0YlWFbUcwFgGdBWcLIm_WCMGj7SB6aGu1VDTLr4Wu10TFWM0TNu'
+                   'hc9YPker2gpj5qzAmdAzwcfWSSvXpJTYR43jfulBTMoj8-2o3wCM0anclW'
+                   'AuKhin-kc4mh9ssDXRQZwlMymZP0QtaxUDw_nlfVrUCZgO7L1_ZsUTk')
+        }
+        assert json.loads(key) == expected
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_rsa_to_jwk_raises_exception_on_invalid_key(self):
+        algo = RSAAlgorithm(RSAAlgorithm.SHA256)
+
+        with pytest.raises(InvalidKeyError):
+            algo.to_jwk({'not': 'a valid key'})
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_rsa_from_jwk_raises_exception_on_invalid_key(self):
+        algo = RSAAlgorithm(RSAAlgorithm.SHA256)
+
+        with open(key_path('jwk_hmac.json'), 'r') as keyfile:
+            with pytest.raises(InvalidKeyError):
+                algo.from_jwk(keyfile.read())
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_ec_should_reject_non_string_key(self):
+        algo = ECAlgorithm(ECAlgorithm.SHA256)
+
+        with pytest.raises(TypeError):
+            algo.prepare_key(None)
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_ec_should_accept_unicode_key(self):
+        algo = ECAlgorithm(ECAlgorithm.SHA256)
+
+        with open(key_path('testkey_ec'), 'r') as ec_key:
+            algo.prepare_key(force_unicode(ec_key.read()))
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_ec_should_accept_pem_private_key_bytes(self):
+        algo = ECAlgorithm(ECAlgorithm.SHA256)
+
+        with open(key_path('testkey_ec'), 'rb') as ec_key:
+            algo.prepare_key(ec_key.read())
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_ec_should_accept_ssh_public_key_bytes(self):
+        algo = ECAlgorithm(ECAlgorithm.SHA256)
+
+        with open(key_path('testkey_ec_ssh.pub'), 'r') as ec_key:
+            algo.prepare_key(ec_key.read())
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_ec_verify_should_return_false_if_signature_invalid(self):
+        algo = ECAlgorithm(ECAlgorithm.SHA256)
+
+        message = force_bytes('Hello World!')
+
+        # Mess up the signature by replacing a known byte
+        sig = base64.b64decode(force_bytes(
+            'AC+m4Jf/xI3guAC6w0w37t5zRpSCF6F4udEz5LiMiTIjCS4vcVe6dDOxK+M'
+            'mvkF8PxJuvqxP2CO3TR3okDPCl/NjATTO1jE+qBZ966CRQSSzcCM+tzcHzw'
+            'LZS5kbvKu0Acd/K6Ol2/W3B1NeV5F/gjvZn/jOwaLgWEUYsg0o4XVrAg65'.replace('r', 's')))
+
+        with open(key_path('testkey_ec.pub'), 'r') as keyfile:
+            pub_key = algo.prepare_key(keyfile.read())
+
+        result = algo.verify(message, pub_key, sig)
+        assert not result
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_ec_verify_should_return_false_if_signature_wrong_length(self):
+        algo = ECAlgorithm(ECAlgorithm.SHA256)
+
+        message = force_bytes('Hello World!')
+
+        sig = base64.b64decode(force_bytes('AC+m4Jf/xI3guAC6w0w3'))
+
+        with open(key_path('testkey_ec.pub'), 'r') as keyfile:
+            pub_key = algo.prepare_key(keyfile.read())
+
+        result = algo.verify(message, pub_key, sig)
+        assert not result
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_rsa_pss_sign_then_verify_should_return_true(self):
+        algo = RSAPSSAlgorithm(RSAPSSAlgorithm.SHA256)
+
+        message = force_bytes('Hello World!')
+
+        with open(key_path('testkey_rsa'), 'r') as keyfile:
+            priv_key = algo.prepare_key(keyfile.read())
+            sig = algo.sign(message, priv_key)
+
+        with open(key_path('testkey_rsa.pub'), 'r') as keyfile:
+            pub_key = algo.prepare_key(keyfile.read())
+
+        result = algo.verify(message, pub_key, sig)
+        assert result
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_rsa_pss_verify_should_return_false_if_signature_invalid(self):
+        algo = RSAPSSAlgorithm(RSAPSSAlgorithm.SHA256)
+
+        jwt_message = force_bytes('Hello World!')
+
+        jwt_sig = base64.b64decode(force_bytes(
+            'ywKAUGRIDC//6X+tjvZA96yEtMqpOrSppCNfYI7NKyon3P7doud5v65oWNu'
+            'vQsz0fzPGfF7mQFGo9Cm9Vn0nljm4G6PtqZRbz5fXNQBH9k10gq34AtM02c'
+            '/cveqACQ8gF3zxWh6qr9jVqIpeMEaEBIkvqG954E0HT9s9ybHShgHX9mlWk'
+            '186/LopP4xe5c/hxOQjwhv6yDlTiwJFiqjNCvj0GyBKsc4iECLGIIO+4mC4'
+            'daOCWqbpZDuLb1imKpmm8Nsm56kAxijMLZnpCcnPgyb7CqG+B93W9GHglA5'
+            'drUeR1gRtO7vqbZMsCAQ4bpjXxwbYyjQlEVuMl73UL6sOWg=='))
+
+        jwt_sig += force_bytes('123')  # Signature is now invalid
+
+        with open(key_path('testkey_rsa.pub'), 'r') as keyfile:
+            jwt_pub_key = algo.prepare_key(keyfile.read())
+
+        result = algo.verify(jwt_message, jwt_pub_key, jwt_sig)
+        assert not result
+
+
+class TestAlgorithmsRFC7520:
+    """
+    These test vectors were taken from RFC 7520
+    (https://tools.ietf.org/html/rfc7520)
+    """
+
+    def test_hmac_verify_should_return_true_for_test_vector(self):
+        """
+        This test verifies that HMAC verification works with a known good
+        signature and key.
+
+        Reference: https://tools.ietf.org/html/rfc7520#section-4.4
+        """
+        signing_input = force_bytes(
+            'eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LWVlZ'
+            'jMxNGJjNzAzNyJ9.SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ'
+            '29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIG'
+            'lmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmc'
+            'gd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4'
+        )
+
+        signature = base64url_decode(force_bytes(
+            's0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0'
+        ))
+
+        algo = HMACAlgorithm(HMACAlgorithm.SHA256)
+        key = algo.prepare_key(load_hmac_key())
+
+        result = algo.verify(signing_input, key, signature)
+        assert result
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_rsa_verify_should_return_true_for_test_vector(self):
+        """
+        This test verifies that RSA PKCS v1.5 verification works with a known
+        good signature and key.
+
+        Reference: https://tools.ietf.org/html/rfc7520#section-4.1
+        """
+        signing_input = force_bytes(
+            'eyJhbGciOiJSUzI1NiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhb'
+            'XBsZSJ9.SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb'
+            '3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdS'
+            'Bkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcmU'
+            'geW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4'
+        )
+
+        signature = base64url_decode(force_bytes(
+            'MRjdkly7_-oTPTS3AXP41iQIGKa80A0ZmTuV5MEaHoxnW2e5CZ5NlKtainoFmKZop'
+            'dHM1O2U4mwzJdQx996ivp83xuglII7PNDi84wnB-BDkoBwA78185hX-Es4JIwmDLJ'
+            'K3lfWRa-XtL0RnltuYv746iYTh_qHRD68BNt1uSNCrUCTJDt5aAE6x8wW1Kt9eRo4'
+            'QPocSadnHXFxnt8Is9UzpERV0ePPQdLuW3IS_de3xyIrDaLGdjluPxUAhb6L2aXic'
+            '1U12podGU0KLUQSE_oI-ZnmKJ3F4uOZDnd6QZWJushZ41Axf_fcIe8u9ipH84ogor'
+            'ee7vjbU5y18kDquDg'
+        ))
+
+        algo = RSAAlgorithm(RSAAlgorithm.SHA256)
+        key = algo.prepare_key(load_rsa_pub_key())
+
+        result = algo.verify(signing_input, key, signature)
+        assert result
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_rsapss_verify_should_return_true_for_test_vector(self):
+        """
+        This test verifies that RSA-PSS verification works with a known good
+        signature and key.
+
+        Reference: https://tools.ietf.org/html/rfc7520#section-4.2
+        """
+        signing_input = force_bytes(
+            'eyJhbGciOiJQUzM4NCIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhb'
+            'XBsZSJ9.SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb'
+            '3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdS'
+            'Bkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcmU'
+            'geW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4'
+        )
+
+        signature = base64url_decode(force_bytes(
+            'cu22eBqkYDKgIlTpzDXGvaFfz6WGoz7fUDcfT0kkOy42miAh2qyBzk1xEsnk2IpN6'
+            '-tPid6VrklHkqsGqDqHCdP6O8TTB5dDDItllVo6_1OLPpcbUrhiUSMxbbXUvdvWXz'
+            'g-UD8biiReQFlfz28zGWVsdiNAUf8ZnyPEgVFn442ZdNqiVJRmBqrYRXe8P_ijQ7p'
+            '8Vdz0TTrxUeT3lm8d9shnr2lfJT8ImUjvAA2Xez2Mlp8cBE5awDzT0qI0n6uiP1aC'
+            'N_2_jLAeQTlqRHtfa64QQSUmFAAjVKPbByi7xho0uTOcbH510a6GYmJUAfmWjwZ6o'
+            'D4ifKo8DYM-X72Eaw'
+        ))
+
+        algo = RSAPSSAlgorithm(RSAPSSAlgorithm.SHA384)
+        key = algo.prepare_key(load_rsa_pub_key())
+
+        result = algo.verify(signing_input, key, signature)
+        assert result
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_ec_verify_should_return_true_for_test_vector(self):
+        """
+        This test verifies that ECDSA verification works with a known good
+        signature and key.
+
+        Reference: https://tools.ietf.org/html/rfc7520#section-4.3
+        """
+        signing_input = force_bytes(
+            'eyJhbGciOiJFUzUxMiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhb'
+            'XBsZSJ9.SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb'
+            '3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdS'
+            'Bkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcmU'
+            'geW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4'
+        )
+
+        signature = base64url_decode(force_bytes(
+            'AE_R_YZCChjn4791jSQCrdPZCNYqHXCTZH0-JZGYNlaAjP2kqaluUIIUnC9qvbu9P'
+            'lon7KRTzoNEuT4Va2cmL1eJAQy3mtPBu_u_sDDyYjnAMDxXPn7XrT0lw-kvAD890j'
+            'l8e2puQens_IEKBpHABlsbEPX6sFY8OcGDqoRuBomu9xQ2'
+        ))
+
+        algo = ECAlgorithm(ECAlgorithm.SHA512)
+        key = algo.prepare_key(load_ec_pub_key())
+
+        result = algo.verify(signing_input, key, signature)
+        assert result

+ 681 - 0
desktop/core/ext-py/PyJWT-1.7.1/tests/test_api_jws.py

@@ -0,0 +1,681 @@
+
+import json
+from decimal import Decimal
+
+from jwt.algorithms import Algorithm
+from jwt.api_jws import PyJWS
+from jwt.exceptions import (
+    DecodeError, InvalidAlgorithmError, InvalidSignatureError,
+    InvalidTokenError
+)
+from jwt.utils import base64url_decode, force_bytes, force_unicode
+
+import pytest
+
+from .compat import string_types, text_type
+
+try:
+    from cryptography.hazmat.backends import default_backend
+    from cryptography.hazmat.primitives.serialization import (
+        load_pem_private_key, load_pem_public_key, load_ssh_public_key
+    )
+
+    has_crypto = True
+except ImportError:
+    has_crypto = False
+
+
+@pytest.fixture
+def jws():
+    return PyJWS()
+
+
+@pytest.fixture
+def payload():
+    """ Creates a sample jws claimset for use as a payload during tests """
+    return force_bytes('hello world')
+
+
+class TestJWS:
+    def test_register_algo_does_not_allow_duplicate_registration(self, jws):
+        jws.register_algorithm('AAA', Algorithm())
+
+        with pytest.raises(ValueError):
+            jws.register_algorithm('AAA', Algorithm())
+
+    def test_register_algo_rejects_non_algorithm_obj(self, jws):
+        with pytest.raises(TypeError):
+            jws.register_algorithm('AAA123', {})
+
+    def test_unregister_algo_removes_algorithm(self, jws):
+        supported = jws.get_algorithms()
+        assert 'none' in supported
+        assert 'HS256' in supported
+
+        jws.unregister_algorithm('HS256')
+
+        supported = jws.get_algorithms()
+        assert 'HS256' not in supported
+
+    def test_unregister_algo_throws_error_if_not_registered(self, jws):
+        with pytest.raises(KeyError):
+            jws.unregister_algorithm('AAA')
+
+    def test_algo_parameter_removes_alg_from_algorithms_list(self, jws):
+        assert 'none' in jws.get_algorithms()
+        assert 'HS256' in jws.get_algorithms()
+
+        jws = PyJWS(algorithms=['HS256'])
+        assert 'none' not in jws.get_algorithms()
+        assert 'HS256' in jws.get_algorithms()
+
+    def test_override_options(self):
+        jws = PyJWS(options={'verify_signature': False})
+
+        assert not jws.options['verify_signature']
+
+    def test_non_object_options_dont_persist(self, jws, payload):
+        token = jws.encode(payload, 'secret')
+
+        jws.decode(token, 'secret', options={'verify_signature': False})
+
+        assert jws.options['verify_signature']
+
+    def test_options_must_be_dict(self, jws):
+        pytest.raises(TypeError, PyJWS, options=object())
+        pytest.raises(TypeError, PyJWS, options=('something'))
+
+    def test_encode_decode(self, jws, payload):
+        secret = 'secret'
+        jws_message = jws.encode(payload, secret)
+        decoded_payload = jws.decode(jws_message, secret)
+
+        assert decoded_payload == payload
+
+    def test_decode_fails_when_alg_is_not_on_method_algorithms_param(self, jws, payload):
+        secret = 'secret'
+        jws_token = jws.encode(payload, secret, algorithm='HS256')
+        jws.decode(jws_token, secret)
+
+        with pytest.raises(InvalidAlgorithmError):
+            jws.decode(jws_token, secret, algorithms=['HS384'])
+
+    def test_decode_works_with_unicode_token(self, jws):
+        secret = 'secret'
+        unicode_jws = text_type(
+            'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9'
+            '.eyJoZWxsbyI6ICJ3b3JsZCJ9'
+            '.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8')
+
+        jws.decode(unicode_jws, secret)
+
+    def test_decode_missing_segments_throws_exception(self, jws):
+        secret = 'secret'
+        example_jws = ('eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9'
+                       '.eyJoZWxsbyI6ICJ3b3JsZCJ9'
+                       '')  # Missing segment
+
+        with pytest.raises(DecodeError) as context:
+            jws.decode(example_jws, secret)
+
+        exception = context.value
+        assert str(exception) == 'Not enough segments'
+
+    def test_decode_invalid_token_type_is_none(self, jws):
+        example_jws = None
+        example_secret = 'secret'
+
+        with pytest.raises(DecodeError) as context:
+            jws.decode(example_jws, example_secret)
+
+        exception = context.value
+        assert 'Invalid token type' in str(exception)
+
+    def test_decode_invalid_token_type_is_int(self, jws):
+        example_jws = 123
+        example_secret = 'secret'
+
+        with pytest.raises(DecodeError) as context:
+            jws.decode(example_jws, example_secret)
+
+        exception = context.value
+        assert 'Invalid token type' in str(exception)
+
+    def test_decode_with_non_mapping_header_throws_exception(self, jws):
+        secret = 'secret'
+        example_jws = ('MQ'  # == 1
+                       '.eyJoZWxsbyI6ICJ3b3JsZCJ9'
+                       '.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8')
+
+        with pytest.raises(DecodeError) as context:
+            jws.decode(example_jws, secret)
+
+        exception = context.value
+        assert str(exception) == 'Invalid header string: must be a json object'
+
+    def test_encode_algorithm_param_should_be_case_sensitive(self, jws, payload):
+
+        jws.encode(payload, 'secret', algorithm='HS256')
+
+        with pytest.raises(NotImplementedError) as context:
+            jws.encode(payload, None, algorithm='hs256')
+
+        exception = context.value
+        assert str(exception) == 'Algorithm not supported'
+
+    def test_decode_algorithm_param_should_be_case_sensitive(self, jws):
+        example_jws = ('eyJhbGciOiJoczI1NiIsInR5cCI6IkpXVCJ9'  # alg = hs256
+                       '.eyJoZWxsbyI6IndvcmxkIn0'
+                       '.5R_FEPE7SW2dT9GgIxPgZATjFGXfUDOSwo7TtO_Kd_g')
+
+        with pytest.raises(InvalidAlgorithmError) as context:
+            jws.decode(example_jws, 'secret')
+
+        exception = context.value
+        assert str(exception) == 'Algorithm not supported'
+
+    def test_bad_secret(self, jws, payload):
+        right_secret = 'foo'
+        bad_secret = 'bar'
+        jws_message = jws.encode(payload, right_secret)
+
+        with pytest.raises(DecodeError) as excinfo:
+            # Backward compat for ticket #315
+            jws.decode(jws_message, bad_secret)
+        assert 'Signature verification failed' == str(excinfo.value)
+
+        with pytest.raises(InvalidSignatureError) as excinfo:
+            jws.decode(jws_message, bad_secret)
+        assert 'Signature verification failed' == str(excinfo.value)
+
+    def test_decodes_valid_jws(self, jws, payload):
+        example_secret = 'secret'
+        example_jws = (
+            b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.'
+            b'aGVsbG8gd29ybGQ.'
+            b'gEW0pdU4kxPthjtehYdhxB9mMOGajt1xCKlGGXDJ8PM')
+
+        decoded_payload = jws.decode(example_jws, example_secret)
+
+        assert decoded_payload == payload
+
+    # 'Control' Elliptic Curve jws created by another library.
+    # Used to test for regressions that could affect both
+    # encoding / decoding operations equally (causing tests
+    # to still pass).
+    @pytest.mark.skipif(not has_crypto, reason="Can't run without cryptography library")
+    def test_decodes_valid_es384_jws(self, jws):
+        example_payload = {'hello': 'world'}
+        with open('tests/keys/testkey_ec.pub', 'r') as fp:
+            example_pubkey = fp.read()
+        example_jws = (
+            b'eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9'
+            b'.eyJoZWxsbyI6IndvcmxkIn0'
+            b'.AGtlemKghaIaYh1yeeekFH9fRuNY7hCaw5hUgZ5aG1N'
+            b'2F8FIbiKLaZKr8SiFdTimXFVTEmxpBQ9sRmdsDsnrM-1'
+            b'HAG0_zxxu0JyINOFT2iqF3URYl9HZ8kZWMeZAtXmn6Cw'
+            b'PXRJD2f7N-f7bJ5JeL9VT5beI2XD3FlK3GgRvI-eE-2Ik')
+        decoded_payload = jws.decode(example_jws, example_pubkey)
+        json_payload = json.loads(force_unicode(decoded_payload))
+
+        assert json_payload == example_payload
+
+    # 'Control' RSA jws created by another library.
+    # Used to test for regressions that could affect both
+    # encoding / decoding operations equally (causing tests
+    # to still pass).
+    @pytest.mark.skipif(not has_crypto, reason="Can't run without cryptography library")
+    def test_decodes_valid_rs384_jws(self, jws):
+        example_payload = {'hello': 'world'}
+        with open('tests/keys/testkey_rsa.pub', 'r') as fp:
+            example_pubkey = fp.read()
+        example_jws = (
+            b'eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9'
+            b'.eyJoZWxsbyI6IndvcmxkIn0'
+            b'.yNQ3nI9vEDs7lEh-Cp81McPuiQ4ZRv6FL4evTYYAh1X'
+            b'lRTTR3Cz8pPA9Stgso8Ra9xGB4X3rlra1c8Jz10nTUju'
+            b'O06OMm7oXdrnxp1KIiAJDerWHkQ7l3dlizIk1bmMA457'
+            b'W2fNzNfHViuED5ISM081dgf_a71qBwJ_yShMMrSOfxDx'
+            b'mX9c4DjRogRJG8SM5PvpLqI_Cm9iQPGMvmYK7gzcq2cJ'
+            b'urHRJDJHTqIdpLWXkY7zVikeen6FhuGyn060Dz9gYq9t'
+            b'uwmrtSWCBUjiN8sqJ00CDgycxKqHfUndZbEAOjcCAhBr'
+            b'qWW3mSVivUfubsYbwUdUG3fSRPjaUPcpe8A')
+        decoded_payload = jws.decode(example_jws, example_pubkey)
+        json_payload = json.loads(force_unicode(decoded_payload))
+
+        assert json_payload == example_payload
+
+    def test_load_verify_valid_jws(self, jws, payload):
+        example_secret = 'secret'
+        example_jws = (
+            b'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.'
+            b'aGVsbG8gd29ybGQ.'
+            b'SIr03zM64awWRdPrAM_61QWsZchAtgDV3pphfHPPWkI'
+        )
+
+        decoded_payload = jws.decode(example_jws, key=example_secret)
+        assert decoded_payload == payload
+
+    def test_allow_skip_verification(self, jws, payload):
+        right_secret = 'foo'
+        jws_message = jws.encode(payload, right_secret)
+        decoded_payload = jws.decode(jws_message, verify=False)
+
+        assert decoded_payload == payload
+
+    def test_verify_false_deprecated(self, jws, recwarn):
+        example_jws = (
+            b'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9'
+            b'.eyJoZWxsbyI6ICJ3b3JsZCJ9'
+            b'.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8')
+
+        pytest.deprecated_call(jws.decode, example_jws, verify=False)
+
+    def test_decode_with_optional_algorithms(self, jws):
+        example_secret = 'secret'
+        example_jws = (
+            b'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.'
+            b'aGVsbG8gd29ybGQ.'
+            b'SIr03zM64awWRdPrAM_61QWsZchAtgDV3pphfHPPWkI'
+        )
+
+        pytest.deprecated_call(jws.decode, example_jws, key=example_secret)
+
+    def test_decode_no_algorithms_verify_signature_false(self, jws):
+        example_secret = 'secret'
+        example_jws = (
+            b'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.'
+            b'aGVsbG8gd29ybGQ.'
+            b'SIr03zM64awWRdPrAM_61QWsZchAtgDV3pphfHPPWkI'
+        )
+
+        try:
+            pytest.deprecated_call(
+                jws.decode, example_jws, key=example_secret,
+                options={'verify_signature': False},
+            )
+        except pytest.fail.Exception:
+            pass
+        else:
+            assert False, "Unexpected DeprecationWarning raised."
+
+    def test_load_no_verification(self, jws, payload):
+        right_secret = 'foo'
+        jws_message = jws.encode(payload, right_secret)
+
+        decoded_payload = jws.decode(jws_message, key=None, verify=False)
+
+        assert decoded_payload == payload
+
+    def test_no_secret(self, jws, payload):
+        right_secret = 'foo'
+        jws_message = jws.encode(payload, right_secret)
+
+        with pytest.raises(DecodeError):
+            jws.decode(jws_message)
+
+    def test_verify_signature_with_no_secret(self, jws, payload):
+        right_secret = 'foo'
+        jws_message = jws.encode(payload, right_secret)
+
+        with pytest.raises(DecodeError) as exc:
+            jws.decode(jws_message)
+
+        assert 'Signature verification' in str(exc.value)
+
+    def test_verify_signature_with_no_algo_header_throws_exception(self, jws, payload):
+        example_jws = (
+            b'e30'
+            b'.eyJhIjo1fQ'
+            b'.KEh186CjVw_Q8FadjJcaVnE7hO5Z9nHBbU8TgbhHcBY'
+        )
+
+        with pytest.raises(InvalidAlgorithmError):
+            jws.decode(example_jws, 'secret')
+
+    def test_invalid_crypto_alg(self, jws, payload):
+        with pytest.raises(NotImplementedError):
+            jws.encode(payload, 'secret', algorithm='HS1024')
+
+    @pytest.mark.skipif(has_crypto, reason='Scenario requires cryptography to not be installed')
+    def test_missing_crypto_library_better_error_messages(self, jws, payload):
+        with pytest.raises(NotImplementedError) as excinfo:
+            jws.encode(payload, 'secret', algorithm='RS256')
+            assert 'cryptography' in str(excinfo.value)
+
+    def test_unicode_secret(self, jws, payload):
+        secret = '\xc2'
+        jws_message = jws.encode(payload, secret)
+        decoded_payload = jws.decode(jws_message, secret)
+
+        assert decoded_payload == payload
+
+    def test_nonascii_secret(self, jws, payload):
+        secret = '\xc2'  # char value that ascii codec cannot decode
+        jws_message = jws.encode(payload, secret)
+
+        decoded_payload = jws.decode(jws_message, secret)
+
+        assert decoded_payload == payload
+
+    def test_bytes_secret(self, jws, payload):
+        secret = b'\xc2'  # char value that ascii codec cannot decode
+        jws_message = jws.encode(payload, secret)
+
+        decoded_payload = jws.decode(jws_message, secret)
+
+        assert decoded_payload == payload
+
+    def test_decode_invalid_header_padding(self, jws):
+        example_jws = (
+            'aeyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9'
+            '.eyJoZWxsbyI6ICJ3b3JsZCJ9'
+            '.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8')
+        example_secret = 'secret'
+
+        with pytest.raises(DecodeError) as exc:
+            jws.decode(example_jws, example_secret)
+
+        assert 'header padding' in str(exc.value)
+
+    def test_decode_invalid_header_string(self, jws):
+        example_jws = (
+            'eyJhbGciOiAiSFMyNTbpIiwgInR5cCI6ICJKV1QifQ=='
+            '.eyJoZWxsbyI6ICJ3b3JsZCJ9'
+            '.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8')
+        example_secret = 'secret'
+
+        with pytest.raises(DecodeError) as exc:
+            jws.decode(example_jws, example_secret)
+
+        assert 'Invalid header' in str(exc.value)
+
+    def test_decode_invalid_payload_padding(self, jws):
+        example_jws = (
+            'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9'
+            '.aeyJoZWxsbyI6ICJ3b3JsZCJ9'
+            '.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8')
+        example_secret = 'secret'
+
+        with pytest.raises(DecodeError) as exc:
+            jws.decode(example_jws, example_secret)
+
+        assert 'Invalid payload padding' in str(exc.value)
+
+    def test_decode_invalid_crypto_padding(self, jws):
+        example_jws = (
+            'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9'
+            '.eyJoZWxsbyI6ICJ3b3JsZCJ9'
+            '.aatvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8')
+        example_secret = 'secret'
+
+        with pytest.raises(DecodeError) as exc:
+            jws.decode(example_jws, example_secret)
+
+        assert 'Invalid crypto padding' in str(exc.value)
+
+    def test_decode_with_algo_none_should_fail(self, jws, payload):
+        jws_message = jws.encode(payload, key=None, algorithm=None)
+
+        with pytest.raises(DecodeError):
+            jws.decode(jws_message)
+
+    def test_decode_with_algo_none_and_verify_false_should_pass(self, jws, payload):
+        jws_message = jws.encode(payload, key=None, algorithm=None)
+        jws.decode(jws_message, verify=False)
+
+    def test_get_unverified_header_returns_header_values(self, jws, payload):
+        jws_message = jws.encode(payload, key='secret', algorithm='HS256',
+                                 headers={'kid': 'toomanysecrets'})
+
+        header = jws.get_unverified_header(jws_message)
+
+        assert 'kid' in header
+        assert header['kid'] == 'toomanysecrets'
+
+    def test_get_unverified_header_fails_on_bad_header_types(self, jws, payload):
+        # Contains a bad kid value (int 123 instead of string)
+        example_jws = (
+            'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6MTIzfQ'
+            '.eyJzdWIiOiIxMjM0NTY3ODkwIn0'
+            '.vs2WY54jfpKP3JGC73Vq5YlMsqM5oTZ1ZydT77SiZSk')
+
+        with pytest.raises(InvalidTokenError) as exc:
+            jws.get_unverified_header(example_jws)
+
+        assert 'Key ID header parameter must be a string' == str(exc.value)
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_encode_decode_with_rsa_sha256(self, jws, payload):
+        # PEM-formatted RSA key
+        with open('tests/keys/testkey_rsa', 'r') as rsa_priv_file:
+            priv_rsakey = load_pem_private_key(force_bytes(rsa_priv_file.read()),
+                                               password=None, backend=default_backend())
+            jws_message = jws.encode(payload, priv_rsakey, algorithm='RS256')
+
+        with open('tests/keys/testkey_rsa.pub', 'r') as rsa_pub_file:
+            pub_rsakey = load_ssh_public_key(force_bytes(rsa_pub_file.read()),
+                                             backend=default_backend())
+
+            jws.decode(jws_message, pub_rsakey)
+
+        # string-formatted key
+        with open('tests/keys/testkey_rsa', 'r') as rsa_priv_file:
+            priv_rsakey = rsa_priv_file.read()
+            jws_message = jws.encode(payload, priv_rsakey, algorithm='RS256')
+
+        with open('tests/keys/testkey_rsa.pub', 'r') as rsa_pub_file:
+            pub_rsakey = rsa_pub_file.read()
+            jws.decode(jws_message, pub_rsakey)
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_encode_decode_with_rsa_sha384(self, jws, payload):
+        # PEM-formatted RSA key
+        with open('tests/keys/testkey_rsa', 'r') as rsa_priv_file:
+            priv_rsakey = load_pem_private_key(force_bytes(rsa_priv_file.read()),
+                                               password=None, backend=default_backend())
+            jws_message = jws.encode(payload, priv_rsakey, algorithm='RS384')
+
+        with open('tests/keys/testkey_rsa.pub', 'r') as rsa_pub_file:
+            pub_rsakey = load_ssh_public_key(force_bytes(rsa_pub_file.read()),
+                                             backend=default_backend())
+            jws.decode(jws_message, pub_rsakey)
+
+        # string-formatted key
+        with open('tests/keys/testkey_rsa', 'r') as rsa_priv_file:
+            priv_rsakey = rsa_priv_file.read()
+            jws_message = jws.encode(payload, priv_rsakey, algorithm='RS384')
+
+        with open('tests/keys/testkey_rsa.pub', 'r') as rsa_pub_file:
+            pub_rsakey = rsa_pub_file.read()
+            jws.decode(jws_message, pub_rsakey)
+
+    @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library')
+    def test_encode_decode_with_rsa_sha512(self, jws, payload):
+        # PEM-formatted RSA key
+        with open('tests/keys/testkey_rsa', 'r') as rsa_priv_file:
+            priv_rsakey = load_pem_private_key(force_bytes(rsa_priv_file.read()),
+                                               password=None, backend=default_backend())
+            jws_message = jws.encode(payload, priv_rsakey, algorithm='RS512')
+
+        with open('tests/keys/testkey_rsa.pub', 'r') as rsa_pub_file:
+            pub_rsakey = load_ssh_public_key(force_bytes(rsa_pub_file.read()),
+                                             backend=default_backend())
+            jws.decode(jws_message, pub_rsakey)
+
+        # string-formatted key
+        with open('tests/keys/testkey_rsa', 'r') as rsa_priv_file:
+            priv_rsakey = rsa_priv_file.read()
+            jws_message = jws.encode(payload, priv_rsakey, algorithm='RS512')
+
+        with open('tests/keys/testkey_rsa.pub', 'r') as rsa_pub_file:
+            pub_rsakey = rsa_pub_file.read()
+            jws.decode(jws_message, pub_rsakey)
+
+    def test_rsa_related_algorithms(self, jws):
+        jws = PyJWS()
+        jws_algorithms = jws.get_algorithms()
+
+        if has_crypto:
+            assert 'RS256' in jws_algorithms
+            assert 'RS384' in jws_algorithms
+            assert 'RS512' in jws_algorithms
+            assert 'PS256' in jws_algorithms
+            assert 'PS384' in jws_algorithms
+            assert 'PS512' in jws_algorithms
+
+        else:
+            assert 'RS256' not in jws_algorithms
+            assert 'RS384' not in jws_algorithms
+            assert 'RS512' not in jws_algorithms
+            assert 'PS256' not in jws_algorithms
+            assert 'PS384' not in jws_algorithms
+            assert 'PS512' not in jws_algorithms
+
+    @pytest.mark.skipif(not has_crypto, reason="Can't run without cryptography library")
+    def test_encode_decode_with_ecdsa_sha256(self, jws, payload):
+        # PEM-formatted EC key
+        with open('tests/keys/testkey_ec', 'r') as ec_priv_file:
+            priv_eckey = load_pem_private_key(force_bytes(ec_priv_file.read()),
+                                              password=None, backend=default_backend())
+            jws_message = jws.encode(payload, priv_eckey, algorithm='ES256')
+
+        with open('tests/keys/testkey_ec.pub', 'r') as ec_pub_file:
+            pub_eckey = load_pem_public_key(force_bytes(ec_pub_file.read()),
+                                            backend=default_backend())
+            jws.decode(jws_message, pub_eckey)
+
+        # string-formatted key
+        with open('tests/keys/testkey_ec', 'r') as ec_priv_file:
+            priv_eckey = ec_priv_file.read()
+            jws_message = jws.encode(payload, priv_eckey, algorithm='ES256')
+
+        with open('tests/keys/testkey_ec.pub', 'r') as ec_pub_file:
+            pub_eckey = ec_pub_file.read()
+            jws.decode(jws_message, pub_eckey)
+
+    @pytest.mark.skipif(not has_crypto, reason="Can't run without cryptography library")
+    def test_encode_decode_with_ecdsa_sha384(self, jws, payload):
+
+        # PEM-formatted EC key
+        with open('tests/keys/testkey_ec', 'r') as ec_priv_file:
+            priv_eckey = load_pem_private_key(force_bytes(ec_priv_file.read()),
+                                              password=None, backend=default_backend())
+            jws_message = jws.encode(payload, priv_eckey, algorithm='ES384')
+
+        with open('tests/keys/testkey_ec.pub', 'r') as ec_pub_file:
+            pub_eckey = load_pem_public_key(force_bytes(ec_pub_file.read()),
+                                            backend=default_backend())
+            jws.decode(jws_message, pub_eckey)
+
+        # string-formatted key
+        with open('tests/keys/testkey_ec', 'r') as ec_priv_file:
+            priv_eckey = ec_priv_file.read()
+            jws_message = jws.encode(payload, priv_eckey, algorithm='ES384')
+
+        with open('tests/keys/testkey_ec.pub', 'r') as ec_pub_file:
+            pub_eckey = ec_pub_file.read()
+            jws.decode(jws_message, pub_eckey)
+
+    @pytest.mark.skipif(not has_crypto, reason="Can't run without cryptography library")
+    def test_encode_decode_with_ecdsa_sha512(self, jws, payload):
+        # PEM-formatted EC key
+        with open('tests/keys/testkey_ec', 'r') as ec_priv_file:
+            priv_eckey = load_pem_private_key(force_bytes(ec_priv_file.read()),
+                                              password=None, backend=default_backend())
+            jws_message = jws.encode(payload, priv_eckey, algorithm='ES521')
+
+        with open('tests/keys/testkey_ec.pub', 'r') as ec_pub_file:
+            pub_eckey = load_pem_public_key(force_bytes(ec_pub_file.read()), backend=default_backend())
+            jws.decode(jws_message, pub_eckey)
+
+        # string-formatted key
+        with open('tests/keys/testkey_ec', 'r') as ec_priv_file:
+            priv_eckey = ec_priv_file.read()
+            jws_message = jws.encode(payload, priv_eckey, algorithm='ES521')
+
+        with open('tests/keys/testkey_ec.pub', 'r') as ec_pub_file:
+            pub_eckey = ec_pub_file.read()
+            jws.decode(jws_message, pub_eckey)
+
+    def test_ecdsa_related_algorithms(self, jws):
+        jws = PyJWS()
+        jws_algorithms = jws.get_algorithms()
+
+        if has_crypto:
+            assert 'ES256' in jws_algorithms
+            assert 'ES384' in jws_algorithms
+            assert 'ES521' in jws_algorithms
+        else:
+            assert 'ES256' not in jws_algorithms
+            assert 'ES384' not in jws_algorithms
+            assert 'ES521' not in jws_algorithms
+
+    def test_skip_check_signature(self, jws):
+        token = ("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
+                 ".eyJzb21lIjoicGF5bG9hZCJ9"
+                 ".4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZA")
+        jws.decode(token, 'secret', options={'verify_signature': False})
+
+    def test_decode_options_must_be_dict(self, jws, payload):
+        token = jws.encode(payload, 'secret')
+
+        with pytest.raises(TypeError):
+            jws.decode(token, 'secret', options=object())
+
+        with pytest.raises(TypeError):
+            jws.decode(token, 'secret', options='something')
+
+    def test_custom_json_encoder(self, jws, payload):
+
+        class CustomJSONEncoder(json.JSONEncoder):
+
+            def default(self, o):
+                if isinstance(o, Decimal):
+                    return 'it worked'
+                return super(CustomJSONEncoder, self).default(o)
+
+        data = {
+            'some_decimal': Decimal('2.2')
+        }
+
+        with pytest.raises(TypeError):
+            jws.encode(payload, 'secret', headers=data)
+
+        token = jws.encode(payload, 'secret', headers=data,
+                           json_encoder=CustomJSONEncoder)
+
+        header = force_bytes(force_unicode(token).split('.')[0])
+        header = json.loads(force_unicode(base64url_decode(header)))
+
+        assert 'some_decimal' in header
+        assert header['some_decimal'] == 'it worked'
+
+    def test_encode_headers_parameter_adds_headers(self, jws, payload):
+        headers = {'testheader': True}
+        token = jws.encode(payload, 'secret', headers=headers)
+
+        if not isinstance(token, string_types):
+            token = token.decode()
+
+        header = token[0:token.index('.')].encode()
+        header = base64url_decode(header)
+
+        if not isinstance(header, text_type):
+            header = header.decode()
+
+        header_obj = json.loads(header)
+
+        assert 'testheader' in header_obj
+        assert header_obj['testheader'] == headers['testheader']
+
+    def test_encode_fails_on_invalid_kid_types(self, jws, payload):
+        with pytest.raises(InvalidTokenError) as exc:
+            jws.encode(payload, 'secret', headers={'kid': 123})
+
+        assert 'Key ID header parameter must be a string' == str(exc.value)
+
+        with pytest.raises(InvalidTokenError) as exc:
+            jws.encode(payload, 'secret', headers={'kid': None})
+
+        assert 'Key ID header parameter must be a string' == str(exc.value)

+ 522 - 0
desktop/core/ext-py/PyJWT-1.7.1/tests/test_api_jwt.py

@@ -0,0 +1,522 @@
+
+import json
+import time
+from calendar import timegm
+from datetime import datetime, timedelta
+from decimal import Decimal
+
+from jwt.api_jwt import PyJWT
+from jwt.exceptions import (
+    DecodeError, ExpiredSignatureError, ImmatureSignatureError,
+    InvalidAudienceError, InvalidIssuedAtError, InvalidIssuerError,
+    MissingRequiredClaimError
+)
+
+import pytest
+
+from .test_api_jws import has_crypto
+from .utils import utc_timestamp
+
+
+@pytest.fixture
+def jwt():
+    return PyJWT()
+
+
+@pytest.fixture
+def payload():
+    """ Creates a sample JWT claimset for use as a payload during tests """
+    return {
+        'iss': 'jeff',
+        'exp': utc_timestamp() + 15,
+        'claim': 'insanity'
+    }
+
+
+class TestJWT:
+    def test_decodes_valid_jwt(self, jwt):
+        example_payload = {'hello': 'world'}
+        example_secret = 'secret'
+        example_jwt = (
+            b'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9'
+            b'.eyJoZWxsbyI6ICJ3b3JsZCJ9'
+            b'.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8')
+        decoded_payload = jwt.decode(example_jwt, example_secret)
+
+        assert decoded_payload == example_payload
+
+    def test_load_verify_valid_jwt(self, jwt):
+        example_payload = {'hello': 'world'}
+        example_secret = 'secret'
+        example_jwt = (
+            b'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9'
+            b'.eyJoZWxsbyI6ICJ3b3JsZCJ9'
+            b'.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8')
+
+        decoded_payload = jwt.decode(example_jwt, key=example_secret)
+
+        assert decoded_payload == example_payload
+
+    def test_decode_invalid_payload_string(self, jwt):
+        example_jwt = (
+            'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.aGVsb'
+            'G8gd29ybGQ.SIr03zM64awWRdPrAM_61QWsZchAtgDV'
+            '3pphfHPPWkI')
+        example_secret = 'secret'
+
+        with pytest.raises(DecodeError) as exc:
+            jwt.decode(example_jwt, example_secret)
+
+        assert 'Invalid payload string' in str(exc.value)
+
+    def test_decode_with_non_mapping_payload_throws_exception(self, jwt):
+        secret = 'secret'
+        example_jwt = ('eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.'
+                       'MQ.'  # == 1
+                       'AbcSR3DWum91KOgfKxUHm78rLs_DrrZ1CrDgpUFFzls')
+
+        with pytest.raises(DecodeError) as context:
+            jwt.decode(example_jwt, secret)
+
+        exception = context.value
+        assert str(exception) == 'Invalid payload string: must be a json object'
+
+    def test_decode_with_invalid_audience_param_throws_exception(self, jwt):
+        secret = 'secret'
+        example_jwt = ('eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9'
+                       '.eyJoZWxsbyI6ICJ3b3JsZCJ9'
+                       '.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8')
+
+        with pytest.raises(TypeError) as context:
+            jwt.decode(example_jwt, secret, audience=1)
+
+        exception = context.value
+        assert str(exception) == 'audience must be a string, iterable, or None'
+
+    def test_decode_with_nonlist_aud_claim_throws_exception(self, jwt):
+        secret = 'secret'
+        example_jwt = ('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9'
+                       '.eyJoZWxsbyI6IndvcmxkIiwiYXVkIjoxfQ'  # aud = 1
+                       '.Rof08LBSwbm8Z_bhA2N3DFY-utZR1Gi9rbIS5Zthnnc')
+
+        with pytest.raises(InvalidAudienceError) as context:
+            jwt.decode(example_jwt, secret, audience='my_audience')
+
+        exception = context.value
+        assert str(exception) == 'Invalid claim format in token'
+
+    def test_decode_with_invalid_aud_list_member_throws_exception(self, jwt):
+        secret = 'secret'
+        example_jwt = ('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9'
+                       '.eyJoZWxsbyI6IndvcmxkIiwiYXVkIjpbMV19'
+                       '.iQgKpJ8shetwNMIosNXWBPFB057c2BHs-8t1d2CCM2A')
+
+        with pytest.raises(InvalidAudienceError) as context:
+            jwt.decode(example_jwt, secret, audience='my_audience')
+
+        exception = context.value
+        assert str(exception) == 'Invalid claim format in token'
+
+    def test_encode_bad_type(self, jwt):
+
+        types = ['string', tuple(), list(), 42, set()]
+
+        for t in types:
+            pytest.raises(TypeError, lambda: jwt.encode(t, 'secret'))
+
+    def test_decode_raises_exception_if_exp_is_not_int(self, jwt):
+        # >>> jwt.encode({'exp': 'not-an-int'}, 'secret')
+        example_jwt = ('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.'
+                       'eyJleHAiOiJub3QtYW4taW50In0.'
+                       'P65iYgoHtBqB07PMtBSuKNUEIPPPfmjfJG217cEE66s')
+
+        with pytest.raises(DecodeError) as exc:
+            jwt.decode(example_jwt, 'secret')
+
+        assert 'exp' in str(exc.value)
+
+    def test_decode_raises_exception_if_iat_is_not_int(self, jwt):
+        # >>> jwt.encode({'iat': 'not-an-int'}, 'secret')
+        example_jwt = ('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.'
+                       'eyJpYXQiOiJub3QtYW4taW50In0.'
+                       'H1GmcQgSySa5LOKYbzGm--b1OmRbHFkyk8pq811FzZM')
+
+        with pytest.raises(InvalidIssuedAtError):
+            jwt.decode(example_jwt, 'secret')
+
+    def test_decode_raises_exception_if_nbf_is_not_int(self, jwt):
+        # >>> jwt.encode({'nbf': 'not-an-int'}, 'secret')
+        example_jwt = ('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.'
+                       'eyJuYmYiOiJub3QtYW4taW50In0.'
+                       'c25hldC8G2ZamC8uKpax9sYMTgdZo3cxrmzFHaAAluw')
+
+        with pytest.raises(DecodeError):
+            jwt.decode(example_jwt, 'secret')
+
+    def test_encode_datetime(self, jwt):
+        secret = 'secret'
+        current_datetime = datetime.utcnow()
+        payload = {
+            'exp': current_datetime,
+            'iat': current_datetime,
+            'nbf': current_datetime
+        }
+        jwt_message = jwt.encode(payload, secret)
+        decoded_payload = jwt.decode(jwt_message, secret, leeway=1)
+
+        assert (decoded_payload['exp'] ==
+                timegm(current_datetime.utctimetuple()))
+        assert (decoded_payload['iat'] ==
+                timegm(current_datetime.utctimetuple()))
+        assert (decoded_payload['nbf'] ==
+                timegm(current_datetime.utctimetuple()))
+
+    # 'Control' Elliptic Curve JWT created by another library.
+    # Used to test for regressions that could affect both
+    # encoding / decoding operations equally (causing tests
+    # to still pass).
+    @pytest.mark.skipif(not has_crypto, reason="Can't run without cryptography library")
+    def test_decodes_valid_es384_jwt(self, jwt):
+        example_payload = {'hello': 'world'}
+        with open('tests/keys/testkey_ec.pub', 'r') as fp:
+            example_pubkey = fp.read()
+        example_jwt = (
+            b'eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9'
+            b'.eyJoZWxsbyI6IndvcmxkIn0'
+            b'.AddMgkmRhzqptDYqlmy_f2dzM6O9YZmVo-txs_CeAJD'
+            b'NoD8LN7YiPeLmtIhkO5_VZeHHKvtQcGc4lsq-Y72c4dK'
+            b'pANr1f6HEYhjpBc03u_bv06PYMcr5N2-9k97-qf-JCSb'
+            b'zqW6R250Q7gNCX5R7NrCl7MTM4DTBZkGbUlqsFUleiGlj')
+        decoded_payload = jwt.decode(example_jwt, example_pubkey)
+
+        assert decoded_payload == example_payload
+
+    # 'Control' RSA JWT created by another library.
+    # Used to test for regressions that could affect both
+    # encoding / decoding operations equally (causing tests
+    # to still pass).
+    @pytest.mark.skipif(not has_crypto, reason="Can't run without cryptography library")
+    def test_decodes_valid_rs384_jwt(self, jwt):
+        example_payload = {'hello': 'world'}
+        with open('tests/keys/testkey_rsa.pub', 'r') as fp:
+            example_pubkey = fp.read()
+        example_jwt = (
+            b'eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9'
+            b'.eyJoZWxsbyI6IndvcmxkIn0'
+            b'.yNQ3nI9vEDs7lEh-Cp81McPuiQ4ZRv6FL4evTYYAh1X'
+            b'lRTTR3Cz8pPA9Stgso8Ra9xGB4X3rlra1c8Jz10nTUju'
+            b'O06OMm7oXdrnxp1KIiAJDerWHkQ7l3dlizIk1bmMA457'
+            b'W2fNzNfHViuED5ISM081dgf_a71qBwJ_yShMMrSOfxDx'
+            b'mX9c4DjRogRJG8SM5PvpLqI_Cm9iQPGMvmYK7gzcq2cJ'
+            b'urHRJDJHTqIdpLWXkY7zVikeen6FhuGyn060Dz9gYq9t'
+            b'uwmrtSWCBUjiN8sqJ00CDgycxKqHfUndZbEAOjcCAhBr'
+            b'qWW3mSVivUfubsYbwUdUG3fSRPjaUPcpe8A')
+        decoded_payload = jwt.decode(example_jwt, example_pubkey)
+
+        assert decoded_payload == example_payload
+
+    def test_decode_with_expiration(self, jwt, payload):
+        payload['exp'] = utc_timestamp() - 1
+        secret = 'secret'
+        jwt_message = jwt.encode(payload, secret)
+
+        with pytest.raises(ExpiredSignatureError):
+            jwt.decode(jwt_message, secret)
+
+    def test_decode_with_notbefore(self, jwt, payload):
+        payload['nbf'] = utc_timestamp() + 10
+        secret = 'secret'
+        jwt_message = jwt.encode(payload, secret)
+
+        with pytest.raises(ImmatureSignatureError):
+            jwt.decode(jwt_message, secret)
+
+    def test_decode_skip_expiration_verification(self, jwt, payload):
+        payload['exp'] = time.time() - 1
+        secret = 'secret'
+        jwt_message = jwt.encode(payload, secret)
+
+        jwt.decode(jwt_message, secret, options={'verify_exp': False})
+
+    def test_decode_skip_notbefore_verification(self, jwt, payload):
+        payload['nbf'] = time.time() + 10
+        secret = 'secret'
+        jwt_message = jwt.encode(payload, secret)
+
+        jwt.decode(jwt_message, secret, options={'verify_nbf': False})
+
+    def test_decode_with_expiration_with_leeway(self, jwt, payload):
+        payload['exp'] = utc_timestamp() - 2
+        secret = 'secret'
+        jwt_message = jwt.encode(payload, secret)
+
+        decoded_payload, signing, header, signature = jwt._load(jwt_message)
+
+        # With 3 seconds leeway, should be ok
+        for leeway in (3, timedelta(seconds=3)):
+            jwt.decode(jwt_message, secret, leeway=leeway)
+
+        # With 1 seconds, should fail
+        for leeway in (1, timedelta(seconds=1)):
+            with pytest.raises(ExpiredSignatureError):
+                jwt.decode(jwt_message, secret, leeway=leeway)
+
+    def test_decode_with_notbefore_with_leeway(self, jwt, payload):
+        payload['nbf'] = utc_timestamp() + 10
+        secret = 'secret'
+        jwt_message = jwt.encode(payload, secret)
+
+        # With 13 seconds leeway, should be ok
+        jwt.decode(jwt_message, secret, leeway=13)
+
+        with pytest.raises(ImmatureSignatureError):
+            jwt.decode(jwt_message, secret, leeway=1)
+
+    def test_check_audience_when_valid(self, jwt):
+        payload = {
+            'some': 'payload',
+            'aud': 'urn:me'
+        }
+        token = jwt.encode(payload, 'secret')
+        jwt.decode(token, 'secret', audience='urn:me')
+
+    def test_check_audience_list_when_valid(self, jwt):
+        payload = {
+            'some': 'payload',
+            'aud': 'urn:me'
+        }
+        token = jwt.encode(payload, 'secret')
+        jwt.decode(token, 'secret', audience=['urn:you', 'urn:me'])
+
+    def test_check_audience_none_specified(self, jwt):
+        payload = {
+            'some': 'payload',
+            'aud': 'urn:me'
+        }
+        token = jwt.encode(payload, 'secret')
+        with pytest.raises(InvalidAudienceError):
+            jwt.decode(token, 'secret')
+
+    def test_raise_exception_invalid_audience_list(self, jwt):
+        payload = {
+            'some': 'payload',
+            'aud': 'urn:me'
+        }
+        token = jwt.encode(payload, 'secret')
+        with pytest.raises(InvalidAudienceError):
+            jwt.decode(token, 'secret', audience=['urn:you', 'urn:him'])
+
+    def test_check_audience_in_array_when_valid(self, jwt):
+        payload = {
+            'some': 'payload',
+            'aud': ['urn:me', 'urn:someone-else']
+        }
+        token = jwt.encode(payload, 'secret')
+        jwt.decode(token, 'secret', audience='urn:me')
+
+    def test_raise_exception_invalid_audience(self, jwt):
+        payload = {
+            'some': 'payload',
+            'aud': 'urn:someone-else'
+        }
+
+        token = jwt.encode(payload, 'secret')
+
+        with pytest.raises(InvalidAudienceError):
+            jwt.decode(token, 'secret', audience='urn-me')
+
+    def test_raise_exception_invalid_audience_in_array(self, jwt):
+        payload = {
+            'some': 'payload',
+            'aud': ['urn:someone', 'urn:someone-else']
+        }
+
+        token = jwt.encode(payload, 'secret')
+
+        with pytest.raises(InvalidAudienceError):
+            jwt.decode(token, 'secret', audience='urn:me')
+
+    def test_raise_exception_token_without_issuer(self, jwt):
+        issuer = 'urn:wrong'
+
+        payload = {
+            'some': 'payload'
+        }
+
+        token = jwt.encode(payload, 'secret')
+
+        with pytest.raises(MissingRequiredClaimError) as exc:
+            jwt.decode(token, 'secret', issuer=issuer)
+
+        assert exc.value.claim == 'iss'
+
+    def test_raise_exception_token_without_audience(self, jwt):
+        payload = {
+            'some': 'payload',
+        }
+        token = jwt.encode(payload, 'secret')
+
+        with pytest.raises(MissingRequiredClaimError) as exc:
+            jwt.decode(token, 'secret', audience='urn:me')
+
+        assert exc.value.claim == 'aud'
+
+    def test_check_issuer_when_valid(self, jwt):
+        issuer = 'urn:foo'
+        payload = {
+            'some': 'payload',
+            'iss': 'urn:foo'
+        }
+        token = jwt.encode(payload, 'secret')
+        jwt.decode(token, 'secret', issuer=issuer)
+
+    def test_raise_exception_invalid_issuer(self, jwt):
+        issuer = 'urn:wrong'
+
+        payload = {
+            'some': 'payload',
+            'iss': 'urn:foo'
+        }
+
+        token = jwt.encode(payload, 'secret')
+
+        with pytest.raises(InvalidIssuerError):
+            jwt.decode(token, 'secret', issuer=issuer)
+
+    def test_skip_check_audience(self, jwt):
+        payload = {
+            'some': 'payload',
+            'aud': 'urn:me',
+        }
+        token = jwt.encode(payload, 'secret')
+        jwt.decode(token, 'secret', options={'verify_aud': False})
+
+    def test_skip_check_exp(self, jwt):
+        payload = {
+            'some': 'payload',
+            'exp': datetime.utcnow() - timedelta(days=1)
+        }
+        token = jwt.encode(payload, 'secret')
+        jwt.decode(token, 'secret', options={'verify_exp': False})
+
+    def test_decode_should_raise_error_if_exp_required_but_not_present(self, jwt):
+        payload = {
+            'some': 'payload',
+            # exp not present
+        }
+        token = jwt.encode(payload, 'secret')
+
+        with pytest.raises(MissingRequiredClaimError) as exc:
+            jwt.decode(token, 'secret', options={'require_exp': True})
+
+        assert exc.value.claim == 'exp'
+
+    def test_decode_should_raise_error_if_iat_required_but_not_present(self, jwt):
+        payload = {
+            'some': 'payload',
+            # iat not present
+        }
+        token = jwt.encode(payload, 'secret')
+
+        with pytest.raises(MissingRequiredClaimError) as exc:
+            jwt.decode(token, 'secret', options={'require_iat': True})
+
+        assert exc.value.claim == 'iat'
+
+    def test_decode_should_raise_error_if_nbf_required_but_not_present(self, jwt):
+        payload = {
+            'some': 'payload',
+            # nbf not present
+        }
+        token = jwt.encode(payload, 'secret')
+
+        with pytest.raises(MissingRequiredClaimError) as exc:
+            jwt.decode(token, 'secret', options={'require_nbf': True})
+
+        assert exc.value.claim == 'nbf'
+
+    def test_skip_check_signature(self, jwt):
+        token = ("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
+                 ".eyJzb21lIjoicGF5bG9hZCJ9"
+                 ".4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZA")
+        jwt.decode(token, 'secret', options={'verify_signature': False})
+
+    def test_skip_check_iat(self, jwt):
+        payload = {
+            'some': 'payload',
+            'iat': datetime.utcnow() + timedelta(days=1)
+        }
+        token = jwt.encode(payload, 'secret')
+        jwt.decode(token, 'secret', options={'verify_iat': False})
+
+    def test_skip_check_nbf(self, jwt):
+        payload = {
+            'some': 'payload',
+            'nbf': datetime.utcnow() + timedelta(days=1)
+        }
+        token = jwt.encode(payload, 'secret')
+        jwt.decode(token, 'secret', options={'verify_nbf': False})
+
+    def test_custom_json_encoder(self, jwt):
+
+        class CustomJSONEncoder(json.JSONEncoder):
+
+            def default(self, o):
+                if isinstance(o, Decimal):
+                    return 'it worked'
+                return super(CustomJSONEncoder, self).default(o)
+
+        data = {
+            'some_decimal': Decimal('2.2')
+        }
+
+        with pytest.raises(TypeError):
+            jwt.encode(data, 'secret')
+
+        token = jwt.encode(data, 'secret', json_encoder=CustomJSONEncoder)
+        payload = jwt.decode(token, 'secret')
+
+        assert payload == {'some_decimal': 'it worked'}
+
+    def test_decode_with_verify_expiration_kwarg(self, jwt, payload):
+        payload['exp'] = utc_timestamp() - 1
+        secret = 'secret'
+        jwt_message = jwt.encode(payload, secret)
+
+        pytest.deprecated_call(
+            jwt.decode,
+            jwt_message,
+            secret,
+            verify_expiration=False
+        )
+
+        with pytest.raises(ExpiredSignatureError):
+            pytest.deprecated_call(
+                jwt.decode,
+                jwt_message,
+                secret,
+                verify_expiration=True
+            )
+
+    def test_decode_with_optional_algorithms(self, jwt, payload):
+        secret = 'secret'
+        jwt_message = jwt.encode(payload, secret)
+
+        pytest.deprecated_call(
+            jwt.decode,
+            jwt_message,
+            secret
+        )
+
+    def test_decode_no_algorithms_verify_false(self, jwt, payload):
+        secret = 'secret'
+        jwt_message = jwt.encode(payload, secret)
+
+        try:
+            pytest.deprecated_call(
+                jwt.decode, jwt_message, secret, verify=False,
+            )
+        except pytest.fail.Exception:
+            pass
+        else:
+            assert False, "Unexpected DeprecationWarning raised."

+ 158 - 0
desktop/core/ext-py/PyJWT-1.7.1/tests/test_cli.py

@@ -0,0 +1,158 @@
+
+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)
+
+    def test_decode_payload_terminal_tty(self, monkeypatch):
+        encode_args = [
+            '--key=secret-key',
+            'encode',
+            'name=hello-world',
+        ]
+        parser = build_argparser()
+        parsed_encode_args = parser.parse_args(encode_args)
+        token = encode_payload(parsed_encode_args)
+
+        decode_args = ['--key=secret-key', 'decode']
+        parsed_decode_args = parser.parse_args(decode_args)
+
+        monkeypatch.setattr(sys.stdin, 'isatty', lambda: True)
+        monkeypatch.setattr(sys.stdin, 'readline', lambda:  token)
+
+        actual = json.loads(decode_payload(parsed_decode_args))
+        assert actual['name'] == 'hello-world'
+
+    def test_decode_payload_raises_terminal_not_a_tty(self, monkeypatch):
+        decode_args = ['--key', '1234', 'decode']
+        parser = build_argparser()
+        args = parser.parse_args(decode_args)
+
+        monkeypatch.setattr(sys.stdin, 'isatty', lambda: False)
+
+        with pytest.raises(IOError) as excinfo:
+            decode_payload(args)
+            assert 'Cannot read from stdin: terminal not a TTY' \
+                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

+ 19 - 0
desktop/core/ext-py/PyJWT-1.7.1/tests/test_compat.py

@@ -0,0 +1,19 @@
+from jwt.compat import constant_time_compare
+from jwt.utils import force_bytes
+
+
+class TestCompat:
+    def test_constant_time_compare_returns_true_if_same(self):
+        assert constant_time_compare(
+            force_bytes('abc'), force_bytes('abc')
+        )
+
+    def test_constant_time_compare_returns_false_if_diff_lengths(self):
+        assert not constant_time_compare(
+            force_bytes('abc'), force_bytes('abcd')
+        )
+
+    def test_constant_time_compare_returns_false_if_totally_different(self):
+        assert not constant_time_compare(
+            force_bytes('abcd'), force_bytes('efgh')
+        )

+ 7 - 0
desktop/core/ext-py/PyJWT-1.7.1/tests/test_exceptions.py

@@ -0,0 +1,7 @@
+from jwt.exceptions import MissingRequiredClaimError
+
+
+def test_missing_required_claim_error_has_proper_str():
+    exc = MissingRequiredClaimError('abc')
+
+    assert str(exc) == 'Token is missing the "abc" claim'

+ 23 - 0
desktop/core/ext-py/PyJWT-1.7.1/tests/test_jwt.py

@@ -0,0 +1,23 @@
+import jwt
+
+from .utils import utc_timestamp
+
+
+def test_encode_decode():
+    """
+    This test exists primarily to ensure that calls to jwt.encode and
+    jwt.decode don't explode. Most functionality is tested by the PyJWT class
+    tests. This is primarily a sanity check to make sure we don't break the
+    public global functions.
+    """
+    payload = {
+        'iss': 'jeff',
+        'exp': utc_timestamp() + 15,
+        'claim': 'insanity'
+    }
+
+    secret = 'secret'
+    jwt_message = jwt.encode(payload, secret)
+    decoded_payload = jwt.decode(jwt_message, secret)
+
+    assert decoded_payload == payload

+ 40 - 0
desktop/core/ext-py/PyJWT-1.7.1/tests/test_utils.py

@@ -0,0 +1,40 @@
+from jwt.utils import (
+    force_bytes, force_unicode, from_base64url_uint, to_base64url_uint
+)
+
+import pytest
+
+
+@pytest.mark.parametrize("inputval,expected", [
+    (0, b'AA'),
+    (1, b'AQ'),
+    (255, b'_w'),
+    (65537, b'AQAB'),
+    (123456789, b'B1vNFQ'),
+    pytest.param(-1, '', marks=pytest.mark.xfail(raises=ValueError))
+])
+def test_to_base64url_uint(inputval, expected):
+    actual = to_base64url_uint(inputval)
+    assert actual == expected
+
+
+@pytest.mark.parametrize("inputval,expected", [
+    (b'AA', 0),
+    (b'AQ', 1),
+    (b'_w', 255),
+    (b'AQAB', 65537),
+    (b'B1vNFQ', 123456789, ),
+])
+def test_from_base64url_uint(inputval, expected):
+    actual = from_base64url_uint(inputval)
+    assert actual == expected
+
+
+def test_force_unicode_raises_error_on_invalid_object():
+    with pytest.raises(TypeError):
+        force_unicode({})
+
+
+def test_force_bytes_raises_error_on_invalid_object():
+    with pytest.raises(TypeError):
+        force_bytes({})

+ 34 - 0
desktop/core/ext-py/PyJWT-1.7.1/tests/utils.py

@@ -0,0 +1,34 @@
+import os
+import struct
+from calendar import timegm
+from datetime import datetime
+
+
+def utc_timestamp():
+    return timegm(datetime.utcnow().utctimetuple())
+
+
+def key_path(key_name):
+    return os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                        'keys', key_name)
+
+
+# Borrowed from `cryptography`
+if hasattr(int, "from_bytes"):
+    int_from_bytes = int.from_bytes
+else:
+    def int_from_bytes(data, byteorder, signed=False):
+        assert byteorder == 'big'
+        assert not signed
+
+        if len(data) % 4 != 0:
+            data = (b'\x00' * (4 - (len(data) % 4))) + data
+
+        result = 0
+
+        while len(data) > 0:
+            digit, = struct.unpack('>I', data[:4])
+            result = (result << 32) + digit
+            data = data[4:]
+
+        return result

+ 18 - 0
desktop/core/ext-py/PyJWT-1.7.1/tox.ini

@@ -0,0 +1,18 @@
+[tox]
+envlist = py{27,34,35,36,37}-crypto, py{27,35,36,37}-contrib_crypto, py{27,35,36,37}-nocrypto, flake8
+
+[testenv]
+commands =
+    python setup.py pytest
+deps =
+    crypto: cryptography
+    contrib_crypto: pycrypto
+    contrib_crypto: ecdsa
+
+[testenv:flake8]
+commands =
+    flake8
+deps =
+    flake8
+    flake8-import-order
+    pep8-naming