|
14 | 14 | from drf_spectacular.utils import extend_schema |
15 | 15 | from drf_spectacular.utils import extend_schema_view |
16 | 16 | from packageurl import PackageURL |
| 17 | +from rest_framework import mixins |
17 | 18 | from rest_framework import serializers |
18 | 19 | from rest_framework import status |
19 | 20 | from rest_framework import viewsets |
| 21 | +from rest_framework.authentication import SessionAuthentication |
20 | 22 | from rest_framework.decorators import action |
| 23 | +from rest_framework.permissions import BasePermission |
21 | 24 | from rest_framework.response import Response |
22 | 25 | from rest_framework.reverse import reverse |
23 | 26 |
|
24 | 27 | from vulnerabilities.models import CodeFix |
25 | 28 | from vulnerabilities.models import Package |
| 29 | +from vulnerabilities.models import PipelineRun |
| 30 | +from vulnerabilities.models import PipelineSchedule |
26 | 31 | from vulnerabilities.models import Vulnerability |
27 | 32 | from vulnerabilities.models import VulnerabilityReference |
28 | 33 | from vulnerabilities.models import VulnerabilitySeverity |
@@ -606,3 +611,146 @@ def get_queryset(self): |
606 | 611 | affected_package_vulnerability__vulnerability__vulnerability_id=vulnerability_id |
607 | 612 | ) |
608 | 613 | return queryset |
| 614 | + |
| 615 | + |
| 616 | +class CreateListRetrieveUpdateViewSet( |
| 617 | + mixins.CreateModelMixin, |
| 618 | + mixins.ListModelMixin, |
| 619 | + mixins.RetrieveModelMixin, |
| 620 | + mixins.UpdateModelMixin, |
| 621 | + viewsets.GenericViewSet, |
| 622 | +): |
| 623 | + """ |
| 624 | + A viewset that provides `create`, `list, `retrieve`, and `update` actions. |
| 625 | + To use it, override the class and set the `.queryset` and |
| 626 | + `.serializer_class` attributes. |
| 627 | + """ |
| 628 | + |
| 629 | + pass |
| 630 | + |
| 631 | + |
| 632 | +class IsAdminWithSessionAuth(BasePermission): |
| 633 | + """Permit only staff users authenticated via session (not token).""" |
| 634 | + |
| 635 | + def has_permission(self, request, view): |
| 636 | + is_authenticated = request.user and request.user.is_authenticated |
| 637 | + is_staff = request.user and request.user.is_staff |
| 638 | + is_session_auth = isinstance(request.successful_authenticator, SessionAuthentication) |
| 639 | + |
| 640 | + return is_authenticated and is_staff and is_session_auth |
| 641 | + |
| 642 | + |
| 643 | +class PipelineRunAPISerializer(serializers.HyperlinkedModelSerializer): |
| 644 | + status = serializers.SerializerMethodField() |
| 645 | + execution_time = serializers.SerializerMethodField() |
| 646 | + log = serializers.SerializerMethodField() |
| 647 | + |
| 648 | + class Meta: |
| 649 | + model = PipelineRun |
| 650 | + fields = [ |
| 651 | + "run_id", |
| 652 | + "status", |
| 653 | + "execution_time", |
| 654 | + "run_start_date", |
| 655 | + "run_end_date", |
| 656 | + "run_exitcode", |
| 657 | + "run_output", |
| 658 | + "created_date", |
| 659 | + "vulnerablecode_version", |
| 660 | + "vulnerablecode_commit", |
| 661 | + "log", |
| 662 | + ] |
| 663 | + |
| 664 | + def get_status(self, run): |
| 665 | + return run.status |
| 666 | + |
| 667 | + def get_execution_time(self, run): |
| 668 | + if run.execution_time: |
| 669 | + return round(run.execution_time, 2) |
| 670 | + |
| 671 | + def get_log(self, run): |
| 672 | + """Return only last 5000 character of log.""" |
| 673 | + return run.log[-5000:] |
| 674 | + |
| 675 | + |
| 676 | +class PipelineScheduleAPISerializer(serializers.HyperlinkedModelSerializer): |
| 677 | + url = serializers.HyperlinkedIdentityField( |
| 678 | + view_name="schedule-detail", lookup_field="pipeline_id" |
| 679 | + ) |
| 680 | + latest_run = serializers.SerializerMethodField() |
| 681 | + next_run_date = serializers.SerializerMethodField() |
| 682 | + |
| 683 | + class Meta: |
| 684 | + model = PipelineSchedule |
| 685 | + fields = [ |
| 686 | + "url", |
| 687 | + "pipeline_id", |
| 688 | + "is_active", |
| 689 | + "live_logging", |
| 690 | + "run_interval", |
| 691 | + "execution_timeout", |
| 692 | + "created_date", |
| 693 | + "schedule_work_id", |
| 694 | + "next_run_date", |
| 695 | + "latest_run", |
| 696 | + ] |
| 697 | + |
| 698 | + def get_next_run_date(self, schedule): |
| 699 | + return schedule.next_run_date |
| 700 | + |
| 701 | + def get_latest_run(self, schedule): |
| 702 | + if latest := schedule.pipelineruns.first(): |
| 703 | + return PipelineRunAPISerializer(latest).data |
| 704 | + return None |
| 705 | + |
| 706 | + |
| 707 | +class PipelineScheduleCreateSerializer(serializers.ModelSerializer): |
| 708 | + class Meta: |
| 709 | + model = PipelineSchedule |
| 710 | + fields = [ |
| 711 | + "pipeline_id", |
| 712 | + "is_active", |
| 713 | + "run_interval", |
| 714 | + "live_logging", |
| 715 | + "execution_timeout", |
| 716 | + ] |
| 717 | + extra_kwargs = { |
| 718 | + field: {"initial": PipelineSchedule._meta.get_field(field).get_default()} |
| 719 | + for field in [ |
| 720 | + "is_active", |
| 721 | + "run_interval", |
| 722 | + "live_logging", |
| 723 | + "execution_timeout", |
| 724 | + ] |
| 725 | + } |
| 726 | + |
| 727 | + |
| 728 | +class PipelineScheduleUpdateSerializer(serializers.ModelSerializer): |
| 729 | + class Meta: |
| 730 | + model = PipelineSchedule |
| 731 | + fields = [ |
| 732 | + "is_active", |
| 733 | + "run_interval", |
| 734 | + "live_logging", |
| 735 | + "execution_timeout", |
| 736 | + ] |
| 737 | + |
| 738 | + |
| 739 | +class PipelineScheduleV2ViewSet(CreateListRetrieveUpdateViewSet): |
| 740 | + queryset = PipelineSchedule.objects.prefetch_related("pipelineruns").all() |
| 741 | + serializer_class = PipelineScheduleAPISerializer |
| 742 | + lookup_field = "pipeline_id" |
| 743 | + lookup_value_regex = r"[\w.]+" |
| 744 | + |
| 745 | + def get_serializer_class(self): |
| 746 | + if self.action == "create": |
| 747 | + return PipelineScheduleCreateSerializer |
| 748 | + elif self.action == "update": |
| 749 | + return PipelineScheduleUpdateSerializer |
| 750 | + return super().get_serializer_class() |
| 751 | + |
| 752 | + def get_permissions(self): |
| 753 | + """Restrict addition and modifications to staff users authenticated via session.""" |
| 754 | + if self.action not in ["list", "retrieve"]: |
| 755 | + return [IsAdminWithSessionAuth()] |
| 756 | + return super().get_permissions() |
0 commit comments