diff --git a/docs/administration.rst b/docs/administration.rst index 9d6c8d12..352318f5 100644 --- a/docs/administration.rst +++ b/docs/administration.rst @@ -161,6 +161,13 @@ Specifying the `SQLALCHEMY_MAX_OVERFLOW` to 0 will enforce limit to not create c Dump all imported or generated CSR and certificate details to stdout using OpenSSL. (default: `False`) +.. data:: ALLOW_CERT_DELETION + :noindex: + + When set to True, certificates can be marked as deleted via the API and deleted certificates will not be displayed + in the UI. When set to False (the default), the certificate delete API will always return "405 method not allowed" + and deleted certificates will always be visible in the UI. (default: `False`) + Certificate Default Options --------------------------- diff --git a/lemur/certificates/models.py b/lemur/certificates/models.py index 34305cc2..ab43cd01 100644 --- a/lemur/certificates/models.py +++ b/lemur/certificates/models.py @@ -101,7 +101,7 @@ class Certificate(db.Model): issuer = Column(String(128)) serial = Column(String(128)) cn = Column(String(128)) - deleted = Column(Boolean, index=True) + deleted = Column(Boolean, index=True, default=False) dns_provider_id = Column(Integer(), ForeignKey('dns_providers.id', ondelete='CASCADE'), nullable=True) not_before = Column(ArrowType) diff --git a/lemur/certificates/service.py b/lemur/certificates/service.py index d5012012..22009043 100644 --- a/lemur/certificates/service.py +++ b/lemur/certificates/service.py @@ -381,6 +381,9 @@ def render(args): now = arrow.now().format('YYYY-MM-DD') query = query.filter(Certificate.not_after <= to).filter(Certificate.not_after >= now) + if current_app.config.get('ALLOW_CERT_DELETION', False): + query = query.filter(Certificate.deleted == False) # noqa + result = database.sort_and_page(query, Certificate, args) return result diff --git a/lemur/certificates/views.py b/lemur/certificates/views.py index 948c44d6..37ebf518 100644 --- a/lemur/certificates/views.py +++ b/lemur/certificates/views.py @@ -6,10 +6,9 @@ .. moduleauthor:: Kevin Glisson """ import base64 -import arrow from builtins import str -from flask import Blueprint, make_response, jsonify, g +from flask import Blueprint, make_response, jsonify, g, current_app from flask_restful import reqparse, Api, inputs from lemur.common.schema import validate_schema @@ -678,17 +677,21 @@ class Certificates(AuthenticatedResource): .. sourcecode:: http - HTTP/1.1 200 OK + HTTP/1.1 204 OK :reqheader Authorization: OAuth token to authenticate :statuscode 204: no error :statuscode 403: unauthenticated :statusoode 404: certificate not found + :statusoode 405: certificate deletion is disabled """ + if not current_app.config.get('ALLOW_CERT_DELETION', False): + return dict(message="Certificate deletion is disabled"), 405 + cert = service.get(certificate_id) - if not cert: + if not cert or cert.deleted: return dict(message="Cannot find specified certificate"), 404 # allow creators @@ -699,12 +702,9 @@ class Certificates(AuthenticatedResource): if not permission.can(): return dict(message='You are not authorized to delete this certificate'), 403 - if arrow.get(cert.not_after) > arrow.utcnow(): - return dict(message='Certificate is still valid, only expired certificates can be deleted'), 412 - service.update(certificate_id, deleted=True) log_service.create(g.current_user, 'delete_cert', certificate=cert) - return '', 204 + return 'Certificate deleted', 204 class NotificationCertificatesList(AuthenticatedResource): diff --git a/lemur/migrations/versions/318b66568358_.py b/lemur/migrations/versions/318b66568358_.py new file mode 100644 index 00000000..9d4aa48d --- /dev/null +++ b/lemur/migrations/versions/318b66568358_.py @@ -0,0 +1,23 @@ +""" Set 'deleted' flag from null to false on all certificates once + +Revision ID: 318b66568358 +Revises: 9f79024fe67b +Create Date: 2019-02-05 15:42:25.477587 + +""" + +# revision identifiers, used by Alembic. +revision = '318b66568358' +down_revision = '9f79024fe67b' + +from alembic import op + + +def upgrade(): + connection = op.get_bind() + # Delete duplicate entries + connection.execute('UPDATE certificates SET deleted = false WHERE deleted IS NULL') + + +def downgrade(): + pass diff --git a/lemur/tests/conf.py b/lemur/tests/conf.py index bbe155cd..525200cf 100644 --- a/lemur/tests/conf.py +++ b/lemur/tests/conf.py @@ -186,3 +186,5 @@ LDAP_BASE_DN = 'dc=example,dc=com' LDAP_EMAIL_DOMAIN = 'example.com' LDAP_REQUIRED_GROUP = 'Lemur Access' LDAP_DEFAULT_ROLE = 'role1' + +ALLOW_CERT_DELETION = True diff --git a/lemur/tests/test_certificates.py b/lemur/tests/test_certificates.py index 8247c36b..75a29e16 100644 --- a/lemur/tests/test_certificates.py +++ b/lemur/tests/test_certificates.py @@ -737,8 +737,8 @@ def test_certificate_put_with_data(client, certificate, issuer_plugin): @pytest.mark.parametrize("token,status", [ (VALID_USER_HEADER_TOKEN, 403), - (VALID_ADMIN_HEADER_TOKEN, 412), - (VALID_ADMIN_API_TOKEN, 412), + (VALID_ADMIN_HEADER_TOKEN, 204), + (VALID_ADMIN_API_TOKEN, 404), ('', 401) ]) def test_certificate_delete(client, token, status):