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 12 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
94 changes: 94 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,100 @@ Delete VPN

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

List CA
^^^^^^^

.. code-block:: text

GET /api/v1/pki/ca/
Copy link
Member

Choose a reason for hiding this comment

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

as shared in the gitter chat, let's follow the URL naming consistently with the other sub apps, so /api/v1/<module-name>/<resource-name>/ which translates to /api/v1/controller/ca/, applies to other occurrences.


Create CA
Copy link
Member

Choose a reason for hiding this comment

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

I'd specify "Create new CA"

^^^^^^^^^

.. code-block:: text

POST /api/v1/pki/ca/

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

.. code-block:: text

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

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

.. code-block:: text

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

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

.. code-block:: text

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

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

.. code-block:: text

GET /api/v1/pki/ca/{id}/crl
Copy link
Member

Choose a reason for hiding this comment

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

final slash missing


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/pki/ca/{id}/

List Cert
^^^^^^^^^

.. code-block:: text

GET /api/v1/pki/cert/

Create Cert
Copy link
Member

Choose a reason for hiding this comment

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

I'd specify "Create new Cert"

^^^^^^^^^^^

.. code-block:: text

POST /api/v1/pki/cert/

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

.. code-block:: text

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

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

.. code-block:: text

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

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

.. code-block:: text

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

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

.. code-block:: text

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

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

Expand Down
144 changes: 144 additions & 0 deletions openwisp_controller/pki/api/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
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 CaListSerializer(BaseSerializer):

extensions = serializers.JSONField(
initial=[],
help_text=_('additional x509 certificate extensions'),
write_only=True,
required=False,
)

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',
'passphrase',
'created',
'modified',
]
read_only_fields = ['created', 'modified']
extra_kwargs = {
'organization': {'required': True},
'key_length': {'initial': '2048'},
'digest': {'initial': 'sha256'},
'validity_start': {
'initial': default_validity_start(),
'default': default_validity_start(),
},
'validity_end': {
'initial': default_ca_validity_end(),
'default': default_ca_validity_end(),
},
}


def CaDetail_fields(fields=None):
Copy link
Member

Choose a reason for hiding this comment

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

same here as for get_cert_list_fields

"""
Returns the fields for the `CADetailSerializer`.
"""
fields.remove('extensions')
fields.remove('passphrase')
fields.insert(16, 'certificate')
fields.insert(17, 'private_key')
return fields


class CaDetailSerializer(BaseSerializer):
class Meta:
model = Ca
fields = CaDetail_fields(CaListSerializer.Meta.fields[:])
read_only_fields = fields[4:]


def CertList_fields(fields=None):
Copy link
Member

Choose a reason for hiding this comment

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

two doubts here:

  • why the default argument is None? it looks like there should be no default argument
  • why this naming? Capital letter is used for classes, I think this should be named get_cert_list_fields

Eg:

def get_cert_list_fields(fields):
    pass

"""
Returns the fields for the `CertListSerializer`.
"""
fields.insert(3, 'ca')
fields.insert(5, 'revoked')
return fields


class CertListSerializer(BaseSerializer):

extensions = serializers.JSONField(
initial=[],
help_text=_('additional x509 certificate extensions'),
write_only=True,
required=False,
)
include_shared = True

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


def CertDetail_fields(fields=None):
Copy link
Member

Choose a reason for hiding this comment

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

same here

"""
Returns the fields for the `CertDetailSerializer`.
"""
fields.remove('extensions')
fields.remove('passphrase')
fields.insert(6, 'revoked_at')
fields.insert(19, 'certificate')
fields.insert(20, 'private_key')
return fields


class CertDetailSerializer(BaseSerializer):
ca = serializers.StringRelatedField()

class Meta:
model = Cert
fields = CertDetail_fields(CertListSerializer.Meta.fields[:])
read_only_fields = fields[5:]
25 changes: 25 additions & 0 deletions openwisp_controller/pki/api/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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('ca/', api_views.ca_list, name='ca_list'),
Copy link
Member

Choose a reason for hiding this comment

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

SN /api/v1/pki/ca/ Status Details
1 [SU][O1A][O1O] Can view only appropriate ca Pass
2 [SU][O1A][O1O] Can create only appropriate ca Pass
3 [SU][O1A] All errors reported together Pass

path('ca/<str:pk>/', api_views.ca_detail, name='ca_detail'),
path('ca/<str:pk>/crl', api_views.crl_download, name='crl_download'),
Copy link
Member

Choose a reason for hiding this comment

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

