Skip to content
This repository has been archived by the owner on Mar 17, 2024. It is now read-only.

Commit

Permalink
Feature/SK-421 | Network policies - users should not be able to delet…
Browse files Browse the repository at this point in the history
…e or edit (#128)
  • Loading branch information
niklastheman authored Apr 18, 2023
1 parent a1da5f0 commit b7a0e31
Show file tree
Hide file tree
Showing 6 changed files with 299 additions and 19 deletions.
30 changes: 28 additions & 2 deletions admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,38 @@


class AppsAdmin(admin.ModelAdmin):
list_display = ("name", "user_can_create", "slug", "revision")
list_display = (
"name",
"user_can_create",
"user_can_edit",
"user_can_delete",
"slug",
)
list_filter = ("user_can_create",)


admin.site.register(Apps, AppsAdmin)
admin.site.register(AppInstance)


class AppInstanceAdmin(admin.ModelAdmin):
list_display = (
"name",
"display_owner",
"display_project",
"state",
"access",
)

list_filter = ["owner", "project", "state"]

def display_owner(self, obj):
return obj.owner.username

def display_project(self, obj):
return obj.project.name


admin.site.register(AppInstance, AppInstanceAdmin)
admin.site.register(AppCategories)
admin.site.register(ResourceData)
admin.site.register(AppStatus)
7 changes: 7 additions & 0 deletions models.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ def __str__(self):

class Apps(models.Model):
user_can_create = models.BooleanField(default=True)
user_can_edit = models.BooleanField(default=True)
user_can_delete = models.BooleanField(default=True)
access = models.CharField(
max_length=20, blank=True, null=True, default="public"
)
Expand Down Expand Up @@ -82,6 +84,11 @@ def get_available_app_dependencies(self, user, project, app_name):
def user_can_create(self, user, project, app_slug):
limit = get_apps_per_project_limit(app_slug)

app = Apps.objects.get(slug=app_slug)

if not app.user_can_create:
return False

num_of_app_instances = self.filter(
~Q(state="Deleted"),
app__slug=app_slug,
Expand Down
43 changes: 31 additions & 12 deletions templates/app_table.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,38 +34,57 @@
<td>{{ appinstance.created_on }}</td>
<td class="table-action">
<div class="dropdown show">
<a href="#" data-bs-toggle="dropdown" data-display="static">
<a href="#" class="default" data-bs-toggle="dropdown" data-display="static">
<i class="bi bi-three-dots-vertical"></i>
</a>
<div class="dropdown-menu dropdown-menu-end">
<a class="dropdown-item disabled" href="{% url 'apps:logs' request.user project.slug appinstance.pk %}">
<a class="dropdown-item default disabled" href="{% url 'apps:logs' request.user project.slug appinstance.pk %}">
<i class="bi bi-activity me-1"></i>
Logs (disabled)
</a>

{% if appinstance.app.settings.publishable == "true" and appinstance.owner.id == request.user.id %}
{% if appinstance.access == "public" %}
<a class="dropdown-item" href="{% url 'apps:unpublish' request.user project.slug category appinstance.pk %}">
<a class="dropdown-item default" href="{% url 'apps:unpublish' request.user project.slug category appinstance.pk %}">
<i class="bi bi-slash-circle me-1"></i>
Unpublish
</a>
{% else %}
<a class="dropdown-item" href="{% url 'apps:publish' request.user project.slug category appinstance.pk %}">
<a class="dropdown-item default" href="{% url 'apps:publish' request.user project.slug category appinstance.pk %}">
<i class="bi bi-share me-1"></i>
Publish
</a>
{% endif %}
{% endif %}

<a class="dropdown-item" href="{% url 'apps:appsettings' request.user project.slug appinstance.pk %}">
<i class="bi bi-sliders2-vertical me-1"></i>
Settings
</a>
{% if appinstance.app.user_can_edit %}

<a class="dropdown-item default" href="{% url 'apps:appsettings' request.user project.slug appinstance.pk %}">
<i class="bi bi-sliders2-vertical me-1"></i>
Settings
</a>
{% else %}

<a class="dropdown-item default disabled">
<i class="bi bi-sliders2-vertical me-1"></i>
Settings
</a>
{% endif %}

{% if appinstance.app.user_can_delete %}

<a class="dropdown-item default bg-danger text-white confirm-delete" href="{% url 'apps:delete' request.user project.slug category appinstance.pk %}">
<i class="bi bi-trash me-1"></i>
Delete
</a>
{% else %}

<a class="dropdown-item default bg-danger text-white confirm-delete disabled">
<i class="bi bi-trash me-1"></i>
Delete
</a>
{% endif %}

<a class="dropdown-item bg-danger text-white confirm-delete" href="{% url 'apps:delete' request.user project.slug category appinstance.pk %}">
<i class="bi bi-trash me-1"></i>
Delete
</a>
</div>
</div>
</td>
Expand Down
108 changes: 108 additions & 0 deletions tests/test_app_settings_view.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
from django.contrib.auth import get_user_model
from django.test import Client, TestCase

from projects.models import Project

from ..models import AppCategories, AppInstance, Apps

User = get_user_model()


class AppSettingsViewTestCase(TestCase):
def setUp(self) -> None:
self.user = User.objects.create_user("foo1", "foo@test.com", "bar")
self.category = AppCategories.objects.create(
name="Network", priority=100, slug="network"
)
self.app = Apps.objects.create(
name="Jupyter Lab",
slug="jupyter-lab",
user_can_edit=False,
category=self.category,
settings={
"apps": {"Persistent Volume": "many"},
"flavor": "one",
"default_values": {"port": "80", "targetport": "8888"},
"environment": {
"name": "from",
"title": "Image",
"quantity": "one",
"type": "match",
},
"permissions": {
"public": {"value": "false", "option": "false"},
"project": {"value": "true", "option": "true"},
"private": {"value": "false", "option": "true"},
},
"export-cli": "True",
},
)

self.project = Project.objects.create_project(
name="test-perm",
owner=self.user,
description="",
repository="",
)

self.app_instance = AppInstance.objects.create(
access="public",
owner=self.user,
name="test_app_instance_public",
app=self.app,
project=self.project,
parameters={
"environment": {"pk": ""},
},
)

def get_data(self, user=None):
project = Project.objects.create_project(
name="test-perm",
owner=user if user is not None else self.user,
description="",
repository="",
)

return project

def test_user_can_edit_true(self):
c = Client()

response = c.post(
"/accounts/login/", {"username": "foo1", "password": "bar"}
)
response.status_code

self.assertEqual(response.status_code, 302)

url = (
f"/{self.user.username}/{self.project.slug}/"
+ f"apps/settings/{self.app_instance.id}"
)

response = c.get(url)

self.assertEqual(response.status_code, 403)

def test_user_can_edit_false(self):
c = Client()

response = c.post(
"/accounts/login/", {"username": "foo1", "password": "bar"}
)
response.status_code

self.assertEqual(response.status_code, 302)

self.app.user_can_edit = True
self.app.save()

url = (
f"/{self.user.username}/{self.project.slug}/"
+ f"apps/settings/{self.app_instance.id}"
)

response = c.get(url)

self.assertEqual(response.status_code, 200)
111 changes: 111 additions & 0 deletions tests/test_delete_app_view.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
from django.contrib.auth import get_user_model
from django.test import Client, TestCase

from projects.models import Project

from ..models import AppCategories, AppInstance, Apps

User = get_user_model()


class DeleteAppViewTestCase(TestCase):
def setUp(self) -> None:
self.user = User.objects.create_user("foo1", "foo@test.com", "bar")
self.category = AppCategories.objects.create(
name="Network", priority=100, slug="network"
)
self.app = Apps.objects.create(
name="Jupyter Lab",
slug="jupyter-lab",
user_can_delete=False,
category=self.category,
settings={
"apps": {"Persistent Volume": "many"},
"flavor": "one",
"default_values": {"port": "80", "targetport": "8888"},
"environment": {
"name": "from",
"title": "Image",
"quantity": "one",
"type": "match",
},
"permissions": {
"public": {"value": "false", "option": "false"},
"project": {"value": "true", "option": "true"},
"private": {"value": "false", "option": "true"},
},
"export-cli": "True",
},
)

self.project = Project.objects.create_project(
name="test-perm",
owner=self.user,
description="",
repository="",
)

self.app_instance = AppInstance.objects.create(
access="public",
owner=self.user,
name="test_app_instance_public",
app=self.app,
project=self.project,
)

def get_data(self, user=None):
project = Project.objects.create_project(
name="test-perm",
owner=user if user is not None else self.user,
description="",
repository="",
)

return project

def test_user_can_delete_true(self):
c = Client()

response = c.post(
"/accounts/login/", {"username": "foo1", "password": "bar"}
)
response.status_code

self.assertEqual(response.status_code, 302)

url = (
f"/{self.user.username}/{self.project.slug}/apps/delete/"
+ f"{self.category.slug}/{self.app_instance.id}"
)

response = c.get(url)

self.assertEqual(response.status_code, 403)

def test_user_can_delete_false(self):
c = Client()

response = c.post(
"/accounts/login/", {"username": "foo1", "password": "bar"}
)
response.status_code

self.assertEqual(response.status_code, 302)

self.app.user_can_delete = True
self.app.save()

url = (
f"/{self.user.username}/{self.project.slug}/apps/delete/"
+ f"{self.category.slug}/{self.app_instance.id}"
)

response = c.get(url)

self.assertEqual(response.status_code, 302)

self.app_instance = AppInstance.objects.get(
name="test_app_instance_public"
)

self.assertEqual("private", self.app_instance.access)
19 changes: 14 additions & 5 deletions views.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,10 @@ def get(self, request, user, project, ai_id):
)
existing_app_name = appinstance.name
app = appinstance.app

if not app.user_can_edit:
return HttpResponseForbidden()

app_settings = appinstance.app.settings
form = generate_form(
app_settings, project, app, request.user, appinstance
Expand All @@ -245,6 +249,9 @@ def post(self, request, user, project, ai_id):
app_settings = app.settings
body = request.POST.copy()

if not app.user_can_edit:
return HttpResponseForbidden()

if not body.get("permission", None):
body.update({"permission": appinstance.access})

Expand Down Expand Up @@ -495,18 +502,20 @@ def unpublish(request, user, project, category, ai_id):

@permission_required_or_403("can_view_project", (Project, "slug", "project"))
def delete(request, user, project, category, ai_id):
print("PK=" + str(ai_id))

if "from" in request.GET:
from_page = request.GET.get("from")
else:
from_page = "filtered"

app_instance = AppInstance.objects.get(pk=ai_id)

if not app_instance.app.user_can_delete:
return HttpResponseForbidden()

delete_resource.delay(ai_id)
# fix: in case appinstance is public swich to private
app = AppInstance.objects.get(pk=ai_id)
app.access = "private"
app.save()
app_instance.access = "private"
app_instance.save()

if "from" in request.GET:
from_page = request.GET.get("from")
Expand Down

0 comments on commit b7a0e31

Please sign in to comment.