Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[api] Rest API for PKI app #455

Merged
merged 33 commits into from
Jul 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
ec92a99
[api] Rest API for PKI app
ManishShah120 May 12, 2021
9f15ab7
[tests] Added more test for Ca model endpoints
ManishShah120 May 15, 2021
443402e
[tests] Added tests for Cert model endpoints
ManishShah120 May 17, 2021
0f40be5
[docs] Updated docs for the API endpoints of the PKI app
ManishShah120 May 20, 2021
84d02a5
[change] Fixed endpoint in the docs and added slash to the url
ManishShah120 May 20, 2021
0bfe2d9
[change] Removed `api_` prefix from pki app endpoints
ManishShah120 May 27, 2021
55e1e35
[api] Updated `crl_download` view with mixin
ManishShah120 May 31, 2021
7d958a1
[test] Fix `crl_view` failing tests
ManishShah120 May 31, 2021
3b7674b
[api] Added endpoint to download `crl`
ManishShah120 Jun 3, 2021
b8661e0
[change] Undo changes in requirements
ManishShah120 Jun 4, 2021
466da67
[api] Improved code quality
ManishShah120 Jun 7, 2021
a93805c
[change] Minor code improvements
ManishShah120 Jun 8, 2021
625738a
[tests] Added `assertNumQueries` from openwisp utils
ManishShah120 Jun 8, 2021
cecd615
[api] Fixed date field error
ManishShah120 Jun 21, 2021
2ccb28b
[tests] Added tests
ManishShah120 Jun 21, 2021
683d690
[api] Added enpoint for impoting existing Ca
ManishShah120 Jun 24, 2021
195b7d8
[docs] Updated API endpoints
ManishShah120 Jun 24, 2021
c89a5ac
[api] Added endpoint for importing
ManishShah120 Jun 25, 2021
7d12010
[api] Added import functionality
ManishShah120 Jun 28, 2021
5231b83
[api] Fixed validity date issues and docs improvements
ManishShah120 Jul 7, 2021
f96ba4e
[docs] Fixed changes in the import CA/Cert sections
ManishShah120 Jul 12, 2021
1b1b9e6
[api] Implemented `select_related` to decrease number of queries
ManishShah120 Jul 16, 2021
45db198
[api] Fixed `ca` and `extensions` fields
ManishShah120 Jul 16, 2021
d67ae09
Merge branch 'master' into rest-api-for-pki-app
nemesifier Jul 17, 2021
ae42efc
[tests] Fixed failing query checks
ManishShah120 Jul 17, 2021
44fed3c
[api] Added endpoints for `revoke` and `renew` for CA and Cert
ManishShah120 Jul 19, 2021
738aa63
[change] Returned the updated insance data after renew & revoke
ManishShah120 Jul 20, 2021
6130f5a
[api] Fixed response for `revoke` and `renew` endpoints
ManishShah120 Jul 20, 2021
91cd457
[change] Inherited respective base classes for renew & revoke
ManishShah120 Jul 20, 2021
4bf3fa4
[change] Minor code improvement
ManishShah120 Jul 23, 2021
d386202
[change] Inroduced `BaseListSerializer` containing common methods
ManishShah120 Jul 23, 2021
9abbb5c
[change] Included `extensions` field in the detail endpoint
ManishShah120 Jul 25, 2021
a020222
[change] Passphrase shall be only write only
nemesifier Jul 25, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 141 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -952,7 +952,8 @@ Download template configuration

GET /api/v1/controller/template/{id}/configuration/

The above endpoint triggers the download of a ``tar.gz`` file containing the generated configuration for that specific template.
The above endpoint triggers the download of a ``tar.gz`` file
containing the generated configuration for that specific template.

Change details of template
^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down Expand Up @@ -1003,7 +1004,8 @@ Download VPN configuration

GET /api/v1/controller/vpn/{id}/configuration/

The above endpoint triggers the download of a ``tar.gz`` file containing the generated configuration for that specific VPN.
The above endpoint triggers the download of a ``tar.gz`` file
containing the generated configuration for that specific VPN.

Change details of VPN
^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -1026,6 +1028,143 @@ Delete VPN

DELETE /api/v1/controller/vpn/{id}/

List CA
^^^^^^^

.. code-block:: text

GET /api/v1/controller/ca/

Create new CA
^^^^^^^^^^^^^

.. code-block:: text

POST /api/v1/controller/ca/

