from __future__ import unicode_literals  # at top of module

import datetime
import json

import arrow
import pytest
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from marshmallow import ValidationError
from freezegun import freeze_time
from unittest.mock import patch

from lemur.certificates.service import create_csr
from lemur.certificates.views import *  # noqa
from lemur.common import utils
from lemur.domains.models import Domain


from lemur.tests.vectors import (
    VALID_ADMIN_API_TOKEN,
    VALID_ADMIN_HEADER_TOKEN,
    VALID_USER_HEADER_TOKEN,
    CSR_STR,
    INTERMEDIATE_CERT_STR,
    SAN_CERT_STR,
    SAN_CERT_CSR,
    SAN_CERT_KEY,
    ROOTCA_KEY,
    ROOTCA_CERT_STR,
)


def test_get_or_increase_name(session, certificate):
    from lemur.certificates.models import get_or_increase_name
    from lemur.tests.factories import CertificateFactory

    serial = "AFF2DB4F8D2D4D8E80FA382AE27C2333"

    assert get_or_increase_name(
        certificate.name, certificate.serial
    ) == "{0}-{1}".format(certificate.name, serial)

    certificate.name = "test-cert-11111111"
    assert (
        get_or_increase_name(certificate.name, certificate.serial)
        == "test-cert-11111111-" + serial
    )

    certificate.name = "test-cert-11111111-1"
    assert (
        get_or_increase_name("test-cert-11111111-1", certificate.serial)
        == "test-cert-11111111-1-" + serial
    )

    CertificateFactory(name="certificate1")
    CertificateFactory(name="certificate1-" + serial)
    session.commit()

    assert get_or_increase_name(
        "certificate1", int(serial, 16)
    ) == "certificate1-{}-1".format(serial)


def test_get_all_certs(session, certificate):
    from lemur.certificates.service import get_all_certs

    assert len(get_all_certs()) > 1


def test_get_by_name(session, certificate):
    from lemur.certificates.service import get_by_name

    found = get_by_name(certificate.name)

    assert found


def test_get_by_serial(session, certificate):
    from lemur.certificates.service import get_by_serial

    found = get_by_serial(certificate.serial)

    assert found


def test_get_all_certs_attached_to_endpoint_without_autorotate(session):
    from lemur.certificates.service import get_all_certs_attached_to_endpoint_without_autorotate, \
        cleanup_after_revoke
    from lemur.tests.factories import EndpointFactory

    # add a certificate with endpoint
    EndpointFactory()

    list_before = get_all_certs_attached_to_endpoint_without_autorotate()
    len_list_before = len(list_before)
    assert len_list_before > 0
    # revoked the first certificate
    first_cert_with_endpoint = list_before[0]
    cleanup_after_revoke(first_cert_with_endpoint)

    list_after = get_all_certs_attached_to_endpoint_without_autorotate()
    assert len(list_after) + 1 == len_list_before


def test_delete_cert(session):
    from lemur.certificates.service import delete, get
    from lemur.tests.factories import CertificateFactory

    delete_this = CertificateFactory(name="DELETEME")
    session.commit()

    cert_exists = get(delete_this.id)

    # it needs to exist first
    assert cert_exists

    delete(delete_this.id)
    cert_exists = get(delete_this.id)

    # then not exist after delete
    assert not cert_exists


def test_cleanup_after_revoke(session, issuer_plugin, crypto_authority):
    from lemur.certificates.service import cleanup_after_revoke, get
    from lemur.tests.factories import CertificateFactory

    revoke_this = CertificateFactory(name="REVOKEME")
    session.commit()

    to_be_revoked = get(revoke_this.id)
    assert to_be_revoked
    to_be_revoked.notify = True
    to_be_revoked.rotation = True

    # Assuming the cert is revoked by corresponding issuer, update the records in lemur
    cleanup_after_revoke(to_be_revoked)
    revoked_cert = get(to_be_revoked.id)

    # then not exist after delete
    assert revoked_cert
    assert revoked_cert.status == "revoked"
    assert not revoked_cert.notify
    assert not revoked_cert.rotation
    assert not revoked_cert.destinations


def test_get_by_attributes(session, certificate):
    from lemur.certificates.service import get_by_attributes

    # Should get one cert
    certificate1 = get_by_attributes(
        {
            "name": "SAN-san.example.org-LemurTrustUnittestsClass1CA2018-20171231-20471231"
        }
    )

    # Should get one cert using multiple attrs
    certificate2 = get_by_attributes(
        {"name": "test-cert-11111111-1", "cn": "san.example.org"}
    )

    # Should get multiple certs
    multiple = get_by_attributes(
        {
            "cn": "LemurTrust Unittests Class 1 CA 2018",
            "issuer": "LemurTrustUnittestsRootCA2018",
        }
    )

    assert len(certificate1) == 1
    assert len(certificate2) == 1
    assert len(multiple) > 1


def test_find_duplicates(session):
    from lemur.certificates.service import find_duplicates

    cert = {"body": SAN_CERT_STR, "chain": INTERMEDIATE_CERT_STR}

    dups1 = find_duplicates(cert)

    cert["chain"] = ""

    dups2 = find_duplicates(cert)

    assert len(dups1) > 0
    assert len(dups2) > 0


