diff --git a/platypush/cron/scheduler.py b/platypush/cron/scheduler.py index 86fbff62..18f1ee8b 100644 --- a/platypush/cron/scheduler.py +++ b/platypush/cron/scheduler.py @@ -153,7 +153,10 @@ class CronScheduler(threading.Thread): for (job_name, job_config) in self.jobs_config.items(): job = self._get_job(name=job_name, config=job_config) if job.state == CronjobState.IDLE: - job.start() + try: + job.start() + except Exception as e: + logger.warning(f'Could not start cronjob {job_name}: {e}') t_before_wait = get_now().timestamp() self._should_stop.wait(timeout=self._poll_seconds) diff --git a/platypush/plugins/__init__.py b/platypush/plugins/__init__.py index f35f06a4..fa7a06fc 100644 --- a/platypush/plugins/__init__.py +++ b/platypush/plugins/__init__.py @@ -158,7 +158,8 @@ class AsyncRunnablePlugin(RunnablePlugin, ABC): asyncio.set_event_loop(self._loop) self._task = self._loop.create_task(self._listen()) - self._task.set_name(self.__class__.__name__ + '.listen') + if hasattr(self._task, 'set_name'): + self._task.set_name(self.__class__.__name__ + '.listen') self._loop.run_forever() def main(self): diff --git a/platypush/user/__init__.py b/platypush/user/__init__.py index 7453aa28..ecbab91f 100644 --- a/platypush/user/__init__.py +++ b/platypush/user/__init__.py @@ -1,17 +1,14 @@ +import base64 import datetime import hashlib +import json import random +import rsa import time from typing import Optional, Dict import bcrypt -try: - from jwt.exceptions import PyJWTError - from jwt import encode as jwt_encode, decode as jwt_decode -except ImportError: - from jwt import PyJWTError, encode as jwt_encode, decode as jwt_decode - from sqlalchemy import Column, Integer, String, DateTime, ForeignKey from sqlalchemy.orm import make_transient @@ -229,17 +226,20 @@ class UserManager: if not user: raise InvalidCredentialsException() - _, priv_key = get_or_generate_jwt_rsa_key_pair() - payload = { - 'username': username, - 'created_at': datetime.datetime.now().timestamp(), - 'expires_at': expires_at.timestamp() if expires_at else None, - } + pub_key, _ = get_or_generate_jwt_rsa_key_pair() + payload = json.dumps( + { + 'username': username, + 'created_at': datetime.datetime.now().timestamp(), + 'expires_at': expires_at.timestamp() if expires_at else None, + }, + sort_keys=True, + indent=None, + ) - token = jwt_encode(payload, priv_key, algorithm='RS256') - if isinstance(token, bytes): - token = token.decode() - return token + return base64.b64encode( + rsa.encrypt(payload.encode('ascii'), pub_key) + ).decode() @staticmethod def validate_jwt_token(token: str) -> Dict[str, str]: @@ -259,12 +259,17 @@ class UserManager: :raises: :class:`platypush.exceptions.user.InvalidJWTTokenException` in case of invalid token. """ - pub_key, _ = get_or_generate_jwt_rsa_key_pair() + _, priv_key = get_or_generate_jwt_rsa_key_pair() try: - payload = jwt_decode(token.encode(), pub_key, algorithms=['RS256']) # type: ignore[reportGeneralTypeIssues] - except PyJWTError as e: - raise InvalidJWTTokenException(str(e)) + payload = json.loads( + rsa.decrypt( + base64.b64decode(token.encode('ascii')), + priv_key + ).decode('ascii') + ) + except (TypeError, ValueError) as e: + raise InvalidJWTTokenException(f'Could not decode JWT token: {e}') expires_at = payload.get('expires_at') if expires_at and time.time() > expires_at: diff --git a/platypush/utils/__init__.py b/platypush/utils/__init__.py index 61f2fc23..14ee7945 100644 --- a/platypush/utils/__init__.py +++ b/platypush/utils/__init__.py @@ -8,6 +8,7 @@ import logging import os import pathlib import re +import rsa import signal import socket import ssl @@ -16,6 +17,7 @@ from typing import Optional, Tuple, Union from dateutil import parser, tz from redis import Redis +from rsa.key import PublicKey, PrivateKey logger = logging.getLogger('utils') @@ -387,9 +389,8 @@ def run(action, *args, **kwargs): return response.output -def generate_rsa_key_pair( - key_file: Optional[str] = None, size: int = 2048 -) -> Tuple[str, str]: +def generate_rsa_key_pair(key_file: Optional[str] = None, size: int = 2048) \ + -> Tuple[PublicKey, PrivateKey]: """ Generate an RSA key pair. @@ -407,30 +408,11 @@ def generate_rsa_key_pair( :param size: Key size (default: 2048 bits). :return: A tuple with the generated ``(priv_key_str, pub_key_str)``. """ - from cryptography.hazmat.primitives import serialization - from cryptography.hazmat.primitives.asymmetric import rsa - from cryptography.hazmat.backends import default_backend - - public_exp = 65537 - private_key = rsa.generate_private_key( - public_exponent=public_exp, key_size=size, backend=default_backend() - ) - - logger.info('Generating RSA {} key pair'.format(size)) - private_key_str = private_key.private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.TraditionalOpenSSL, - encryption_algorithm=serialization.NoEncryption(), - ).decode() - - public_key_str = ( - private_key.public_key() - .public_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PublicFormat.PKCS1, - ) - .decode() - ) + logger.info('Generating RSA keypair') + pub_key, priv_key = rsa.newkeys(size) + logger.info('Generated RSA keypair') + public_key_str = pub_key.save_pkcs1('PEM').decode() + private_key_str = priv_key.save_pkcs1('PEM').decode() if key_file: logger.info('Saving private key to {}'.format(key_file)) @@ -441,7 +423,7 @@ def generate_rsa_key_pair( f2.write(public_key_str) os.chmod(key_file, 0o600) - return public_key_str, private_key_str + return pub_key, priv_key def get_or_generate_jwt_rsa_key_pair(): @@ -452,8 +434,14 @@ def get_or_generate_jwt_rsa_key_pair(): pub_key_file = priv_key_file + '.pub' if os.path.isfile(priv_key_file) and os.path.isfile(pub_key_file): - with open(pub_key_file, 'r') as f1, open(priv_key_file, 'r') as f2: - return f1.read(), f2.read() + with ( + open(pub_key_file, 'r') as f1, + open(priv_key_file, 'r') as f2 + ): + return ( + rsa.PublicKey.load_pkcs1(f1.read().encode()), + rsa.PrivateKey.load_pkcs1(f2.read().encode()), + ) pathlib.Path(key_dir).mkdir(parents=True, exist_ok=True, mode=0o755) return generate_rsa_key_pair(priv_key_file, size=2048) diff --git a/requirements.txt b/requirements.txt index 0ac8538f..3381ad9b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,8 +14,7 @@ frozendict requests sqlalchemy bcrypt -cryptography -pyjwt +rsa zeroconf paho-mqtt websocket-client diff --git a/setup.py b/setup.py index 9b2aff4c..f650ebc7 100755 --- a/setup.py +++ b/setup.py @@ -64,8 +64,7 @@ setup( 'zeroconf>=0.27.0', 'tz', 'python-dateutil', - # 'cryptography', - 'pyjwt', + 'rsa', 'marshmallow', 'frozendict', 'flask',