Import existing CA
^^^^^^^^^^^^^^^^^^

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you please spend a few words here to explain how to import a CA (specify the fields required to make the import cert work)?

.. code-block:: text

POST /api/v1/controller/ca/

**Note**: To import an existing CA, only ``name``, ``certificate``
and ``private_key`` fields have to be filled in the ``HTML`` form or
included in the ``JSON`` format.

Get CA Detail
^^^^^^^^^^^^^

.. code-block:: text

GET /api/v1/controller/ca/{id}/

Change details of CA
^^^^^^^^^^^^^^^^^^^^

.. code-block:: text

PUT /api/v1/controller/ca/{id}/

Patch details of CA
^^^^^^^^^^^^^^^^^^^

.. code-block:: text

PATCH /api/v1/controller/ca/{id}/

Download CA(crl)
^^^^^^^^^^^^^^^^

.. code-block:: text

GET /api/v1/controller/ca/{id}/crl/

The above endpoint triggers the download of ``{id}.crl`` file containing
up to date CRL of that specific CA.

Delete CA
^^^^^^^^^

.. code-block:: text

DELETE /api/v1/controller/ca/{id}/

Renew CA
^^^^^^^^

.. code-block:: text

POST /api/v1/controller/ca/{id}/renew/

List Cert
^^^^^^^^^

.. code-block:: text

GET /api/v1/controller/cert/

Create new Cert
^^^^^^^^^^^^^^^

.. code-block:: text

POST /api/v1/controller/cert/

Import existing Cert
^^^^^^^^^^^^^^^^^^^^

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you please spend a few words here to explain how to import a cert (specify the fields required to make the import cert work)?

.. code-block:: text

POST /api/v1/controller/cert/

**Note**: To import an existing Cert, only ``name``, ``ca``,
``certificate`` and ``private_key`` fields have to be filled
in the ``HTML`` form or included in the ``JSON`` format.

Get Cert Detail
^^^^^^^^^^^^^^^

.. code-block:: text

GET /api/v1/controller/cert/{id}/

Change details of Cert
^^^^^^^^^^^^^^^^^^^^^^

.. code-block:: text

PUT /api/v1/controller/cert/{id}/

Patch details of Cert
^^^^^^^^^^^^^^^^^^^^^

.. code-block:: text

PATCH /api/v1/controller/cert/{id}/

Delete Cert
^^^^^^^^^^^

.. code-block:: text

DELETE /api/v1/controller/cert/{id}/

Renew Cert
^^^^^^^^^^

.. code-block:: text

POST /api/v1/controller/cert/{id}/renew/

Revoke Cert
^^^^^^^^^^^

.. code-block:: text

POST /api/v1/controller/cert/{id}/revoke/

Default Alerts / Notifications
------------------------------

Expand Down
191 changes: 191 additions & 0 deletions openwisp_controller/pki/api/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
from django.utils.translation import ugettext_lazy as _
from django_x509.base.models import (
default_ca_validity_end,
default_cert_validity_end,
default_validity_start,
)
from rest_framework import serializers
from swapper import load_model

from openwisp_users.api.mixins import FilterSerializerByOrgManaged
from openwisp_utils.api.serializers import ValidatedModelSerializer

Ca = load_model('django_x509', 'Ca')
Cert = load_model('django_x509', 'Cert')


class BaseSerializer(FilterSerializerByOrgManaged, ValidatedModelSerializer):
pass


class BaseListSerializer(BaseSerializer):
extensions = serializers.JSONField(
initial=[],
help_text=_('additional x509 certificate extensions'),
required=False,
)

def get_import_data_hook(self, instance):
return get_import_data(instance)

def validate(self, data):
instance = self.instance or self.Meta.model(**data)
instance.full_clean()
if data.get('certificate') and data.get('private_key'):
data = self.get_import_data_hook(instance)
return data

def validate_validity_start(self, value):
if value is None:
value = default_validity_start()
return value

def default_validity_end_hook(self):
return default_ca_validity_end()

def validate_validity_end(self, value):
if value is None:
value = self.default_validity_end_hook()
return value


