Skip to content

Commit 243e5bb

Browse files
authored
Merge branch 'main' into remove-admin-panel
2 parents c3d95a8 + e316059 commit 243e5bb

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+3630
-46
lines changed

.VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
refs=$Format:%D$
2-
commit=$Format:%H$
2+
commit=$Format:%h$
33
abbrev_commit=$Format:%H$

.dockerignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ docker-compose.yml
66

77

88
# Ignore Git directory and files and github directory.
9-
**/.git
109
**/.gitignore
1110
**/.gitattributes
1211
**/.gitmodules

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.VERSION export-subst

Dockerfile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,21 @@ ENV PYTHONDONTWRITEBYTECODE 1
1717

1818
RUN mkdir -p /var/vulnerablecode/static
1919

20+
RUN apt-get update \
21+
&& apt-get install -y --no-install-recommends \
22+
wait-for-it \
23+
&& apt-get clean \
24+
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
25+
2026
# Keep the dependencies installation before the COPY of the app/ for proper caching
2127
COPY setup.cfg setup.py requirements.txt pyproject.toml /app/
2228
RUN pip install . -c requirements.txt
2329

2430
COPY . /app
31+
32+
# Store commit hash for docker deployment from local checkout.
33+
RUN if [ -d ".git" ]; then \
34+
GIT_COMMIT=$(git rev-parse --short HEAD) && \
35+
echo "VULNERABLECODE_GIT_COMMIT=\"$GIT_COMMIT\"" >> /app/vulnerablecode/settings.py; \
36+
rm -rf .git; \
37+
fi

docker-compose.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@ services:
1010
- db_data:/var/lib/postgresql/data/
1111
- ./etc/postgresql/postgresql.conf:/etc/postgresql/postgresql.conf
1212

13+
vulnerablecode_redis:
14+
image: redis
15+
# Enable redis data persistence using the "Append Only File" with the
16+
# default policy of fsync every second. See https://redis.io/topics/persistence
17+
command: redis-server --appendonly yes
18+
volumes:
19+
- vulnerablecode_redis_data:/data
20+
restart: always
21+
1322
vulnerablecode:
1423
build: .
1524
command: /bin/sh -c "
@@ -26,6 +35,31 @@ services:
2635
depends_on:
2736
- db
2837

38+
vulnerablecode_scheduler:
39+
build: .
40+
command: wait-for-it web:8000 -- python ./manage.py run_scheduler
41+
env_file:
42+
- docker.env
43+
volumes:
44+
- /etc/vulnerablecode/:/etc/vulnerablecode/
45+
depends_on:
46+
- vulnerablecode_redis
47+
- db
48+
- vulnerablecode
49+
50+
vulnerablecode_rqworker:
51+
build: .
52+
command: wait-for-it web:8000 -- python ./manage.py rqworker default
53+
env_file:
54+
- docker.env
55+
volumes:
56+
- /etc/vulnerablecode/:/etc/vulnerablecode/
57+
depends_on:
58+
- vulnerablecode_redis
59+
- db
60+
- vulnerablecode
61+
62+
2963
nginx:
3064
image: nginx
3165
ports:
@@ -44,4 +78,5 @@ services:
4478
volumes:
4579
db_data:
4680
static:
81+
vulnerablecode_redis_data:
4782

docker.env

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ POSTGRES_PASSWORD=vulnerablecode
44

55
VULNERABLECODE_DB_HOST=db
66
VULNERABLECODE_STATIC_ROOT=/var/vulnerablecode/static/
7+
8+
VULNERABLECODE_REDIS_HOST=vulnerablecode_redis

docs/source/conf.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
"https://example.org/api/non-existent-packages",
3939
"https://github.com/aboutcode-org/vulnerablecode/pull/495/commits",
4040
"https://nvd.nist.gov/products/cpe",
41+
"https://ftp.suse.com/pub/projects/security/yaml/suse-cvss-scores.yaml",
42+
"http://ftp.suse.com/pub/projects/security/yaml/",
4143
]
4244

4345
# Add any Sphinx extension module names here, as strings. They can be

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,10 @@ python-dateutil==2.8.2
9292
python-dotenv==0.20.0
9393
pytz==2022.1
9494
PyYAML==6.0.1
95+
redis==5.0.1
9596
requests==2.32.0
9697
restructuredtext-lint==1.4.0
98+
rq==1.15.1
9799
saneyaml==0.6.0
98100
semantic-version==2.9.0
99101
six==1.16.0

setup.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ install_requires =
9494

9595
#pipeline
9696
aboutcode.pipeline>=0.1.0
97+
django-rq==2.10.1
98+
rq-scheduler==0.13.1
9799

98100
#vulntotal
99101
python-dotenv