def test_get_certificate_primitives(certificate):
    from lemur.certificates.service import get_certificate_primitives

    names = [x509.DNSName(x.name) for x in certificate.domains]

    with freeze_time(datetime.date(year=2016, month=10, day=30)):
        primitives = get_certificate_primitives(certificate)
        assert len(primitives) == 25
        assert (primitives["key_type"] == "RSA2048")


def test_certificate_output_schema(session, certificate, issuer_plugin):
    from lemur.certificates.schemas import CertificateOutputSchema

    # Clear the cached attribute first
    if "parsed_cert" in certificate.__dict__:
        del certificate.__dict__["parsed_cert"]

    # Make sure serialization parses the cert only once (uses cached 'parsed_cert' attribute)
    with patch(
        "lemur.common.utils.parse_certificate", side_effect=utils.parse_certificate
    ) as wrapper:
        data, errors = CertificateOutputSchema().dump(certificate)
        assert data["issuer"] == "LemurTrustUnittestsClass1CA2018"
        assert data["distinguishedName"] == "L=Earth,ST=N/A,C=EE,OU=Karate Lessons,O=Daniel San & co,CN=san.example.org"
        # Authority does not have 'cab_compliant', thus subject details should not be returned
        assert "organization" not in data

    assert wrapper.call_count == 1


def test_certificate_output_schema_subject_details(session, certificate, issuer_plugin):
    from lemur.certificates.schemas import CertificateOutputSchema
    from lemur.authorities.service import update_options

    # Mark authority as non-cab-compliant
    update_options(certificate.authority.id, '[{"name": "cab_compliant","value":false}]')

    data, errors = CertificateOutputSchema().dump(certificate)
    assert not errors
    assert data["issuer"] == "LemurTrustUnittestsClass1CA2018"
    assert data["distinguishedName"] == "L=Earth,ST=N/A,C=EE,OU=Karate Lessons,O=Daniel San & co,CN=san.example.org"

    # Original subject details should be returned because of cab_compliant option update above
    assert data["country"] == "EE"
    assert data["state"] == "N/A"
    assert data["location"] == "Earth"
    assert data["organization"] == "Daniel San & co"
    assert data["organizationalUnit"] == "Karate Lessons"

    # Mark authority as cab-compliant
    update_options(certificate.authority.id, '[{"name": "cab_compliant","value":true}]')
    data, errors = CertificateOutputSchema().dump(certificate)
    assert not errors
    assert "country" not in data
    assert "state" not in data
    assert "location" not in data
    assert "organization" not in data
    assert "organizationalUnit" not in data


def test_certificate_edit_schema(session):
    from lemur.certificates.schemas import CertificateEditInputSchema

    input_data = {"owner": "bob@example.com"}
    data, errors = CertificateEditInputSchema().load(input_data)

    assert not errors
    assert len(data["notifications"]) == 3
    assert data["roles"][0].name == input_data["owner"]


def test_authority_key_identifier_schema():
    from lemur.schemas import AuthorityKeyIdentifierSchema

    input_data = {"useKeyIdentifier": True, "useAuthorityCert": True}

    data, errors = AuthorityKeyIdentifierSchema().load(input_data)

    assert sorted(data) == sorted(
        {"use_key_identifier": True, "use_authority_cert": True}
    )
    assert not errors

    data, errors = AuthorityKeyIdentifierSchema().dumps(data)
    assert sorted(data) == sorted(json.dumps(input_data))
    assert not errors


def test_certificate_info_access_schema():
    from lemur.schemas import CertificateInfoAccessSchema

    input_data = {"includeAIA": True}

    data, errors = CertificateInfoAccessSchema().load(input_data)
    assert not errors
    assert data == {"include_aia": True}

    data, errors = CertificateInfoAccessSchema().dump(data)
    assert not errors
    assert data == input_data


def test_subject_key_identifier_schema():
    from lemur.schemas import SubjectKeyIdentifierSchema

    input_data = {"includeSKI": True}

    data, errors = SubjectKeyIdentifierSchema().load(input_data)
    assert not errors
    assert data == {"include_ski": True}
    data, errors = SubjectKeyIdentifierSchema().dump(data)
    assert not errors
    assert data == input_data


def test_extension_schema(client):
    from lemur.certificates.schemas import ExtensionSchema

    input_data = {
        "keyUsage": {"useKeyEncipherment": True, "useDigitalSignature": True},
        "extendedKeyUsage": {"useServerAuthentication": True},
        "subjectKeyIdentifier": {"includeSKI": True},
    }

    data, errors = ExtensionSchema().load(input_data)
    assert not errors

    data, errors = ExtensionSchema().dump(data)
    assert not errors


def test_certificate_input_schema(client, authority):
    from lemur.certificates.schemas import CertificateInputSchema

    input_data = {
        "commonName": "test.example.com",
        "owner": "jim@example.com",
        "authority": {"id": authority.id},
        "description": "testtestest",
        "validityStart": arrow.get(2018, 11, 9).isoformat(),
        "validityEnd": arrow.get(2019, 11, 9).isoformat(),
        "dnsProvider": None,
        "location": "A Place"
    }

    data, errors = CertificateInputSchema().load(input_data)

    assert not errors
    assert data["authority"].id == authority.id
    assert data["location"] == "A Place"

    # make sure the defaults got set
    assert data["common_name"] == "test.example.com"
    assert data["country"] == "US"
    assert data["key_type"] == "ECCPRIME256V1"

    assert len(data.keys()) == 19


