diff --git a/lemur/auth/service.py b/lemur/auth/service.py index 0e1521b3..f954ce51 100644 --- a/lemur/auth/service.py +++ b/lemur/auth/service.py @@ -101,7 +101,8 @@ def login_required(f): return dict(message="Token is invalid"), 403 try: - payload = jwt.decode(token, current_app.config["LEMUR_TOKEN_SECRET"]) + header_data = fetch_token_header(token) + payload = jwt.decode(token, current_app.config["LEMUR_TOKEN_SECRET"], algorithms=[header_data["alg"]]) except jwt.DecodeError: return dict(message="Token is invalid"), 403 except jwt.ExpiredSignatureError: diff --git a/lemur/authorities/models.py b/lemur/authorities/models.py index 61ab779e..94985cc9 100644 --- a/lemur/authorities/models.py +++ b/lemur/authorities/models.py @@ -18,7 +18,7 @@ from sqlalchemy import ( func, ForeignKey, DateTime, - PassiveDefault, + DefaultClause, Boolean, ) from sqlalchemy.dialects.postgresql import JSON @@ -39,7 +39,7 @@ class Authority(db.Model): plugin_name = Column(String(64)) description = Column(Text) options = Column(JSON) - date_created = Column(DateTime, PassiveDefault(func.now()), nullable=False) + date_created = Column(DateTime, DefaultClause(func.now()), nullable=False) roles = relationship( "Role", secondary=roles_authorities, diff --git a/lemur/certificates/models.py b/lemur/certificates/models.py index f6562b3f..94e3a42e 100644 --- a/lemur/certificates/models.py +++ b/lemur/certificates/models.py @@ -16,7 +16,7 @@ from sqlalchemy import ( Integer, ForeignKey, String, - PassiveDefault, + DefaultClause, func, Column, Text, @@ -138,7 +138,7 @@ class Certificate(db.Model): not_after = Column(ArrowType) not_after_ix = Index("ix_certificates_not_after", not_after.desc()) - date_created = Column(ArrowType, PassiveDefault(func.now()), nullable=False) + date_created = Column(ArrowType, DefaultClause(func.now()), nullable=False) signing_algorithm = Column(String(128)) status = Column(String(128)) @@ -184,7 +184,6 @@ class Certificate(db.Model): "PendingCertificate", secondary=pending_cert_replacement_associations, backref="pending_replace", - viewonly=True, ) logs = relationship("Log", backref="certificate") diff --git a/lemur/factory.py b/lemur/factory.py index 0563d873..edea571a 100644 --- a/lemur/factory.py +++ b/lemur/factory.py @@ -10,7 +10,7 @@ """ import os -import imp +import importlib import errno import pkg_resources import socket @@ -73,8 +73,9 @@ def from_file(file_path, silent=False): :param file_path: :param silent: """ - d = imp.new_module("config") - d.__file__ = file_path + module_spec = importlib.util.spec_from_file_location("config", file_path) + d = importlib.util.module_from_spec(module_spec) + try: with open(file_path) as config_file: exec( # nosec: config file safe diff --git a/lemur/logs/models.py b/lemur/logs/models.py index 07a2ded3..30cc204a 100644 --- a/lemur/logs/models.py +++ b/lemur/logs/models.py @@ -7,7 +7,7 @@ .. moduleauthor:: Kevin Glisson """ -from sqlalchemy import Column, Integer, ForeignKey, PassiveDefault, func, Enum +from sqlalchemy import Column, Integer, ForeignKey, DefaultClause, func, Enum from sqlalchemy_utils.types.arrow import ArrowType @@ -29,5 +29,5 @@ class Log(db.Model): ), nullable=False, ) - logged_at = Column(ArrowType(), PassiveDefault(func.now()), nullable=False) + logged_at = Column(ArrowType(), DefaultClause(func.now()), nullable=False) user_id = Column(Integer, ForeignKey("users.id"), nullable=False) diff --git a/lemur/notifications/messaging.py b/lemur/notifications/messaging.py index 3928689e..3fa339d2 100644 --- a/lemur/notifications/messaging.py +++ b/lemur/notifications/messaging.py @@ -137,11 +137,11 @@ def send_expiration_notifications(exclude): # security team gets all security_email = current_app.config.get("LEMUR_SECURITY_TEAM_EMAIL") - security_data = [] for owner, notification_group in get_eligible_certificates(exclude=exclude).items(): for notification_label, certificates in notification_group.items(): notification_data = [] + security_data = [] notification = certificates[0][0] diff --git a/lemur/notifications/service.py b/lemur/notifications/service.py index 34edccc0..5bc5f3e1 100644 --- a/lemur/notifications/service.py +++ b/lemur/notifications/service.py @@ -43,7 +43,7 @@ def create_default_expiration_notifications(name, recipients, intervals=None): "name": "recipients", "type": "str", "required": True, - "validation": "^([\w+-.%]+@[\w-.]+\.[A-Za-z]{2,4},?)+$", + "validation": r"^([\w+-.%]+@[\w-.]+\.[A-Za-z]{2,4},?)+$", "helpMessage": "Comma delimited list of email addresses", "value": ",".join(recipients), }, @@ -63,7 +63,7 @@ def create_default_expiration_notifications(name, recipients, intervals=None): "name": "interval", "type": "int", "required": True, - "validation": "^\d+$", + "validation": r"^\d+$", "helpMessage": "Number of days to be alert before expiration.", "value": i, } diff --git a/lemur/pending_certificates/models.py b/lemur/pending_certificates/models.py index fa6be073..ee3e5e97 100644 --- a/lemur/pending_certificates/models.py +++ b/lemur/pending_certificates/models.py @@ -9,7 +9,7 @@ from sqlalchemy import ( Integer, ForeignKey, String, - PassiveDefault, + DefaultClause, func, Column, Text, @@ -76,14 +76,14 @@ class PendingCertificate(db.Model): chain = Column(Text()) private_key = Column(Vault, nullable=True) - date_created = Column(ArrowType, PassiveDefault(func.now()), nullable=False) + date_created = Column(ArrowType, DefaultClause(func.now()), nullable=False) dns_provider_id = Column( Integer, ForeignKey("dns_providers.id", ondelete="CASCADE") ) status = Column(Text(), nullable=True) last_updated = Column( - ArrowType, PassiveDefault(func.now()), onupdate=func.now(), nullable=False + ArrowType, DefaultClause(func.now()), onupdate=func.now(), nullable=False ) rotation = Column(Boolean, default=False) diff --git a/lemur/plugins/bases/notification.py b/lemur/plugins/bases/notification.py index 0da0dad2..76aa33de 100644 --- a/lemur/plugins/bases/notification.py +++ b/lemur/plugins/bases/notification.py @@ -42,7 +42,7 @@ class ExpirationNotificationPlugin(NotificationPlugin): "name": "interval", "type": "int", "required": True, - "validation": "^\d+$", + "validation": r"^\d+$", "helpMessage": "Number of days to be alert before expiration.", }, { diff --git a/lemur/plugins/lemur_acme/plugin.py b/lemur/plugins/lemur_acme/plugin.py index e0e5b495..1835971b 100644 --- a/lemur/plugins/lemur_acme/plugin.py +++ b/lemur/plugins/lemur_acme/plugin.py @@ -481,7 +481,7 @@ class ACMEIssuerPlugin(IssuerPlugin): "name": "acme_url", "type": "str", "required": True, - "validation": "/^http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+$/", + "validation": r"/^http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+$/", "helpMessage": "Must be a valid web url starting with http[s]://", }, { @@ -494,7 +494,7 @@ class ACMEIssuerPlugin(IssuerPlugin): "name": "email", "type": "str", "default": "", - "validation": "/^?([-a-zA-Z0-9.`?{}]+@\w+\.\w+)$/", + "validation": r"/^?([-a-zA-Z0-9.`?{}]+@\w+\.\w+)$/", "helpMessage": "Email to use", }, { diff --git a/lemur/plugins/lemur_acme/tests/test_acme.py b/lemur/plugins/lemur_acme/tests/test_acme.py index 89ca6ee1..4ee56396 100644 --- a/lemur/plugins/lemur_acme/tests/test_acme.py +++ b/lemur/plugins/lemur_acme/tests/test_acme.py @@ -3,6 +3,7 @@ from unittest.mock import patch, Mock import josepy as jose from cryptography.x509 import DNSName +from flask import Flask from lemur.plugins.lemur_acme import plugin from lemur.common.utils import generate_private_key from mock import MagicMock @@ -22,6 +23,16 @@ class TestAcme(unittest.TestCase): "test.fakedomain.net": [mock_dns_provider], } + # Creates a new Flask application for a test duration. In python 3.8, manual push of application context is + # needed to run tests in dev environment without getting error 'Working outside of application context'. + _app = Flask('lemur_test_acme') + self.ctx = _app.app_context() + assert self.ctx + self.ctx.push() + + def tearDown(self): + self.ctx.pop() + @patch("lemur.plugins.lemur_acme.plugin.len", return_value=1) def test_get_dns_challenges(self, mock_len): assert mock_len @@ -117,22 +128,24 @@ class TestAcme(unittest.TestCase): mock_dns_provider = Mock() mock_dns_provider.wait_for_dns_change = Mock(return_value=True) + mock_dns_challenge = Mock() + response = Mock() + response.simple_verify = Mock(return_value=False) + mock_dns_challenge.response = Mock(return_value=response) + mock_authz = Mock() - mock_authz.dns_challenge.response = Mock() - mock_authz.dns_challenge.response.simple_verify = Mock(return_value=False) - mock_authz.authz = [] + mock_authz.dns_challenge = [] + mock_authz.dns_challenge.append(mock_dns_challenge) + mock_authz.target_domain = "www.test.com" mock_authz_record = Mock() mock_authz_record.body.identifier.value = "test" + mock_authz.authz = [] mock_authz.authz.append(mock_authz_record) mock_authz.change_id = [] mock_authz.change_id.append("123") - mock_authz.dns_challenge = [] - dns_challenge = Mock() - mock_authz.dns_challenge.append(dns_challenge) - self.assertRaises( - ValueError, self.acme.complete_dns_challenge(mock_acme, mock_authz) - ) + with self.assertRaises(ValueError): + self.acme.complete_dns_challenge(mock_acme, mock_authz) @patch("acme.client.Client") @patch("OpenSSL.crypto", return_value="mock_cert") diff --git a/lemur/plugins/lemur_acme/tests/test_powerdns.py b/lemur/plugins/lemur_acme/tests/test_powerdns.py index 37e4968e..cf850970 100644 --- a/lemur/plugins/lemur_acme/tests/test_powerdns.py +++ b/lemur/plugins/lemur_acme/tests/test_powerdns.py @@ -1,5 +1,7 @@ import unittest from unittest.mock import patch, Mock + +from flask import Flask from lemur.plugins.lemur_acme import plugin, powerdns @@ -17,6 +19,16 @@ class TestPowerdns(unittest.TestCase): "test.fakedomain.net": [mock_dns_provider], } + # Creates a new Flask application for a test duration. In python 3.8, manual push of application context is + # needed to run tests in dev environment without getting error 'Working outside of application context'. + _app = Flask('lemur_test_acme') + self.ctx = _app.app_context() + assert self.ctx + self.ctx.push() + + def tearDown(self): + self.ctx.pop() + @patch("lemur.plugins.lemur_acme.powerdns.current_app") def test_get_zones(self, mock_current_app): account_number = "1234567890" diff --git a/lemur/plugins/lemur_acme/tests/test_ultradns.py b/lemur/plugins/lemur_acme/tests/test_ultradns.py index f1d61e68..7616459e 100644 --- a/lemur/plugins/lemur_acme/tests/test_ultradns.py +++ b/lemur/plugins/lemur_acme/tests/test_ultradns.py @@ -1,6 +1,7 @@ import unittest from unittest.mock import patch, Mock +from flask import Flask from lemur.plugins.lemur_acme import plugin, ultradns from requests.models import Response @@ -19,6 +20,16 @@ class TestUltradns(unittest.TestCase): "test.fakedomain.net": [mock_dns_provider], } + # Creates a new Flask application for a test duration. In python 3.8, manual push of application context is + # needed to run tests in dev environment without getting error 'Working outside of application context'. + _app = Flask('lemur_test_acme') + self.ctx = _app.app_context() + assert self.ctx + self.ctx.push() + + def tearDown(self): + self.ctx.pop() + @patch("lemur.plugins.lemur_acme.ultradns.requests") @patch("lemur.plugins.lemur_acme.ultradns.current_app") def test_ultradns_get_token(self, mock_current_app, mock_requests): diff --git a/lemur/plugins/lemur_email/plugin.py b/lemur/plugins/lemur_email/plugin.py index f380c82e..041b27ec 100644 --- a/lemur/plugins/lemur_email/plugin.py +++ b/lemur/plugins/lemur_email/plugin.py @@ -91,7 +91,7 @@ class EmailNotificationPlugin(ExpirationNotificationPlugin): "name": "recipients", "type": "str", "required": True, - "validation": "^([\w+-.%]+@[\w-.]+\.[A-Za-z]{2,4},?)+$", + "validation": r"^([\w+-.%]+@[\w-.]+\.[A-Za-z]{2,4},?)+$", "helpMessage": "Comma delimited list of email addresses", } ] diff --git a/lemur/plugins/lemur_sftp/plugin.py b/lemur/plugins/lemur_sftp/plugin.py index 66784048..2447cc4e 100644 --- a/lemur/plugins/lemur_sftp/plugin.py +++ b/lemur/plugins/lemur_sftp/plugin.py @@ -47,7 +47,7 @@ class SFTPDestinationPlugin(DestinationPlugin): "type": "int", "required": True, "helpMessage": "The SFTP port, default is 22.", - "validation": "^(6553[0-5]|655[0-2][0-9]\d|65[0-4](\d){2}|6[0-4](\d){3}|[1-5](\d){4}|[1-9](\d){0,3})", + "validation": r"^(6553[0-5]|655[0-2][0-9]\d|65[0-4](\d){2}|6[0-4](\d){3}|[1-5](\d){4}|[1-9](\d){0,3})", "default": "22", }, { diff --git a/lemur/plugins/lemur_slack/plugin.py b/lemur/plugins/lemur_slack/plugin.py index 70d97aa5..3ad22bca 100644 --- a/lemur/plugins/lemur_slack/plugin.py +++ b/lemur/plugins/lemur_slack/plugin.py @@ -89,7 +89,7 @@ class SlackNotificationPlugin(ExpirationNotificationPlugin): "name": "webhook", "type": "str", "required": True, - "validation": "^https:\/\/hooks\.slack\.com\/services\/.+$", + "validation": r"^https:\/\/hooks\.slack\.com\/services\/.+$", "helpMessage": "The url Slack told you to use for this integration", }, { diff --git a/lemur/tests/test_dns_providers.py b/lemur/tests/test_dns_providers.py index 83315be5..9b8fdb5a 100644 --- a/lemur/tests/test_dns_providers.py +++ b/lemur/tests/test_dns_providers.py @@ -13,7 +13,7 @@ class TestDNSProvider(unittest.TestCase): self.assertFalse(dnsutil.is_valid_domain('example-of-over-63-character-domain-label-length-limit-123456789.com')) self.assertTrue(dnsutil.is_valid_domain('_acme-chall.example.com')) self.assertFalse(dnsutil.is_valid_domain('e/xample.com')) - self.assertFalse(dnsutil.is_valid_domain('exam\ple.com')) + self.assertFalse(dnsutil.is_valid_domain('exam\\ple.com')) self.assertFalse(dnsutil.is_valid_domain('