vulnerabilities/api_v2.py

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,20 @@
1414
from drf_spectacular.utils import extend_schema
1515
from drf_spectacular.utils import extend_schema_view
1616
from packageurl import PackageURL
17+
from rest_framework import mixins
1718
from rest_framework import serializers
1819
from rest_framework import status
1920
from rest_framework import viewsets
21+
from rest_framework.authentication import SessionAuthentication
2022
from rest_framework.decorators import action
23+
from rest_framework.permissions import BasePermission
2124
from rest_framework.response import Response
2225
from rest_framework.reverse import reverse
2326

2427
from vulnerabilities.models import CodeFix
2528
from vulnerabilities.models import Package
29+
from vulnerabilities.models import PipelineRun
30+
from vulnerabilities.models import PipelineSchedule
2631
from vulnerabilities.models import Vulnerability
2732
from vulnerabilities.models import VulnerabilityReference
2833
from vulnerabilities.models import VulnerabilitySeverity
@@ -606,3 +611,146 @@ def get_queryset(self):
606611
affected_package_vulnerability__vulnerability__vulnerability_id=vulnerability_id
607612
)
608613
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()

vulnerabilities/forms.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#
99

1010
from django import forms
11+
from django.contrib.admin.forms import AdminAuthenticationForm
1112
from django.core.validators import validate_email
1213
from django_recaptcha.fields import ReCaptchaField
1314
from django_recaptcha.widgets import ReCaptchaV2Checkbox
@@ -85,3 +86,25 @@ def clean_username(self):
8586

8687
def save_m2m(self):
8788
pass
89+
90+
91+
class PipelineSchedulePackageForm(forms.Form):
92+
search = forms.CharField(
93+
required=True,
94+
label=False,
95+
widget=forms.TextInput(
96+
attrs={
97+
"placeholder": "Search a pipeline...",
98+
"class": "input ",
99+
},
100+
),
101+
)
102+
103+
104+
class AdminLoginForm(AdminAuthenticationForm):
105+
captcha = ReCaptchaField(
106+
error_messages={
107+
"required": ("Captcha is required"),
108+
},
109+
widget=ReCaptchaV2Checkbox(),
110+
)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#
2+
# Copyright (c) nexB Inc. and others. All rights reserved.
3+
# VulnerableCode is a trademark of nexB Inc.
4+
# SPDX-License-Identifier: Apache-2.0
5+
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
6+
# See https://github.com/aboutcode-org/vulnerablecode for support or download.
7+
# See https://aboutcode.org for more information about nexB OSS projects.
8+
#
9+
10+
11+
from django_rq.management.commands import rqscheduler
12+
13+
from vulnerabilities import models
14+
from vulnerabilities.schedules import clear_zombie_pipeline_schedules
15+
from vulnerabilities.schedules import scheduled_job_exists
16+
from vulnerabilities.schedules import update_pipeline_schedule
17+
18+
19+
def init_pipeline_scheduled():
20+
"""Initialize schedule jobs for active PipelineSchedule."""
21+
active_pipeline_qs = models.PipelineSchedule.objects.filter(is_active=True).order_by(
22+
"created_date"
23+
)
24+
for pipeline_schedule in active_pipeline_qs:
25+
if scheduled_job_exists(pipeline_schedule.schedule_work_id):
26+
continue
27+
new_id = pipeline_schedule.create_new_job()
28+
pipeline_schedule.schedule_work_id = new_id
29+
pipeline_schedule.save(update_fields=["schedule_work_id"])
30+
31+
32+
class Command(rqscheduler.Command):
33+
def handle(self, *args, **kwargs):
34+
clear_zombie_pipeline_schedules()
35+
update_pipeline_schedule()
36+
init_pipeline_scheduled()
37+
super(Command, self).handle(*args, **kwargs)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#
2+
# Copyright (c) nexB Inc. and others. All rights reserved.
3+
# VulnerableCode is a trademark of nexB Inc.
4+
# SPDX-License-Identifier: Apache-2.0
5+
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
6+
# See https://github.com/aboutcode-org/vulnerablecode for support or download.
7+
# See https://aboutcode.org for more information about nexB OSS projects.
8+
#
9+
10+
import zoneinfo
11+
12+
from django.utils import timezone
13+
14+
15+
class UserTimezoneMiddleware:
16+
def __init__(self, get_response):
17+
self.get_response = get_response
18+
19+
def __call__(self, request):
20+
try:
21+
# Activate local timezone for user using cookies
22+
tzname = request.COOKIES.get("user_timezone")
23+
if tzname:
24+
timezone.activate(zoneinfo.ZoneInfo(tzname))
25+
else:
26+
timezone.deactivate()
27+
except Exception as e:
28+
timezone.deactivate()
29+
30+
return self.get_response(request)

0 commit comments

Comments
 (0)