def test_certificate_input_with_extensions(client, authority):
    from lemur.certificates.schemas import CertificateInputSchema

    input_data = {
        "commonName": "test.example.com",
        "owner": "jim@example.com",
        "authority": {"id": authority.id},
        "description": "testtestest",
        "extensions": {
            "keyUsage": {"digital_signature": True},
            "extendedKeyUsage": {
                "useClientAuthentication": True,
                "useServerAuthentication": True,
            },
            "subjectKeyIdentifier": {"includeSKI": True},
            "subAltNames": {
                "names": [{"nameType": "DNSName", "value": "test.example.com"}]
            },
        },
        "dnsProvider": None,
        "keyType": "RSA2048"
    }

    data, errors = CertificateInputSchema().load(input_data)
    assert not errors
    assert data["key_type"] == "RSA2048"


def test_certificate_input_schema_parse_csr(authority):
    from lemur.certificates.schemas import CertificateInputSchema

    test_san_dns = "foobar.com"
    extensions = {
        "sub_alt_names": {
            "names": x509.SubjectAlternativeName([x509.DNSName(test_san_dns)])
        }
    }
    csr, private_key = create_csr(
        owner="joe@example.com",
        common_name="ACommonName",
        organization="test",
        organizational_unit="Meters",
        country="NL",
        state="Noord-Holland",
        location="Amsterdam",
        key_type="RSA2048",
        extensions=extensions,
    )

    input_data = {
        "commonName": "test.example.com",
        "owner": "jim@example.com",
        "authority": {"id": authority.id},
        "description": "testtestest",
        "csr": csr,
        "dnsProvider": None,
    }

    data, errors = CertificateInputSchema().load(input_data)

    assert not errors
    for san in data["extensions"]["sub_alt_names"]["names"]:
        assert san.value == test_san_dns

    assert data["key_type"] == "RSA2048"


def test_certificate_out_of_range_date(client, authority):
    from lemur.certificates.schemas import CertificateInputSchema

    input_data = {
        "commonName": "test.example.com",
        "owner": "jim@example.com",
        "authority": {"id": authority.id},
        "description": "testtestest",
        "validityYears": 100,
        "dnsProvider": None,
    }

    data, errors = CertificateInputSchema().load(input_data)
    assert errors

    input_data["validityStart"] = "2017-04-30T00:12:34.513631"

    data, errors = CertificateInputSchema().load(input_data)
    assert errors

    input_data["validityEnd"] = "2018-04-30T00:12:34.513631"

    data, errors = CertificateInputSchema().load(input_data)
    assert errors


def test_certificate_valid_years(client, authority):
    from lemur.certificates.schemas import CertificateInputSchema

    input_data = {
        "commonName": "test.example.com",
        "owner": "jim@example.com",
        "authority": {"id": authority.id},
        "description": "testtestest",
        "validityYears": 1,
        "dnsProvider": None,
    }

    data, errors = CertificateInputSchema().load(input_data)
    assert not errors


def test_certificate_valid_dates(client, authority):
    from lemur.certificates.schemas import CertificateInputSchema

    input_data = {
        "commonName": "test.example.com",
        "owner": "jim@example.com",
        "authority": {"id": authority.id},
        "description": "testtestest",
        "validityStart": "2020-01-01T00:00:00",
        "validityEnd": "2020-01-01T00:00:01",
        "dnsProvider": None,
    }

    data, errors = CertificateInputSchema().load(input_data)
    assert not errors


def test_certificate_cn_admin(client, authority, logged_in_admin):
    """Admin is exempt from CN/SAN domain restrictions."""
    from lemur.certificates.schemas import CertificateInputSchema

    input_data = {
        "commonName": "*.admin-overrides-allowlist.com",
        "owner": "jim@example.com",
        "authority": {"id": authority.id},
        "description": "testtestest",
        "validityStart": "2020-01-01T00:00:00",
        "validityEnd": "2020-01-01T00:00:01",
        "dnsProvider": None,
    }

    data, errors = CertificateInputSchema().load(input_data)
    assert not errors


def test_certificate_allowed_names(client, authority, session, logged_in_user):
    """Test for allowed CN and SAN values."""
    from lemur.certificates.schemas import CertificateInputSchema

    input_data = {
        "commonName": "Names with spaces are not checked",
        "owner": "jim@example.com",
        "authority": {"id": authority.id},
        "description": "testtestest",
        "validityStart": "2020-01-01T00:00:00",
        "validityEnd": "2020-01-01T00:00:01",
        "extensions": {
            "subAltNames": {
                "names": [
                    {"nameType": "DNSName", "value": "allowed.example.com"},
                    {"nameType": "IPAddress", "value": "127.0.0.1"},
                ]
            }
        },
        "dnsProvider": None,
    }

    data, errors = CertificateInputSchema().load(input_data)
    assert not errors


def test_certificate_incative_authority(client, authority, session, logged_in_user):
    """Cannot issue certificates with an inactive authority."""
    from lemur.certificates.schemas import CertificateInputSchema

    authority.active = False
    session.add(authority)

    input_data = {
        "commonName": "foo.example.com",
        "owner": "jim@example.com",
        "authority": {"id": authority.id},
        "description": "testtestest",
        "validityStart": "2020-01-01T00:00:00",
        "validityEnd": "2020-01-01T00:00:01",
        "dnsProvider": None,
    }

    data, errors = CertificateInputSchema().load(input_data)
    assert errors["authority"][0] == "The authority is inactive."


