Code development platform for open source projects from the European Union institutions

Skip to content
Snippets Groups Projects
Commit 5d75e15e authored by Vincent SIMONIN's avatar Vincent SIMONIN
Browse files

:twisted_rightwards_arrows: Merge branch 'certificate_state' into 'main'

:sparkles: Add new certificate property `state`

See merge request !52
parents 8c2df14f 7c766a77
No related branches found
No related tags found
1 merge request!52✨ Add new certificate property `state`
Pipeline #113025 passed
Showing with 119 additions and 45 deletions
...@@ -23,6 +23,7 @@ class NestedCertificateSerializer(WritableNestedSerializer): ...@@ -23,6 +23,7 @@ class NestedCertificateSerializer(WritableNestedSerializer):
"expiration_time", "expiration_time",
"cert_created_at", "cert_created_at",
"cert_expired_at", "cert_expired_at",
"state",
"custom_fields", "custom_fields",
"created", "created",
"last_updated", "last_updated",
...@@ -48,6 +49,7 @@ class CertificateSerializer(NetBoxModelSerializer): ...@@ -48,6 +49,7 @@ class CertificateSerializer(NetBoxModelSerializer):
"expiration_time", "expiration_time",
"cert_created_at", "cert_created_at",
"cert_expired_at", "cert_expired_at",
"state",
"custom_fields", "custom_fields",
"created", "created",
"last_updated", "last_updated",
......
...@@ -19,6 +19,7 @@ class CertificateFilterSet(NetBoxModelFilterSet): ...@@ -19,6 +19,7 @@ class CertificateFilterSet(NetBoxModelFilterSet):
"alt_name", "alt_name",
"ca", "ca",
"expiration_time", "expiration_time",
"state",
"cert_created_at", "cert_created_at",
"cert_expired_at", "cert_expired_at",
) )
......
...@@ -8,7 +8,7 @@ from netbox.forms import ( ...@@ -8,7 +8,7 @@ from netbox.forms import (
) )
from utilities.forms.widgets import DatePicker from utilities.forms.widgets import DatePicker
from utilities.forms.fields import TagFilterField from utilities.forms.fields import TagFilterField
from .models import Certificate, ExpirationTimeChoices, CaChoices from .models import Certificate, ExpirationTimeChoices, CaChoices, StateChoices
class CertificateForm(NetBoxModelForm): class CertificateForm(NetBoxModelForm):
...@@ -21,6 +21,7 @@ class CertificateForm(NetBoxModelForm): ...@@ -21,6 +21,7 @@ class CertificateForm(NetBoxModelForm):
"alt_name", "alt_name",
"ca", "ca",
"expiration_time", "expiration_time",
"state",
"cert_created_at", "cert_created_at",
"cert_expired_at", "cert_expired_at",
"tags", "tags",
...@@ -31,6 +32,7 @@ class CertificateForm(NetBoxModelForm): ...@@ -31,6 +32,7 @@ class CertificateForm(NetBoxModelForm):
"alt_name": "Alt name", "alt_name": "Alt name",
"ca": "CA", "ca": "CA",
"expiration_time": "Expiration time", "expiration_time": "Expiration time",
"state": "Certificate state",
"cert_created_at": "Effective certificate's creation date", "cert_created_at": "Effective certificate's creation date",
"cert_expired_at": "Effective certificate's expiration date", "cert_expired_at": "Effective certificate's expiration date",
} }
...@@ -56,6 +58,9 @@ class CertificateFilterForm(NetBoxModelFilterSetForm): ...@@ -56,6 +58,9 @@ class CertificateFilterForm(NetBoxModelFilterSetForm):
expiration_time = forms.MultipleChoiceField( expiration_time = forms.MultipleChoiceField(
label="Expiration time", choices=ExpirationTimeChoices, required=False label="Expiration time", choices=ExpirationTimeChoices, required=False
) )
state = forms.MultipleChoiceField(
label="Certificate state", choices=StateChoices, required=False
)
cert_created_at = forms.DateField( cert_created_at = forms.DateField(
label="Effective certificate's creation date", required=False, widget=DatePicker label="Effective certificate's creation date", required=False, widget=DatePicker
) )
...@@ -77,11 +82,13 @@ class CertificateImportForm(NetBoxModelImportForm): ...@@ -77,11 +82,13 @@ class CertificateImportForm(NetBoxModelImportForm):
"alt_name", "alt_name",
"ca", "ca",
"expiration_time", "expiration_time",
"state",
"cert_created_at", "cert_created_at",
"cert_expired_at", "cert_expired_at",
) )
labels = { labels = {
"ca": "Certificate Authority. Can be 'letsencrypt', 'comisign', 'globalsign'", "ca": "Certificate Authority. Can be 'letsencrypt', 'comisign', 'globalsign'",
"expiration_time": "Expiration time needed. Can be '1m', '3m', '6m', '1y', '3y'", "expiration_time": "Expiration time needed. Can be '1m', '3m', '6m', '1y', '3y'",
"alt_name": "Alt name separated by commas, encased with double quotes (e.g. \"alt1,alt2,alt3\")" "state": "Certificate status. Can be 'pending', 'valid', 'to_be_renewed'",
"alt_name": 'Alt name separated by commas, encased with double quotes (e.g. "alt1,alt2,alt3")',
} }
"""Migration File"""
# pylint: disable=C0103
from django.db import migrations, models
class Migration(migrations.Migration):
"""Migration Class"""
dependencies = [
("netbox_cert_plugin", "0002_new_alt_name_array"),
]
operations = [
migrations.AlterModelOptions(
name="certificate",
options={"ordering": ["cn"]},
),
migrations.AddField(
model_name="certificate",
name="state",
field=models.CharField(default="valid", max_length=32),
),
migrations.AlterField(
model_name="certificate",
name="ca",
field=models.CharField(default="letsencrypt", max_length=32),
),
migrations.AlterField(
model_name="certificate",
name="expiration_time",
field=models.CharField(default="1m", max_length=2),
),
]
...@@ -37,6 +37,20 @@ class ExpirationTimeChoices(ChoiceSet): ...@@ -37,6 +37,20 @@ class ExpirationTimeChoices(ChoiceSet):
] ]
class StateChoices(ChoiceSet):
"""CA choices definition class"""
key = "Certificate.state"
DEFAULT_VALUE = "pending"
CHOICES = [
("pending", "Pending", "yellow"),
("valid", "Valid", "green"),
("to_be_renewed", "To be renewed", "blue"),
]
class Certificate(NetBoxModel): class Certificate(NetBoxModel):
"""Certificate definition class""" """Certificate definition class"""
...@@ -45,16 +59,14 @@ class Certificate(NetBoxModel): ...@@ -45,16 +59,14 @@ class Certificate(NetBoxModel):
blank=False, blank=False,
verbose_name="Common Name", verbose_name="Common Name",
unique=True, unique=True,
help_text="Unique Common Name" help_text="Unique Common Name",
) )
alt_name = ArrayField( alt_name = ArrayField(
base_field=models.CharField( base_field=models.CharField(max_length=256),
max_length=256
),
null=True, null=True,
blank=True, blank=True,
verbose_name="Alt Name", verbose_name="Alt Name",
help_text="Alt Name is a list of host separated by commas (e.g. alt1,alt2,alt3)" help_text="Alt Name is a list of host separated by commas (e.g. alt1,alt2,alt3)",
) )
ca = models.CharField( ca = models.CharField(
max_length=32, max_length=32,
...@@ -66,7 +78,7 @@ class Certificate(NetBoxModel): ...@@ -66,7 +78,7 @@ class Certificate(NetBoxModel):
expiration_time = models.CharField( expiration_time = models.CharField(
max_length=2, max_length=2,
choices=ExpirationTimeChoices, choices=ExpirationTimeChoices,
default=CaChoices.DEFAULT_VALUE, default=ExpirationTimeChoices.DEFAULT_VALUE,
blank=False, blank=False,
verbose_name="Expiration time needed", verbose_name="Expiration time needed",
) )
...@@ -76,6 +88,13 @@ class Certificate(NetBoxModel): ...@@ -76,6 +88,13 @@ class Certificate(NetBoxModel):
cert_expired_at = models.DateField( cert_expired_at = models.DateField(
blank=True, null=True, verbose_name="Effective certificate's expiration date" blank=True, null=True, verbose_name="Effective certificate's expiration date"
) )
state = models.CharField(
max_length=32,
choices=StateChoices,
default=StateChoices.DEFAULT_VALUE,
blank=False,
verbose_name="Certificate State",
)
class Meta: class Meta:
ordering = ["cn"] ordering = ["cn"]
......
...@@ -9,8 +9,4 @@ class CertificateIndex(SearchIndex): ...@@ -9,8 +9,4 @@ class CertificateIndex(SearchIndex):
"""Certificate search definition class""" """Certificate search definition class"""
model = Certificate model = Certificate
fields = ( fields = (("cn", 256), ("alt_name", 256), ("ca", 32), ("state", 32))
('cn', 256),
('alt_name', 256),
('ca', 32),
)
...@@ -19,16 +19,18 @@ class CertificateTable(NetBoxTable): ...@@ -19,16 +19,18 @@ class CertificateTable(NetBoxTable):
"cn", "cn",
"alt_name", "alt_name",
"ca", "ca",
"state",
"expiration_time", "expiration_time",
"cert_created_at", "cert_created_at",
"cert_expired_at", "cert_expired_at",
"tags" "tags",
) )
default_columns = ( default_columns = (
"cn", "cn",
"alt_name", "alt_name",
"ca", "ca",
"expiration_time", "expiration_time",
"state",
"cert_created_at", "cert_created_at",
"cert_expired_at", "cert_expired_at",
) )
...@@ -38,6 +38,10 @@ ...@@ -38,6 +38,10 @@
<th scope="row">Expiration time</th> <th scope="row">Expiration time</th>
<td>{{ object.get_expiration_time_display|placeholder }}</td> <td>{{ object.get_expiration_time_display|placeholder }}</td>
</tr> </tr>
<tr>
<th scope="row">State</th>
<td>{{ object.get_state_display|placeholder }}</td>
</tr>
<tr> <tr>
<th scope="row">Effective certificate's creation date</th> <th scope="row">Effective certificate's creation date</th>
<td>{{ object.cert_created_at|placeholder }}</td> <td>{{ object.cert_created_at|placeholder }}</td>
......
...@@ -22,11 +22,21 @@ class TestCertificateCreation(unittest.TestCase): ...@@ -22,11 +22,21 @@ class TestCertificateCreation(unittest.TestCase):
response = requests.post( response = requests.post(
url=f"http://{HOST}:{PORT}/api/plugins/cert/certificate/", url=f"http://{HOST}:{PORT}/api/plugins/cert/certificate/",
json={ json={"cn": "truc00.com", "ca": "letsencrypt", "expiration_time": "1m"},
"cn": "truc00.com", headers={"Authorization": f"Token {API_KEY}"},
"ca": "letsencrypt", timeout=5,
"expiration_time": "1m" )
},
self.assertEqual(response.status_code, 201)
self.mapping_id = json.loads(response.content)["id"]
def test_that_default_certificate_properties_is_set(self) -> None:
"""Test that default certificate properties is set"""
response = requests.post(
url=f"http://{HOST}:{PORT}/api/plugins/cert/certificate/",
json={"cn": "truc00.com", "ca": "letsencrypt"},
headers={"Authorization": f"Token {API_KEY}"}, headers={"Authorization": f"Token {API_KEY}"},
timeout=5, timeout=5,
) )
...@@ -35,16 +45,16 @@ class TestCertificateCreation(unittest.TestCase): ...@@ -35,16 +45,16 @@ class TestCertificateCreation(unittest.TestCase):
self.mapping_id = json.loads(response.content)["id"] self.mapping_id = json.loads(response.content)["id"]
content = json.loads(response.content)
self.assertEqual(content["expiration_time"], "1m")
self.assertEqual(content["state"], "pending")
def test_that_cn_is_unique(self) -> None: def test_that_cn_is_unique(self) -> None:
"""Test that CN is unique""" """Test that CN is unique"""
response = requests.post( response = requests.post(
url=f"http://{HOST}:{PORT}/api/plugins/cert/certificate/", url=f"http://{HOST}:{PORT}/api/plugins/cert/certificate/",
json={ json={"cn": "truc00.com", "ca": "letsencrypt", "expiration_time": "1m"},
"cn": "truc00.com",
"ca": "letsencrypt",
"expiration_time": "1m"
},
headers={"Authorization": f"Token {API_KEY}"}, headers={"Authorization": f"Token {API_KEY}"},
timeout=5, timeout=5,
) )
...@@ -55,51 +65,47 @@ class TestCertificateCreation(unittest.TestCase): ...@@ -55,51 +65,47 @@ class TestCertificateCreation(unittest.TestCase):
response = requests.post( response = requests.post(
url=f"http://{HOST}:{PORT}/api/plugins/cert/certificate/", url=f"http://{HOST}:{PORT}/api/plugins/cert/certificate/",
json={ json={"cn": "truc00.com", "ca": "commissign", "expiration_time": "3m"},
"cn": "truc00.com",
"ca": "commissign",
"expiration_time": "3m"
},
headers={"Authorization": f"Token {API_KEY}"}, headers={"Authorization": f"Token {API_KEY}"},
timeout=5, timeout=5,
) )
self.assertEqual(response.status_code, 400) self.assertEqual(response.status_code, 400)
self.assertEqual(response.content, b'{"cn":["certificate with this Common Name already exists."]}') self.assertEqual(
response.content,
b'{"cn":["certificate with this Common Name already exists."]}',
)
def test_that_ca_is_valid(self) -> None: def test_that_ca_is_valid(self) -> None:
"""Test that CA is valid""" """Test that CA is valid"""
response = requests.post( response = requests.post(
url=f"http://{HOST}:{PORT}/api/plugins/cert/certificate/", url=f"http://{HOST}:{PORT}/api/plugins/cert/certificate/",
json={ json={"cn": "truc00.com", "ca": "randomca", "expiration_time": "3m"},
"cn": "truc00.com",
"ca": "randomca",
"expiration_time": "3m"
},
headers={"Authorization": f"Token {API_KEY}"}, headers={"Authorization": f"Token {API_KEY}"},
timeout=5, timeout=5,
) )
self.assertEqual(response.status_code, 400) self.assertEqual(response.status_code, 400)
self.assertEqual(response.content, b'{"ca":["\\"randomca\\" is not a valid choice."]}') self.assertEqual(
response.content, b'{"ca":["\\"randomca\\" is not a valid choice."]}'
)
def test_that_expiration_time_is_valid(self) -> None: def test_that_expiration_time_is_valid(self) -> None:
"""Test that expiration time is valid""" """Test that expiration time is valid"""
response = requests.post( response = requests.post(
url=f"http://{HOST}:{PORT}/api/plugins/cert/certificate/", url=f"http://{HOST}:{PORT}/api/plugins/cert/certificate/",
json={ json={"cn": "truc00.com", "ca": "commissign", "expiration_time": "10m"},
"cn": "truc00.com",
"ca": "commissign",
"expiration_time": "10m"
},
headers={"Authorization": f"Token {API_KEY}"}, headers={"Authorization": f"Token {API_KEY}"},
timeout=5, timeout=5,
) )
self.assertEqual(response.status_code, 400) self.assertEqual(response.status_code, 400)
self.assertEqual(response.content, b'{"expiration_time":["\\"10m\\" is not a valid choice."]}') self.assertEqual(
response.content,
b'{"expiration_time":["\\"10m\\" is not a valid choice."]}',
)
def test_that_alt_name_is_an_array(self) -> None: def test_that_alt_name_is_an_array(self) -> None:
"""Test that Alt name is an array""" """Test that Alt name is an array"""
...@@ -110,14 +116,17 @@ class TestCertificateCreation(unittest.TestCase): ...@@ -110,14 +116,17 @@ class TestCertificateCreation(unittest.TestCase):
"cn": "truc00.com", "cn": "truc00.com",
"ca": "letsencrypt", "ca": "letsencrypt",
"expiration_time": "1m", "expiration_time": "1m",
"alt_name": "truc01.com" "alt_name": "truc01.com",
}, },
headers={"Authorization": f"Token {API_KEY}"}, headers={"Authorization": f"Token {API_KEY}"},
timeout=5, timeout=5,
) )
self.assertEqual(response.status_code, 400) self.assertEqual(response.status_code, 400)
self.assertEqual(response.content, b'{"alt_name":["Expected a list of items but got type \\"str\\"."]}') self.assertEqual(
response.content,
b'{"alt_name":["Expected a list of items but got type \\"str\\"."]}',
)
def test_that_certificate_is_created_with_alt_name(self) -> None: def test_that_certificate_is_created_with_alt_name(self) -> None:
"""Test that certificate is created""" """Test that certificate is created"""
...@@ -128,7 +137,7 @@ class TestCertificateCreation(unittest.TestCase): ...@@ -128,7 +137,7 @@ class TestCertificateCreation(unittest.TestCase):
"cn": "truc00.com", "cn": "truc00.com",
"ca": "commissign", "ca": "commissign",
"expiration_time": "1m", "expiration_time": "1m",
"alt_name": ["192.168.1.1", "truc01.com"] "alt_name": ["192.168.1.1", "truc01.com"],
}, },
headers={"Authorization": f"Token {API_KEY}"}, headers={"Authorization": f"Token {API_KEY}"},
timeout=5, timeout=5,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment