diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 018c4e1..7caaeb0 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -28,7 +28,8 @@ name: tests env: APP_NAME: blti CONF_PATH: conf - COVERAGE_DJANGO_VERSION: 3.2 + COVERAGE_DJANGO_VERSION: '4.2' + COVERAGE_PYTHON_VERSION: '3.10' on: push: @@ -42,10 +43,13 @@ on: jobs: test: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: matrix: + python-version: + - '3.8' + - '3.10' django-version: - '3.2' - '4.2' @@ -54,19 +58,18 @@ jobs: - name: Checkout Repo uses: actions/checkout@v3 - - name: Setup Python + - name: Setup Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: - python-version: '3.8' + python-version: ${{ matrix.python-version }} - name: Install Dependencies run: | - sudo apt-get install python-dev libxml2-dev libxmlsec1-dev python -m pip install --upgrade pip pip install -e . pip install coverage coveralls==3.3.1 - - name: Upgrade Django Version + - name: Upgrade Django ${{ matrix.django-version }} run: pip install "Django~=${{ matrix.django-version }}.0" - name: Setup Django @@ -90,7 +93,9 @@ jobs: coverage run --source=${APP_NAME}/ manage.py test ${APP_NAME} - name: Report Test Coverage - if: matrix.django-version == env.COVERAGE_DJANGO_VERSION + if: | + matrix.django-version == env.COVERAGE_DJANGO_VERSION && + matrix.python-version == env.COVERAGE_PYTHON_VERSION env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} shell: bash @@ -101,7 +106,7 @@ jobs: needs: test - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Checkout Repo @@ -110,7 +115,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: '3.8' + python-version: '3.10' - name: Publish to PyPi uses: uw-it-aca/actions/publish-pypi@main diff --git a/blti/__init__.py b/blti/__init__.py index e8ab890..46efbe2 100644 --- a/blti/__init__.py +++ b/blti/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 diff --git a/blti/crypto.py b/blti/crypto.py index 5102921..497c19f 100644 --- a/blti/crypto.py +++ b/blti/crypto.py @@ -1,48 +1,55 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 -from Crypto.Cipher import AES +from cryptography.hazmat.primitives import padding +from cryptography.hazmat.primitives.ciphers import Cipher, modes +from cryptography.hazmat.primitives.ciphers.algorithms import AES from base64 import b64decode, b64encode class aes128cbc(object): + """ + Advanced Encryption Standard object + + For reference: + https://cryptography.io/en/latest/hazmat/primitives/symmetric-encryption/ + """ _key = None _iv = None def __init__(self, key, iv): - """ - Advanced Encryption Standard object - """ - self._bs = 16 # Block size - if key is None: raise ValueError('Missing AES key') - else: - self._key = key - if iv is None: raise ValueError('Missing AES initialization vector') - else: - self._iv = iv + + self._key = key.encode('utf8') + self._iv = iv.encode('utf8') def encrypt(self, msg): msg = self._pad(self.str_to_bytes(msg)) - crypt = AES.new(self._key, AES.MODE_CBC, self._iv) - return b64encode(crypt.encrypt(msg)).decode('utf-8') + cipher = Cipher(AES(self._key), modes.CBC(self._iv)) + encryptor = cipher.encryptor() + ct = encryptor.update(msg) + encryptor.finalize() + return ct def decrypt(self, msg): - msg = b64decode(msg) - crypt = AES.new(self._key, AES.MODE_CBC, self._iv) - return self._unpad(crypt.decrypt(msg)).decode('utf-8') + cipher = Cipher(AES(self._key), modes.CBC(self._iv)) + decryptor = cipher.decryptor() + dct = decryptor.update(msg) + decryptor.finalize() + return self._unpad(dct).decode('utf-8') def _pad(self, s): - return s + (self._bs - len(s) % self._bs) * self.str_to_bytes(chr( - self._bs - len(s) % self._bs)) + padder = padding.PKCS7(AES.block_size).padder() + pd = padder.update(s) + padder.finalize() + return pd def _unpad(self, s): - return s[:-ord(s[len(s)-1:])] + unpadder = padding.PKCS7(AES.block_size).unpadder() + upd = unpadder.update(s) + unpadder.finalize() + return upd def str_to_bytes(self, s): u_type = type(b''.decode('utf8')) diff --git a/blti/middleware.py b/blti/middleware.py index 007c0a4..2529b75 100644 --- a/blti/middleware.py +++ b/blti/middleware.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 diff --git a/blti/models.py b/blti/models.py index 799b35d..51dbcd1 100644 --- a/blti/models.py +++ b/blti/models.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 diff --git a/blti/performance.py b/blti/performance.py index 794ec59..729fabd 100644 --- a/blti/performance.py +++ b/blti/performance.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 diff --git a/blti/tests.py b/blti/tests.py index 11c35bd..0c5c962 100644 --- a/blti/tests.py +++ b/blti/tests.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 @@ -210,13 +210,25 @@ def test_get_session(self): self.assertEqual(blti.get_session(self.request), {}) def test_encrypt_decrypt_session(self): + blti = BLTI() data = {'abc': {'key': 123}, 'xyz': ('LTI provides a framework through which an LMS ' 'can send some verifiable information about a ' 'user to a third party.')} - enc = BLTI()._encrypt_session(data) - self.assertEquals(BLTI()._decrypt_session(enc), data) + enc = blti._encrypt_session(data) + self.assertEquals(blti._decrypt_session(enc), data) + + bdata = b'abcdef' + self.assertRaises(TypeError, blti._encrypt_session, bdata) + + def test_missing_key_iv(self): + blti = BLTI() + with override_settings(BLTI_AES_KEY=None): + self.assertRaises(ValueError, blti._encrypt_session, '') + + with override_settings(BLTI_AES_IV=None): + self.assertRaises(ValueError, blti._encrypt_session, '') def test_filter_oauth_params(self): data = getattr(settings, 'CANVAS_LTI_V1_LAUNCH_PARAMS', {}) diff --git a/blti/urls.py b/blti/urls.py index 93e01b1..ca843b7 100644 --- a/blti/urls.py +++ b/blti/urls.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 diff --git a/blti/validators.py b/blti/validators.py index 0d22cd0..bf3eec6 100644 --- a/blti/validators.py +++ b/blti/validators.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 diff --git a/blti/views/__init__.py b/blti/views/__init__.py index f4ca572..7b9034e 100644 --- a/blti/views/__init__.py +++ b/blti/views/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 diff --git a/blti/views/develop.py b/blti/views/develop.py index 4d205f4..425672e 100644 --- a/blti/views/develop.py +++ b/blti/views/develop.py @@ -1,4 +1,4 @@ -# Copyright 2023 UW-IT, University of Washington +# Copyright 2024 UW-IT, University of Washington # SPDX-License-Identifier: Apache-2.0 diff --git a/setup.py b/setup.py index eada27f..47153a7 100644 --- a/setup.py +++ b/setup.py @@ -21,14 +21,14 @@ install_requires=[ 'Django>=3.2,<5', 'oauthlib', - 'PyCrypto', + 'cryptography', 'mock', ], license='Apache License, Version 2.0', description='A Django Application on which to build IMS BLTI Tool Providers', long_description=README, url='https://github.com/uw-it-aca/django-blti', - author="UW-IT AXDD", + author="UW-IT T&LS", author_email="aca-it@uw.edu", classifiers=[ 'Environment :: Web Environment', @@ -37,6 +37,5 @@ 'License :: OSI Approved :: Apache Software License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 3.8', ], )