def test_certificate_disallowed_names(client, authority, session, logged_in_user):
    """The CN and SAN are disallowed by LEMUR_ALLOWED_DOMAINS."""
    from lemur.certificates.schemas import CertificateInputSchema

    input_data = {
        "commonName": "*.example.com",
        "owner": "jim@example.com",
        "authority": {"id": authority.id},
        "description": "testtestest",
        "validityStart": "2020-01-01T00:00:00",
        "validityEnd": "2020-01-01T00:00:01",
        "extensions": {
            "subAltNames": {
                "names": [
                    {"nameType": "DNSName", "value": "allowed.example.com"},
                    {"nameType": "DNSName", "value": "evilhacker.org"},
                ]
            }
        },
        "dnsProvider": None,
    }

    data, errors = CertificateInputSchema().load(input_data)
    assert errors["common_name"][0].startswith(
        "Domain *.example.com does not match allowed domain patterns"
    )
    assert errors["extensions"]["sub_alt_names"]["names"][0].startswith(
        "Domain evilhacker.org does not match allowed domain patterns"
    )


def test_certificate_sensitive_name(client, authority, session, logged_in_user):
    """The CN is disallowed by 'sensitive' flag on Domain model."""
    from lemur.certificates.schemas import CertificateInputSchema

    input_data = {
        "commonName": "sensitive.example.com",
        "owner": "jim@example.com",
        "authority": {"id": authority.id},
        "description": "testtestest",
        "validityStart": "2020-01-01T00:00:00",
        "validityEnd": "2020-01-01T00:00:01",
        "dnsProvider": None,
    }
    session.add(Domain(name="sensitive.example.com", sensitive=True))

    data, errors = CertificateInputSchema().load(input_data)
    assert errors["common_name"][0].startswith(
        "Domain sensitive.example.com has been marked as sensitive"
    )


def test_certificate_upload_schema_ok(client):
    from lemur.certificates.schemas import CertificateUploadInputSchema

    data = {
        "name": "Jane",
        "owner": "pwner@example.com",
        "body": SAN_CERT_STR,
        "privateKey": SAN_CERT_KEY,
        "chain": INTERMEDIATE_CERT_STR,
        "csr": SAN_CERT_CSR,
        "external_id": "1234",
    }
    data, errors = CertificateUploadInputSchema().load(data)
    assert not errors


def test_certificate_upload_schema_minimal(client):
    from lemur.certificates.schemas import CertificateUploadInputSchema

    data = {"owner": "pwner@example.com", "body": SAN_CERT_STR}
    data, errors = CertificateUploadInputSchema().load(data)
    assert not errors


def test_certificate_upload_schema_long_chain(client):
    from lemur.certificates.schemas import CertificateUploadInputSchema

    data = {
        "owner": "pwner@example.com",
        "body": SAN_CERT_STR,
        "chain": INTERMEDIATE_CERT_STR + "\n" + ROOTCA_CERT_STR,
    }
    data, errors = CertificateUploadInputSchema().load(data)
    assert not errors


def test_certificate_upload_schema_invalid_body(client):
    from lemur.certificates.schemas import CertificateUploadInputSchema

    data = {
        "owner": "pwner@example.com",
        "body": "Hereby I certify that this is a valid body",
    }
    data, errors = CertificateUploadInputSchema().load(data)
    assert errors == {"body": ["Public certificate presented is not valid."]}


def test_certificate_upload_schema_invalid_pkey(client):
    from lemur.certificates.schemas import CertificateUploadInputSchema

    data = {
        "owner": "pwner@example.com",
        "body": SAN_CERT_STR,
        "privateKey": "Look at me Im a private key!!111",
    }
    data, errors = CertificateUploadInputSchema().load(data)
    assert errors == {"private_key": ["Private key presented is not valid."]}


def test_certificate_upload_schema_invalid_chain(client):
    from lemur.certificates.schemas import CertificateUploadInputSchema

    data = {"body": SAN_CERT_STR, "chain": "CHAINSAW", "owner": "pwner@example.com"}
    data, errors = CertificateUploadInputSchema().load(data)
    assert errors == {"chain": ["Invalid certificate in certificate chain."]}


def test_certificate_upload_schema_wrong_pkey(client):
    from lemur.certificates.schemas import CertificateUploadInputSchema

    data = {
        "body": SAN_CERT_STR,
        "privateKey": ROOTCA_KEY,
        "chain": INTERMEDIATE_CERT_STR,
        "owner": "pwner@example.com",
    }
    data, errors = CertificateUploadInputSchema().load(data)
    assert errors == {"_schema": ["Private key does not match certificate."]}


def test_certificate_upload_schema_wrong_chain(client):
    from lemur.certificates.schemas import CertificateUploadInputSchema

    data = {
        "owner": "pwner@example.com",
        "body": SAN_CERT_STR,
        "chain": ROOTCA_CERT_STR,
    }
    data, errors = CertificateUploadInputSchema().load(data)
    assert errors == {
        "_schema": [
            "Incorrect chain certificate(s) provided: 'san.example.org' is not signed by "
            "'LemurTrust Unittests Root CA 2018'"
        ]
    }


