-
Notifications
You must be signed in to change notification settings - Fork 171
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
Changes from 12 commits
ec92a99
9f15ab7
443402e
0f40be5
84d02a5
0bfe2d9
55e1e35
7d958a1
3b7674b
b8661e0
466da67
a93805c
625738a
cecd615
2ccb28b
683d690
195b7d8
c89a5ac
7d12010
5231b83
f96ba4e
1b1b9e6
45db198
d67ae09
ae42efc
44fed3c
738aa63
6130f5a
91cd457
4bf3fa4
d386202
9abbb5c
a020222
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -898,6 +898,100 @@ Delete VPN | |
|
||
DELETE /api/v1/controller/vpn/{pk}/ | ||
|
||
List CA | ||
^^^^^^^ | ||
|
||
.. code-block:: text | ||
|
||
GET /api/v1/pki/ca/ | ||
|
||
Create CA | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
------------------------------ | ||
|
||
|
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): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here as for |
||
""" | ||
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): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. two doubts here:
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): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:] |
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'), | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||||||||||||
path('ca/<str:pk>/', api_views.ca_detail, name='ca_detail'), | ||||||||||||||||||||||||||
path('ca/<str:pk>/crl', api_views.crl_download, name='crl_download'), | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||||||||||||
path('cert/', api_views.cert_list, name='cert_list'), | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
@atb00ker By this do you mean There was a problem hiding this comment. Choose a reason for hiding this commentThe 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! There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, 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'), | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||||||||||||
] | ||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||
return [] | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
urlpatterns = get_pki_api_urls(api_views) |
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() |
There was a problem hiding this comment.
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.