From 3e0be4a2016745f17c867d44a6f3ebbbe1a922fd Mon Sep 17 00:00:00 2001 From: Gerwout van der Veen Date: Thu, 24 Mar 2022 13:49:14 +0100 Subject: [PATCH 1/7] Replaced itsdangerous JWT encoding and decoding with PyJWT library Itsdangerous no longer has JWT encoding or decoding functionality with newer versions (i.e. 2.1.1). This will completely break flask_oidc --- flask_oidc/__init__.py | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/flask_oidc/__init__.py b/flask_oidc/__init__.py index 5ca54a4..32db2c5 100644 --- a/flask_oidc/__init__.py +++ b/flask_oidc/__init__.py @@ -38,13 +38,12 @@ from oauth2client.client import flow_from_clientsecrets, OAuth2WebServerFlow,\ AccessTokenRefreshError, OAuth2Credentials import httplib2 -from itsdangerous import JSONWebSignatureSerializer, BadSignature +import jwt __all__ = ['OpenIDConnect', 'MemoryCredentials'] logger = logging.getLogger(__name__) - def _json_loads(content): if not isinstance(content, str): content = content.decode('utf-8') @@ -176,11 +175,10 @@ def init_app(self, app): cache=secrets_cache) assert isinstance(self.flow, OAuth2WebServerFlow) - # create signers using the Flask secret key - self.extra_data_serializer = JSONWebSignatureSerializer( - app.config['SECRET_KEY'], salt='flask-oidc-extra-data') - self.cookie_serializer = JSONWebSignatureSerializer( - app.config['SECRET_KEY'], salt='flask-oidc-cookie') + self.extra_data_salt = 'flask-oidc-extra-data' + self.cookie_salt = 'flask-oidc-cookie' + self.extra_data_key = app.config['SECRET_KEY'] + self.extra_data_salt + self.cookie_key = app.config['SECRET_KEY'] + self.cookie_salt try: self.credentials_store = app.config['OIDC_CREDENTIALS_STORE'] @@ -353,13 +351,17 @@ def _get_cookie_id_token(self): # Do not error if we were unable to get the cookie. # The user can debug this themselves. return None - return self.cookie_serializer.loads(id_token_cookie) - except SignatureExpired: + return jwt.decode(id_token_cookie, self.cookie_key, audience=self.client_secrets['client_id'], + algorithms=["HS512"]) + except jwt.ExpiredSignatureError: logger.debug("Invalid ID token cookie", exc_info=True) return None - except BadSignature: + except jwt.InvalidSignatureError: logger.info("Signature invalid for ID token cookie", exc_info=True) return None + except: + logger.info("Token cookie JWT error", exc_info=True) + return None def set_cookie_id_token(self, id_token): """ @@ -390,7 +392,7 @@ def _after_request(self, response): if getattr(g, 'oidc_id_token_dirty', False): if g.oidc_id_token: - signed_id_token = self.cookie_serializer.dumps(g.oidc_id_token) + signed_id_token = jwt.encode(g.oidc_id_token, self.cookie_key, algorithm="HS512") response.set_cookie( current_app.config['OIDC_ID_TOKEN_COOKIE_NAME'], signed_id_token, @@ -575,8 +577,7 @@ def redirect_to_auth_server(self, destination=None, customstate=None): if customstate is not None: statefield = 'custom' statevalue = customstate - state[statefield] = self.extra_data_serializer.dumps( - statevalue).decode('utf-8') + state[statefield] = jwt.encode({'statevalue': statevalue}, self.extra_data_key, algorithm="HS512") extra_params = { 'state': urlsafe_b64encode(json.dumps(state).encode('utf-8')), @@ -684,6 +685,7 @@ def decorated(*args, **kwargs): def _oidc_callback(self): plainreturn, data = self._process_callback('destination') + if plainreturn: return data else: @@ -731,10 +733,10 @@ def _process_callback(self, statefield): # when Google is the IdP, the subject is their G+ account number self.credentials_store[id_token['sub']] = credentials.to_json() - # Retrieve the extra statefield data try: - response = self.extra_data_serializer.loads(state[statefield]) - except BadSignature: + response = jwt.decode(state[statefield], self.extra_data_key, algorithms=["HS512"]) + response = response['statevalue'] + except: logger.error('State field was invalid') return True, self._oidc_error() From ba139d6495267fedf063de564557b7fea734a589 Mon Sep 17 00:00:00 2001 From: Gerwout van der Veen Date: Thu, 24 Mar 2022 13:55:35 +0100 Subject: [PATCH 2/7] - removed itsdangerous, added PyJWT --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 36b3dd9..f6f2a1f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ Flask -itsdangerous +PyJWT oauth2client six From 1b3b5eb8efaf78e4c1070000898757eb22bf8708 Mon Sep 17 00:00:00 2001 From: Erik Cederstrand Date: Thu, 21 Jul 2022 12:54:54 +0200 Subject: [PATCH 3/7] Remove submodule to allow pip install using git --- .gitmodules | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .gitmodules diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index bf7b494..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "docs/_themes"] - path = docs/_themes - url = git://github.com/mitsuhiko/flask-sphinx-themes.git From 62880cc67f15806443efaa2f39e0911ba1b8de87 Mon Sep 17 00:00:00 2001 From: Erik Cederstrand Date: Thu, 21 Jul 2022 13:04:14 +0200 Subject: [PATCH 4/7] Let Makefile handle themes git repo. Fixes #152 --- docs/Makefile | 5 +++++ docs/_themes | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) delete mode 160000 docs/_themes diff --git a/docs/Makefile b/docs/Makefile index c31026b..7576704 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -6,6 +6,7 @@ SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build +THEMESDIR = _themes # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) @@ -52,6 +53,10 @@ help: .PHONY: clean clean: rm -rf $(BUILDDIR)/* + rm -rf $(THEMESDIR)/* + git clone git://github.com/mitsuhiko/flask-sphinx-themes.git $(THEMESDIR) + @echo + @echo "Clones Sphinx themes into $(THEMESDIR)" .PHONY: html html: diff --git a/docs/_themes b/docs/_themes deleted file mode 160000 index 1cc4468..0000000 --- a/docs/_themes +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1cc44686f0f9dad27cce2c9d16cf42f97bc87dbd From 839ab83e11f47139d3904264b39798ed0e0f37ba Mon Sep 17 00:00:00 2001 From: Erik Cederstrand Date: Thu, 21 Jul 2022 13:14:44 +0200 Subject: [PATCH 5/7] Only check existence of submodule if it's needed --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c758656..bb8f473 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ from setuptools import setup # This check is to make sure we checkout docs/_themes before running sdist -if not os.path.exists("./docs/_themes/README"): +if "sdist" in sys.argv and not os.path.exists("./docs/_themes/README"): print('Please make sure you have docs/_themes checked out while running setup.py!') if os.path.exists('.git'): print('You seem to be using a git checkout, please execute the following commands to get the docs/_themes directory:') From 41388b41ca9a5bf818059e5c0ced8f4eb25bd008 Mon Sep 17 00:00:00 2001 From: Erik Cederstrand Date: Fri, 14 Oct 2022 09:46:50 +0200 Subject: [PATCH 6/7] Reuse requirements.txt This removes duplication and pulls in the itsdangerous -> PyJWT change into setup.pp --- setup.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/setup.py b/setup.py index bb8f473..34313ea 100644 --- a/setup.py +++ b/setup.py @@ -19,6 +19,8 @@ here = os.path.abspath(os.path.dirname(__file__)) with io.open(os.path.join(here, 'README.rst')) as f: readme = f.read() +with io.open(os.path.join(here, 'requirements.txt')) as f: + requirements = f.read().split() setup( name='flask-oidc', @@ -30,13 +32,8 @@ version='1.4.0', packages=[ 'flask_oidc', - ], - install_requires=[ - 'Flask', - 'itsdangerous', - 'oauth2client', - 'six', - ], + ], + install_requires=requirements, tests_require=['nose', 'mock'], entry_points={ 'console_scripts': ['oidc-register=flask_oidc.registration_util:main'], From 57ac92f27c8b680b637af8bda4fb6c7e0664ecf7 Mon Sep 17 00:00:00 2001 From: Erik Cederstrand Date: Fri, 14 Oct 2022 09:48:40 +0200 Subject: [PATCH 7/7] Pull in requirements.txt into MANIFEST This allow installs from the source package to work --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index c4b8375..349f478 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -include README.rst LICENSE.rst CHANGES.rst +include requirements.txt README.rst LICENSE.rst CHANGES.rst recursive-include docs * recursive-exclude docs *.pyc recursive-exclude docs *.pyo