def test_certificate_upload_schema_wrong_chain_2nd(client):
    from lemur.certificates.schemas import CertificateUploadInputSchema

    data = {
        "owner": "pwner@example.com",
        "body": SAN_CERT_STR,
        "chain": INTERMEDIATE_CERT_STR + "\n" + SAN_CERT_STR,
    }
    data, errors = CertificateUploadInputSchema().load(data)
    assert errors == {
        "_schema": [
            "Incorrect chain certificate(s) provided: 'LemurTrust Unittests Class 1 CA 2018' is "
            "not signed by 'san.example.org'"
        ]
    }


def test_certificate_revoke_schema():
    from lemur.certificates.schemas import CertificateRevokeSchema

    input = {
        "comments": "testing certificate revoke schema",
        "crl_reason": "cessationOfOperation"
    }
    data, errors = CertificateRevokeSchema().load(input)
    assert not errors

    input["crl_reason"] = "fakeCrlReason"
    data, errors = CertificateRevokeSchema().load(input)
    assert errors == {
        "crl_reason": ['Not a valid choice.']
    }


def test_create_basic_csr(client):
    csr_config = dict(
        common_name="example.com",
        organization="Example, Inc.",
        organizational_unit="Operations",
        country="US",
        state="CA",
        location="A place",
        owner="joe@example.com",
        key_type="RSA2048",
        extensions=dict(
            names=dict(
                sub_alt_names=x509.SubjectAlternativeName(
                    [
                        x509.DNSName("test.example.com"),
                        x509.DNSName("test2.example.com"),
                    ]
                )
            )
        ),
    )
    csr, pem = create_csr(**csr_config)

    csr = x509.load_pem_x509_csr(csr.encode("utf-8"), default_backend())
    for name in csr.subject:
        assert name.value in csr_config.values()


def test_csr_empty_san(client):
    """Test that an empty "names" list does not produce a CSR with empty SubjectAltNames extension.

    The Lemur UI always submits this extension even when no alt names are defined.
    """

    csr_text, pkey = create_csr(
        common_name="daniel-san.example.com",
        owner="daniel-san@example.com",
        key_type="RSA2048",
        extensions={"sub_alt_names": {"names": x509.SubjectAlternativeName([])}},
    )

    csr = x509.load_pem_x509_csr(csr_text.encode("utf-8"), default_backend())

    with pytest.raises(x509.ExtensionNotFound):
        csr.extensions.get_extension_for_class(x509.SubjectAlternativeName)


def test_csr_disallowed_cn(client, logged_in_user):
    """Domain name CN is disallowed via LEMUR_ALLOWED_DOMAINS."""
    from lemur.common import validators

    request, pkey = create_csr(
        common_name="evilhacker.org", owner="joe@example.com", key_type="RSA2048"
    )
    with pytest.raises(ValidationError) as err:
        validators.csr(request)
    assert str(err.value).startswith(
        "Domain evilhacker.org does not match allowed domain patterns"
    )


def test_csr_disallowed_san(client, logged_in_user):
    """SAN name is disallowed by LEMUR_ALLOWED_DOMAINS."""
    from lemur.common import validators

    request, pkey = create_csr(
        common_name="CN with spaces isn't a domain and is thus allowed",
        owner="joe@example.com",
        key_type="RSA2048",
        extensions={
            "sub_alt_names": {
                "names": x509.SubjectAlternativeName([x509.DNSName("evilhacker.org")])
            }
        },
    )
    with pytest.raises(ValidationError) as err:
        validators.csr(request)
    assert str(err.value).startswith(
        "Domain evilhacker.org does not match allowed domain patterns"
    )


def test_get_name_from_arn(client):
    from lemur.certificates.service import get_name_from_arn

    arn = "arn:aws:iam::11111111:server-certificate/mycertificate"
    assert get_name_from_arn(arn) == "mycertificate"


def test_get_account_number(client):
    from lemur.certificates.service import get_account_number

    arn = "arn:aws:iam::11111111:server-certificate/mycertificate"
    assert get_account_number(arn) == "11111111"


def test_mint_certificate(issuer_plugin, authority):
    from lemur.certificates.service import mint

    cert_body, private_key, chain, external_id, csr = mint(
        authority=authority, csr=CSR_STR
    )
    assert cert_body == SAN_CERT_STR


def test_create_certificate(issuer_plugin, authority, user):
    from lemur.certificates.service import create

    cert = create(
        authority=authority, csr=CSR_STR, owner="joe@example.com", creator=user["user"]
    )
    assert str(cert.not_after) == "2047-12-31T22:00:00+00:00"
    assert str(cert.not_before) == "2017-12-31T22:00:00+00:00"
    assert cert.issuer == "LemurTrustUnittestsClass1CA2018"
    assert (
        cert.name
        == "SAN-san.example.org-LemurTrustUnittestsClass1CA2018-20171231-20471231-AFF2DB4F8D2D4D8E80FA382AE27C2333"
    )

    cert = create(
        authority=authority,
        csr=CSR_STR,
        owner="joe@example.com",
        name="ACustomName1",
        creator=user["user"],
    )
    assert cert.name == "ACustomName1"