SN /api/v1/pki/ca/str:id Status Details
1 [SU][O1A][O1O] Can view only appropriate Cas Pass
2 [SU][O1A][O1O] Can edit only appropriate Cas Pass
3 [SU][O1A] Correct data is shown Pass
4 [SU][01A] Can delete only appropriate data Pass
5 [SU][O1A][O1O] Anyone can download CRLs Pass

path('cert/', api_views.cert_list, name='cert_list'),
Copy link
Member

Choose a reason for hiding this comment

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

SN /api/v1/pki/cert/ Status Details
1 [SU][O1A][O1O] Can view only appropriate ca Pass
2 [SU][O1A][O1O] Can create only appropriate ca Fail When trying to import certificate, I need to provide validity period.
3 [SU][O1A] All errors reported together Pass

Copy link
Member Author

Choose a reason for hiding this comment

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

When trying to import certificate, I need to provide validity period.

@atb00ker By this do you mean validity_start & validity_end, this fields are not required fields, and even if the data get''s sent in a POST request, it gets replaced with the cert data from which it is imported.

Copy link
Member

Choose a reason for hiding this comment

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

@ManishShah120 Are you sure they are not required? It wouldn't let me submit on firefox without these fields!

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, I am able to submit it, I also tested it in JSON format form.

Copy link
Member

Choose a reason for hiding this comment

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

I confirm that I have been able to import as expected and without issues on Chrome,
while on Firefox as @atb00ker noted, the validity start and end fields are somehow prefilled and hence submitting the form fails with a validation error unless the prefilled value is edited manually.

Not a big deal, because this will happen only when using it from the browsable API. @ManishShah120 can you double check on Firefox and see if you can force the default value to be empty there too?

path('cert/<str:pk>/', api_views.cert_detail, name='cert_detail'),
Copy link
Member

Choose a reason for hiding this comment

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

SN /api/v1/pki/cert/str:pk Status Details
1 [SU][O1A][O1O] Can view only appropriate Cert Pass
2 [SU][O1A][O1O] Can edit only appropriate Cert Pass
3 [SU][O1A] Correct data is shown Pass
4 [SU][01A] Can delete only appropriate data Pass

]
else:
return []


urlpatterns = get_pki_api_urls(api_views)
78 changes: 78 additions & 0 deletions openwisp_controller/pki/api/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from rest_framework import pagination
from rest_framework.authentication import SessionAuthentication
from rest_framework.generics import (
ListCreateAPIView,
RetrieveAPIView,
RetrieveUpdateDestroyAPIView,
)
from rest_framework.permissions import DjangoModelPermissions, IsAuthenticated
from swapper import load_model

from openwisp_users.api.authentication import BearerAuthentication
from openwisp_users.api.mixins import FilterByOrganizationManaged

from .serializers import (
CaDetailSerializer,
CaListSerializer,
CertDetailSerializer,
CertListSerializer,
)

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


class ListViewPagination(pagination.PageNumberPagination):
page_size = 10
page_size_query_param = 'page_size'
max_page_size = 100


class ProtectedAPIMixin(FilterByOrganizationManaged):
authentication_classes = [BearerAuthentication, SessionAuthentication]
permission_classes = [
IsAuthenticated,
DjangoModelPermissions,
]


class CaListCreateView(ProtectedAPIMixin, ListCreateAPIView):
serializer_class = CaListSerializer
queryset = Ca.objects.order_by('-created')
pagination_class = ListViewPagination


class CaDetailView(ProtectedAPIMixin, RetrieveUpdateDestroyAPIView):
serializer_class = CaDetailSerializer
queryset = Ca.objects.all()


class CrlDownloadView(ProtectedAPIMixin, RetrieveAPIView):
serializer_class = CaDetailSerializer
queryset = Ca.objects.none()

def retrieve(self, request, *args, **kwargs):
instance = get_object_or_404(Ca, pk=kwargs['pk'])
return HttpResponse(
instance.crl, status=200, content_type='application/x-pem-file'
)
ManishShah120 marked this conversation as resolved.
Show resolved Hide resolved


class CertListCreateView(ProtectedAPIMixin, ListCreateAPIView):
serializer_class = CertListSerializer
queryset = Cert.objects.order_by('-created')
pagination_class = ListViewPagination


class CertDetailView(ProtectedAPIMixin, RetrieveUpdateDestroyAPIView):
serializer_class = CertDetailSerializer
queryset = Cert.objects.all()


ca_list = CaListCreateView.as_view()
ca_detail = CaDetailView.as_view()
cert_list = CertListCreateView.as_view()
cert_detail = CertDetailView.as_view()
crl_download = CrlDownloadView.as_view()
Loading