jumpserver/apps/terminal/api/applet/applet.py

123 lines
4.6 KiB
Python
Raw Normal View History

2022-10-25 04:57:34 +00:00
import shutil
import zipfile
2022-11-01 12:37:04 +00:00
from typing import Callable
2022-10-25 04:57:34 +00:00
import yaml
2022-10-28 10:19:44 +00:00
import os.path
2022-10-25 11:31:13 +00:00
2022-10-25 04:57:34 +00:00
from rest_framework import viewsets
2022-11-01 10:40:42 +00:00
from django.http import HttpResponse
2022-11-02 03:08:13 +00:00
from django.core.files.storage import default_storage
from django.db.models import Prefetch
2022-10-25 04:57:34 +00:00
from rest_framework.decorators import action
2022-11-01 12:37:04 +00:00
from rest_framework.request import Request
2022-10-25 04:57:34 +00:00
from rest_framework.response import Response
2022-10-25 11:31:13 +00:00
from rest_framework.serializers import ValidationError
2022-10-25 04:57:34 +00:00
2022-10-28 10:19:44 +00:00
from terminal import serializers
from terminal.models import AppletPublication, Applet
2022-10-25 04:57:34 +00:00
from terminal.serializers import AppletUploadSerializer
2022-10-28 10:19:44 +00:00
__all__ = ['AppletViewSet', 'AppletPublicationViewSet']
2022-11-01 12:37:04 +00:00
class DownloadUploadMixin:
get_serializer: Callable
request: Request
get_object: Callable
2022-10-25 11:31:13 +00:00
def extract_and_check_file(self, request):
serializer = self.get_serializer(data=self.request.data)
2022-10-25 04:57:34 +00:00
serializer.is_valid(raise_exception=True)
file = serializer.validated_data['file']
save_to = 'applets/{}'.format(file.name + '.tmp.zip')
if default_storage.exists(save_to):
default_storage.delete(save_to)
rel_path = default_storage.save(save_to, file)
path = default_storage.path(rel_path)
extract_to = default_storage.path('applets/{}.tmp'.format(file.name))
if os.path.exists(extract_to):
shutil.rmtree(extract_to)
with zipfile.ZipFile(path) as zp:
if zp.testzip() is not None:
return Response({'msg': 'Invalid Zip file'}, status=400)
zp.extractall(extract_to)
tmp_dir = os.path.join(extract_to, file.name.replace('.zip', ''))
2022-11-01 11:06:35 +00:00
files = ['manifest.yml', 'icon.png', 'i18n.yml', 'setup.yml']
2022-10-25 04:57:34 +00:00
for name in files:
path = os.path.join(tmp_dir, name)
if not os.path.exists(path):
2022-10-25 11:31:13 +00:00
raise ValidationError({'error': 'Missing file {}'.format(name)})
2022-10-25 04:57:34 +00:00
with open(os.path.join(tmp_dir, 'manifest.yml')) as f:
manifest = yaml.safe_load(f)
2022-10-25 11:31:13 +00:00
if not manifest.get('name', ''):
raise ValidationError({'error': 'Missing name in manifest.yml'})
return manifest, tmp_dir
@action(detail=False, methods=['post'], serializer_class=AppletUploadSerializer)
def upload(self, request, *args, **kwargs):
manifest, tmp_dir = self.extract_and_check_file(request)
name = manifest['name']
update = request.query_params.get('update')
2022-10-28 10:19:44 +00:00
instance = Applet.objects.filter(name=name).first()
2022-10-25 04:57:34 +00:00
if instance and not update:
return Response({'error': 'Applet already exists: {}'.format(name)}, status=400)
serializer = serializers.AppletSerializer(data=manifest, instance=instance)
serializer.is_valid(raise_exception=True)
save_to = default_storage.path('applets/{}'.format(name))
if os.path.exists(save_to):
shutil.rmtree(save_to)
shutil.move(tmp_dir, save_to)
serializer.save()
return Response(serializer.data, status=201)
2022-11-01 10:40:42 +00:00
@action(detail=True, methods=['get'])
def download(self, request, *args, **kwargs):
2022-11-01 12:37:04 +00:00
instance = self.get_object()
2022-11-01 10:40:42 +00:00
path = default_storage.path('applets/{}'.format(instance.name))
zip_path = shutil.make_archive(path, 'zip', path)
with open(zip_path, 'rb') as f:
response = HttpResponse(f.read(), status=200, content_type='application/octet-stream')
response['Content-Disposition'] = 'attachment; filename*=UTF-8\'\'{}.zip'.format(instance.name)
return response
2022-10-25 04:57:34 +00:00
2022-11-01 12:37:04 +00:00
class AppletViewSet(DownloadUploadMixin, viewsets.ModelViewSet):
queryset = Applet.objects.all()
serializer_class = serializers.AppletSerializer
rbac_perms = {
'upload': 'terminal.add_applet',
'download': 'terminal.view_applet',
}
2022-11-02 03:08:13 +00:00
def get_queryset(self):
queryset = super().get_queryset()
2022-11-01 12:37:04 +00:00
host = self.request.query_params.get('host')
2022-11-02 03:08:13 +00:00
if not host:
return queryset.prefetch_related('publications')
else:
publications = AppletPublication.objects.filter(host_id=host)
return queryset.prefetch_related(Prefetch('publications', queryset=publications))
2022-11-01 12:37:04 +00:00
def perform_destroy(self, instance):
if not instance.name:
raise ValidationError('Applet is not null')
path = default_storage.path('applets/{}'.format(instance.name))
if os.path.exists(path):
shutil.rmtree(path)
instance.delete()
2022-10-25 04:57:34 +00:00
class AppletPublicationViewSet(viewsets.ModelViewSet):
2022-10-28 10:19:44 +00:00
queryset = AppletPublication.objects.all()
2022-10-25 04:57:34 +00:00
serializer_class = serializers.AppletPublicationSerializer