def test_reissue_certificate(
    issuer_plugin, crypto_authority, certificate, logged_in_user
):
    from lemur.certificates.service import reissue_certificate
    from lemur.authorities.service import update_options
    from lemur.tests.conf import LEMUR_DEFAULT_ORGANIZATION

    # test-authority would return a mismatching private key, so use 'cryptography-issuer' plugin instead.
    certificate.authority = crypto_authority
    new_cert = reissue_certificate(certificate)
    assert new_cert
    assert new_cert.key_type == "RSA2048"
    assert new_cert.organization != certificate.organization
    # Check for default value since authority does not have cab_compliant option set
    assert new_cert.organization == LEMUR_DEFAULT_ORGANIZATION
    assert new_cert.description.startswith(f"Reissued by Lemur for cert ID {certificate.id}")

    # update cab_compliant option to false for crypto_authority to maintain subject details
    update_options(crypto_authority.id, '[{"name": "cab_compliant","value":false}]')
    new_cert = reissue_certificate(certificate)
    assert new_cert.organization == certificate.organization


def test_create_csr():
    csr, private_key = create_csr(
        owner="joe@example.com",
        common_name="ACommonName",
        organization="test",
        organizational_unit="Meters",
        country="US",
        state="CA",
        location="Here",
        key_type="RSA2048",
    )
    assert csr
    assert private_key

    extensions = {
        "sub_alt_names": {
            "names": x509.SubjectAlternativeName([x509.DNSName("AnotherCommonName")])
        }
    }
    csr, private_key = create_csr(
        owner="joe@example.com",
        common_name="ACommonName",
        organization="test",
        organizational_unit="Meters",
        country="US",
        state="CA",
        location="Here",
        extensions=extensions,
        key_type="RSA2048",
    )
    assert csr
    assert private_key


def test_import(user):
    from lemur.certificates.service import import_certificate

    cert = import_certificate(
        body=SAN_CERT_STR,
        chain=INTERMEDIATE_CERT_STR,
        private_key=SAN_CERT_KEY,
        creator=user["user"],
    )
    assert str(cert.not_after) == "2047-12-31T22:00:00+00:00"
    assert str(cert.not_before) == "2017-12-31T22:00:00+00:00"
    assert cert.issuer == "LemurTrustUnittestsClass1CA2018"
    assert cert.name.startswith(
        "SAN-san.example.org-LemurTrustUnittestsClass1CA2018-20171231-20471231"
    )

    cert = import_certificate(
        body=SAN_CERT_STR,
        chain=INTERMEDIATE_CERT_STR,
        private_key=SAN_CERT_KEY,
        owner="joe@example.com",
        name="ACustomName2",
        creator=user["user"],
    )
    assert cert.name == "ACustomName2"


@pytest.mark.skip
def test_upload(user):
    from lemur.certificates.service import upload

    cert = upload(
        body=SAN_CERT_STR,
        chain=INTERMEDIATE_CERT_STR,
        private_key=SAN_CERT_KEY,
        owner="joe@example.com",
        creator=user["user"],
    )
    assert str(cert.not_after) == "2040-01-01T20:30:52+00:00"
    assert str(cert.not_before) == "2015-06-26T20:30:52+00:00"
    assert cert.issuer == "Example"
    assert cert.name == "long.lived.com-Example-20150626-20400101-3"

    cert = upload(
        body=SAN_CERT_STR,
        chain=INTERMEDIATE_CERT_STR,
        private_key=SAN_CERT_KEY,
        owner="joe@example.com",
        name="ACustomName",
        creator=user["user"],
    )
    assert "ACustomName" in cert.name


# verify upload with a private key as a str
def test_upload_private_key_str(user):
    from lemur.certificates.service import upload

    cert = upload(
        body=SAN_CERT_STR,
        chain=INTERMEDIATE_CERT_STR,
        private_key=SAN_CERT_KEY,
        owner="joe@example.com",
        name="ACustomName",
        creator=user["user"],
    )
    assert cert


@pytest.mark.parametrize(
    "token,status",
    [
        (VALID_USER_HEADER_TOKEN, 200),
        (VALID_ADMIN_HEADER_TOKEN, 200),
        (VALID_ADMIN_API_TOKEN, 200),
        ("", 401),
    ],
)
def test_certificate_get_private_key(client, token, status):
    assert (
        client.get(
            api.url_for(Certificates, certificate_id=1), headers=token
        ).status_code
        == status
    )


@pytest.mark.parametrize(
    "token,status",
    [
        (VALID_USER_HEADER_TOKEN, 200),
        (VALID_ADMIN_HEADER_TOKEN, 200),
        (VALID_ADMIN_API_TOKEN, 200),
        ("", 401),
    ],
)
def test_certificate_get(client, token, status):
    assert (
        client.get(
            api.url_for(Certificates, certificate_id=1), headers=token
        ).status_code
        == status
    )


def test_certificate_get_body(client):
    response_body = client.get(
        api.url_for(Certificates, certificate_id=1), headers=VALID_USER_HEADER_TOKEN
    ).json
    assert response_body["serial"] == "211983098819107449768450703123665283596"
    assert response_body["serialHex"] == "9F7A75B39DAE4C3F9524C68B06DA6A0C"
    assert response_body["distinguishedName"] == (
        "L=Earth,"
        "ST=N/A,"
        "C=EE,"
        "OU=Unittesting Operations Center,"
        "O=LemurTrust Enterprises Ltd,"
        "CN=LemurTrust Unittests Class 1 CA 2018"
    )

    # No authority details are provided in this test, no information about being cab_compliant is available.
    # Thus original subject details should be returned.
    assert response_body["country"] == "EE"
    assert response_body["state"] == "N/A"
    assert response_body["location"] == "Earth"
    assert response_body["organization"] == "LemurTrust Enterprises Ltd"
    assert response_body["organizationalUnit"] == "Unittesting Operations Center"


@pytest.mark.parametrize(
    "token,status",
    [
        (VALID_USER_HEADER_TOKEN, 403),
        (VALID_ADMIN_HEADER_TOKEN, 200),
        (VALID_ADMIN_API_TOKEN, 200),
        ("", 401),
    ],
)
def test_certificate_post_update_notify(client, certificate, token, status):
    # negate the current notify flag and pass it to update POST call to flip the notify
    toggled_notify = not certificate.notify

    response = client.post(
        api.url_for(Certificates, certificate_id=certificate.id),
        data=json.dumps({"notify": toggled_notify}),
        headers=token
    )

    assert response.status_code == status
    if status == 200:
        assert response.json.get("notify") == toggled_notify


@pytest.mark.parametrize(
    "token,status",
    [
        (VALID_USER_HEADER_TOKEN, 400),
        (VALID_ADMIN_HEADER_TOKEN, 400),
        (VALID_ADMIN_API_TOKEN, 400),
        ("", 401),
    ],
)
def test_certificate_put(client, token, status):
    assert (
        client.put(
            api.url_for(Certificates, certificate_id=1), data={}, headers=token
        ).status_code
        == status
    )


def test_certificate_put_with_data(client, certificate, issuer_plugin):
    resp = client.put(
        api.url_for(Certificates, certificate_id=certificate.id),
        data=json.dumps(
            {"owner": "bob@example.com", "description": "test", "notify": True}
        ),
        headers=VALID_ADMIN_HEADER_TOKEN,
    )
    assert resp.status_code == 200
    assert len(certificate.notifications) == 3
    assert certificate.roles[0].name == "bob@example.com"
    assert certificate.notify


@pytest.mark.parametrize(
    "token,status",
    [
        (VALID_USER_HEADER_TOKEN, 403),
        (VALID_ADMIN_HEADER_TOKEN, 204),
        (VALID_ADMIN_API_TOKEN, 412),
        ("", 401),
    ],
)
def test_certificate_delete(client, token, status):
    assert (
        client.delete(
            api.url_for(Certificates, certificate_id=1), headers=token
        ).status_code
        == status
    )


@pytest.mark.parametrize(
    "token,status",
    [
        (VALID_USER_HEADER_TOKEN, 403),
        (VALID_ADMIN_HEADER_TOKEN, 204),
        (VALID_ADMIN_API_TOKEN, 204),
        ("", 401),
    ],
)
def test_invalid_certificate_delete(client, invalid_certificate, token, status):
    assert (
        client.delete(
            api.url_for(Certificates, certificate_id=invalid_certificate.id),
            headers=token,
        ).status_code
        == status
    )


@pytest.mark.parametrize(
    "token,status",
    [
        (VALID_USER_HEADER_TOKEN, 405),
        (VALID_ADMIN_HEADER_TOKEN, 405),
        (VALID_ADMIN_API_TOKEN, 405),
        ("", 405),
    ],
)
def test_certificate_patch(client, token, status):
    assert (
        client.patch(
            api.url_for(Certificates, certificate_id=1), data={}, headers=token
        ).status_code
        == status
    )


@pytest.mark.parametrize(
    "token,status",
    [
        (VALID_USER_HEADER_TOKEN, 200),
        (VALID_ADMIN_HEADER_TOKEN, 200),
        (VALID_ADMIN_API_TOKEN, 200),
        ("", 401),
    ],
)
def test_certificates_get(client, token, status):
    assert (
        client.get(api.url_for(CertificatesList), headers=token).status_code == status
    )


@pytest.mark.parametrize(
    "token,status",
    [
        (VALID_USER_HEADER_TOKEN, 400),
        (VALID_ADMIN_HEADER_TOKEN, 400),
        (VALID_ADMIN_API_TOKEN, 400),
        ("", 401),
    ],
)
def test_certificates_post(client, token, status):
    assert (
        client.post(api.url_for(CertificatesList), data={}, headers=token).status_code
        == status
    )


@pytest.mark.parametrize(
    "token,status",
    [
        (VALID_USER_HEADER_TOKEN, 405),
        (VALID_ADMIN_HEADER_TOKEN, 405),
        (VALID_ADMIN_API_TOKEN, 405),
        ("", 405),
    ],
)
def test_certificates_put(client, token, status):
    assert (
        client.put(api.url_for(CertificatesList), data={}, headers=token).status_code
        == status
    )


@pytest.mark.parametrize(
    "token,status",
    [
        (VALID_USER_HEADER_TOKEN, 405),
        (VALID_ADMIN_HEADER_TOKEN, 405),
        (VALID_ADMIN_API_TOKEN, 405),
        ("", 405),
    ],
)
def test_certificates_delete(client, token, status):
    assert (
        client.delete(api.url_for(CertificatesList), headers=token).status_code
        == status
    )


@pytest.mark.parametrize(
    "token,status",
    [
        (VALID_USER_HEADER_TOKEN, 405),
        (VALID_ADMIN_HEADER_TOKEN, 405),
        (VALID_ADMIN_API_TOKEN, 405),
        ("", 405),
    ],
)
def test_certificates_patch(client, token, status):
    assert (
        client.patch(api.url_for(CertificatesList), data={}, headers=token).status_code
        == status
    )