class CaListSerializer(BaseListSerializer):
class Meta:
model = Ca
fields = [
'id',
'name',
'organization',
'notes',
'key_length',
'digest',
'validity_start',
'validity_end',
ManishShah120 marked this conversation as resolved.
Show resolved Hide resolved
'country_code',
'state',
'city',
'organization_name',
'organizational_unit_name',
'email',
'common_name',
'extensions',
'serial_number',
'certificate',
'private_key',
'passphrase',
'created',
'modified',
]
read_only_fields = ['created', 'modified']
extra_kwargs = {
'organization': {'required': True},
'key_length': {'initial': '2048'},
'digest': {'initial': 'sha256'},
'passphrase': {'write_only': True},
'validity_start': {'default': default_validity_start()},
'validity_end': {'default': default_ca_validity_end()},
}


def get_ca_detail_fields(fields):
"""
Returns the fields for the `CADetailSerializer`.
"""
fields.remove('passphrase')
return fields


class CaDetailSerializer(BaseSerializer):
extensions = serializers.JSONField(read_only=True)

class Meta:
model = Ca
fields = get_ca_detail_fields(CaListSerializer.Meta.fields[:])
read_only_fields = fields[4:]


class CaRenewSerializer(CaDetailSerializer):
class Meta(CaDetailSerializer.Meta):
read_only_fields = CaDetailSerializer.Meta.fields


def get_import_data(instance):
data = {
'name': instance.name,
'organization': instance.organization,
'key_length': instance.key_length,
'digest': instance.digest,
'validity_start': instance.validity_start,
'validity_end': instance.validity_end,
'country_code': instance.country_code,
'state': instance.state,
'city': instance.city,
'organization_name': instance.organization_name,
'organizational_unit_name': instance.organizational_unit_name,
'email': instance.email,
'common_name': instance.common_name,
'extensions': instance.extensions,
'serial_number': instance.serial_number,
'certificate': instance.certificate,
'private_key': instance.private_key,
'passphrase': instance.passphrase,
}
return data


def get_cert_list_fields(fields):
"""
Returns the fields for the `CertListSerializer`.
"""
fields.insert(3, 'ca')
fields.insert(5, 'revoked')
fields.insert(6, 'revoked_at')
return fields


class CertListSerializer(BaseListSerializer):
include_shared = True

class Meta:
model = Cert
fields = get_cert_list_fields(CaListSerializer.Meta.fields[:])
read_only_fields = ['created', 'modified']
extra_kwargs = {
'revoked': {'read_only': True},
'revoked_at': {'read_only': True},
'key_length': {'initial': '2048'},
'digest': {'initial': 'sha256'},
'passphrase': {'write_only': True},
'validity_start': {'default': default_validity_start()},
'validity_end': {'default': default_cert_validity_end()},
}

def get_import_data_hook(self, instance):
data = super().get_import_data_hook(instance)
data.update({'ca': instance.ca})
return data
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks mostly the same as the Ca serializer


def default_validity_end_hook(self):
return default_cert_validity_end()


def get_cert_detail_fields(fields):
"""
Returns the fields for the `CertDetailSerializer`.
"""
fields.remove('passphrase')
return fields


class CertDetailSerializer(BaseSerializer):
extensions = serializers.JSONField(read_only=True)

class Meta:
model = Cert
fields = get_cert_detail_fields(CertListSerializer.Meta.fields[:])
read_only_fields = ['ca'] + fields[5:]


class CertRevokeRenewSerializer(CertDetailSerializer):
class Meta(CertDetailSerializer.Meta):
read_only_fields = CertDetailSerializer.Meta.fields
42 changes: 42 additions & 0 deletions openwisp_controller/pki/api/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from django.conf import settings
from django.urls import path

from . import views as api_views

app_name = 'openwisp_controller'


def get_pki_api_urls(api_views):
"""
returns:: all the API urls of the PKI app
"""
if getattr(settings, 'OPENWISP_CONTROLLER_PKI_API', True):
return [
path('controller/ca/', api_views.ca_list, name='ca_list'),
path('controller/ca/<str:pk>/', api_views.ca_detail, name='ca_detail'),
path('controller/ca/<str:pk>/renew/', api_views.ca_renew, name='ca_renew'),
path(
'controller/ca/<str:pk>/crl',
api_views.crl_download,
name='crl_download',
),
path('controller/cert/', api_views.cert_list, name='cert_list'),
path(
'controller/cert/<str:pk>/', api_views.cert_detail, name='cert_detail'
),
path(
'controller/cert/<str:pk>/revoke/',
api_views.cert_revoke,
name='cert_revoke',
),
path(
'controller/cert/<str:pk>/renew/',
api_views.cert_renew,
name='cert_renew',
),
]
else:
return []


urlpatterns = get_pki_api_urls(api_views)
Loading