@pytest.mark.parametrize(
    "token,status",
    [
        (VALID_USER_HEADER_TOKEN, 405),
        (VALID_ADMIN_HEADER_TOKEN, 405),
        (VALID_ADMIN_API_TOKEN, 405),
        ("", 405),
    ],
)
def test_certificate_credentials_post(client, token, status):
    assert (
        client.post(
            api.url_for(CertificatePrivateKey, certificate_id=1), data={}, headers=token
        ).status_code
        == status
    )


@pytest.mark.parametrize(
    "token,status",
    [
        (VALID_USER_HEADER_TOKEN, 405),
        (VALID_ADMIN_HEADER_TOKEN, 405),
        (VALID_ADMIN_API_TOKEN, 405),
        ("", 405),
    ],
)
def test_certificate_credentials_put(client, token, status):
    assert (
        client.put(
            api.url_for(CertificatePrivateKey, certificate_id=1), data={}, headers=token
        ).status_code
        == status
    )


@pytest.mark.parametrize(
    "token,status",
    [
        (VALID_USER_HEADER_TOKEN, 405),
        (VALID_ADMIN_HEADER_TOKEN, 405),
        (VALID_ADMIN_API_TOKEN, 405),
        ("", 405),
    ],
)
def test_certificate_credentials_delete(client, token, status):
    assert (
        client.delete(
            api.url_for(CertificatePrivateKey, certificate_id=1), headers=token
        ).status_code
        == status
    )


@pytest.mark.parametrize(
    "token,status",
    [
        (VALID_USER_HEADER_TOKEN, 405),
        (VALID_ADMIN_HEADER_TOKEN, 405),
        (VALID_ADMIN_API_TOKEN, 405),
        ("", 405),
    ],
)
def test_certificate_credentials_patch(client, token, status):
    assert (
        client.patch(
            api.url_for(CertificatePrivateKey, certificate_id=1), data={}, headers=token
        ).status_code
        == status
    )


@pytest.mark.parametrize(
    "token,status",
    [
        (VALID_USER_HEADER_TOKEN, 405),
        (VALID_ADMIN_HEADER_TOKEN, 405),
        (VALID_ADMIN_API_TOKEN, 405),
        ("", 405),
    ],
)
def test_certificates_upload_get(client, token, status):
    assert (
        client.get(api.url_for(CertificatesUpload), headers=token).status_code == status
    )


@pytest.mark.parametrize(
    "token,status",
    [
        (VALID_USER_HEADER_TOKEN, 400),
        (VALID_ADMIN_HEADER_TOKEN, 400),
        (VALID_ADMIN_API_TOKEN, 400),
        ("", 401),
    ],
)
def test_certificates_upload_post(client, token, status):
    assert (
        client.post(api.url_for(CertificatesUpload), data={}, headers=token).status_code
        == status
    )


@pytest.mark.parametrize(
    "token,status",
    [
        (VALID_USER_HEADER_TOKEN, 405),
        (VALID_ADMIN_HEADER_TOKEN, 405),
        (VALID_ADMIN_API_TOKEN, 405),
        ("", 405),
    ],
)
def test_certificates_upload_put(client, token, status):
    assert (
        client.put(api.url_for(CertificatesUpload), data={}, headers=token).status_code
        == status
    )


@pytest.mark.parametrize(
    "token,status",
    [
        (VALID_USER_HEADER_TOKEN, 405),
        (VALID_ADMIN_HEADER_TOKEN, 405),
        (VALID_ADMIN_API_TOKEN, 405),
        ("", 405),
    ],
)
def test_certificates_upload_delete(client, token, status):
    assert (
        client.delete(api.url_for(CertificatesUpload), headers=token).status_code
        == status
    )


@pytest.mark.parametrize(
    "token,status",
    [
        (VALID_USER_HEADER_TOKEN, 405),
        (VALID_ADMIN_HEADER_TOKEN, 405),
        (VALID_ADMIN_API_TOKEN, 405),
        ("", 405),
    ],
)
def test_certificates_upload_patch(client, token, status):
    assert (
        client.patch(
            api.url_for(CertificatesUpload), data={}, headers=token
        ).status_code
        == status
    )


def test_sensitive_sort(client):
    resp = client.get(
        api.url_for(CertificatesList) + "?sortBy=private_key&sortDir=asc",
        headers=VALID_ADMIN_HEADER_TOKEN,
    )
    assert "'private_key' is not sortable or filterable" in resp.json["message"]


def test_boolean_filter(client):
    resp = client.get(
        api.url_for(CertificatesList) + "?filter=notify;true",
        headers=VALID_ADMIN_HEADER_TOKEN,
    )
    assert resp.status_code == 200
    # Also don't crash with invalid input (we currently treat that as false)
    resp = client.get(
        api.url_for(CertificatesList) + "?filter=notify;whatisthis",
        headers=VALID_ADMIN_HEADER_TOKEN,
    )
    assert resp.status_code == 200


def test_issued_cert_count_for_authority(authority):
    from lemur.tests.factories import CertificateFactory
    from lemur.certificates.service import get_issued_cert_count_for_authority

    assert get_issued_cert_count_for_authority(authority) == 0

    # create a few certs issued by the authority
    CertificateFactory(authority=authority, name="test_issued_cert_count_for_authority1")
    CertificateFactory(authority=authority, name="test_issued_cert_count_for_authority2")
    CertificateFactory(authority=authority, name="test_issued_cert_count_for_authority3")

    assert get_issued_cert_count_for_authority(authority) == 3
