feat(core): Add permission checking to Tickets form

ref: #250 #252 #96 #93 #95 #90 #115
This commit is contained in:
2024-08-27 17:18:19 +09:30
parent e63bec83e8
commit 09afd7f165
12 changed files with 336 additions and 71 deletions

6
.gitignore vendored
View File

@ -9,3 +9,9 @@ artifacts/
volumes/
build/
pages/
node_modules/
.markdownlint-cli2.jsonc
.markdownlint.json
package-lock.json
package.json
**.junit.xml

View File

@ -248,9 +248,11 @@ class OrganizationMixin():
return True
if self.request.user.has_perms(perms) and len(self.kwargs) == 0 and str(self.request.method).lower() == 'get':
if self.request.user.has_perms(perms) and str(self.request.method).lower() == 'get':
return True
if len(self.kwargs) == 0 or (len(self.kwargs) == 1 and 'ticket_type' in self.kwargs):
return True
for required_permission in self.permission_required:

View File

@ -1,15 +1,20 @@
from django import forms
from django.db.models import Q
from django.forms import ValidationError
from app import settings
from core.forms.common import CommonModelForm
from core.forms.validate_ticket import TicketValidation
from core.models.ticket.ticket import Ticket, RelatedTickets
class TicketForm(CommonModelForm):
class TicketForm(
CommonModelForm,
TicketValidation,
):
prefix = 'ticket'
@ -18,7 +23,9 @@ class TicketForm(CommonModelForm):
fields = '__all__'
def __init__(self, *args, **kwargs):
def __init__(self, request, *args, **kwargs):
self.request = request
super().__init__(*args, **kwargs)
@ -41,6 +48,7 @@ class TicketForm(CommonModelForm):
self.fields['description'].widget.attrs = {'style': "height: 800px; width: 900px"}
self.fields['opened_by'].initial = kwargs['user'].pk
self.fields['opened_by'].widget = self.fields['opened_by'].hidden_widget()
self.fields['ticket_type'].widget = self.fields['ticket_type'].hidden_widget()
@ -116,16 +124,70 @@ class TicketForm(CommonModelForm):
del self.fields[field]
def clean(self):
cleaned_data = super().clean()
return cleaned_data
def is_valid(self) -> bool:
is_valid = super().is_valid()
ticket_type_choice_id = int(self.cleaned_data['ticket_type'] - 1)
ticket_type = str(self.fields['ticket_type'].choices.choices.pop(ticket_type_choice_id)[1]).lower().replace(' ', '_')
if self.instance.pk:
self.original_object = self.Meta.model.objects.get(pk=self.instance.pk)
self.validate_ticket()
if ticket_type == 'change':
self.validate_change_ticket()
elif ticket_type == 'incident':
self.validate_incident_ticket()
elif ticket_type == 'issue':
# self.validate_issue_ticket()
raise ValidationError(
'This Ticket type is not yet available'
)
elif ticket_type == 'merge_request':
# self.validate_merge_request_ticket()
raise ValidationError(
'This Ticket type is not yet available'
)
elif ticket_type == 'problem':
self.validate_problem_ticket()
elif ticket_type == 'project_task':
# self.validate_project_task_ticket()
raise ValidationError(
'This Ticket type is not yet available'
)
elif ticket_type == 'request':
self.validate_request_ticket()
else:
raise ValidationError('Ticket Type must be set')
return is_valid

View File

@ -44,6 +44,9 @@ class CommentForm(CommonModelForm):
self.fields['ticket'].widget = self.fields['ticket'].hidden_widget()
self.fields['parent'].widget = self.fields['parent'].hidden_widget()
self.fields['comment_type'].widget = self.fields['comment_type'].hidden_widget()
if 'qs_comment_type' in kwargs['initial']:
comment_type = kwargs['initial']['qs_comment_type']

View File

@ -0,0 +1,172 @@
from django.core.exceptions import PermissionDenied
from django.forms import ValidationError
from access.mixin import OrganizationMixin
class TicketValidation(
OrganizationMixin,
):
original_object = None
add_fields: list = [
'title',
'description',
'urgency',
]
change_fields: list = []
delete_fields: list = [
'is_deleted',
]
import_fields: list = [
'assigned_users',
'assigned_teams',
'created',
'date_closed',
'external_ref',
'external_system',
'impact',
'opened_by',
'planned_start_date',
'planned_finish_date',
'priority',
'project',
'real_start_date',
'real_finish_date',
'subscribed_users',
'subscribed_teams',
]
triage_fields: list = [
'assigned_users',
'assigned_teams',
'impact',
'opened_by',
'planned_start_date',
'planned_finish_date',
'priority',
'project',
'real_start_date',
'real_finish_date',
'subscribed_users',
'subscribed_teams',
]
def validate_field_permission(self):
""" Check field permissions
Users can't edit all fields. They can only adjust fields that they
have the permissions to adjust.
Raises:
PermissionDenied: Access Denied when user has no ticket permissions assigned
PermissionDenied: _description_
"""
fields_allowed: list = []
if self.permission_check(
request = self.request,
permissions_required = [ 'add_ticket_'+ self.initial['type_ticket'] ]
) and not self.request.user.is_superuser:
fields_allowed = fields_allowed + self.add_fields
if self.permission_check(
request = self.request,
permissions_required = [ 'change_ticket_'+ self.initial['type_ticket'] ]
) and not self.request.user.is_superuser:
fields_allowed = fields_allowed + self.change_fields
if self.permission_check(
request = self.request,
permissions_required = [ 'delete_ticket_'+ self.initial['type_ticket'] ]
) and not self.request.user.is_superuser:
fields_allowed = fields_allowed + self.delete_fields
if self.permission_check(
request = self.request,
permissions_required = [ 'import_ticket_'+ self.initial['type_ticket'] ]
) and not self.request.user.is_superuser:
fields_allowed = fields_allowed + self.import_fields
if self.permission_check(
request = self.request,
permissions_required = [ 'triage_ticket_'+ self.initial['type_ticket'] ]
) and not self.request.user.is_superuser:
fields_allowed = fields_allowed + self.triage_fields
if self.request.user.is_superuser:
all_fields: list = self.add_fields
all_fields = all_fields + self.change_fields
all_fields = all_fields + self.delete_fields
all_fields = all_fields + self.import_fields
all_fields = all_fields + self.triage_fields
fields_allowed = fields_allowed + all_fields
if len(fields_allowed) == 0:
raise PermissionDenied('Access Denied')
for field in self.changed_data:
if field not in fields_allowed:
raise PermissionDenied(f'cant edit field: {field}')
def validate_ticket(self):
"""Validations common to all ticket types."""
self.validate_field_permission()
def validate_change_ticket(self):
# check status
# check type
pass
def validate_incident_ticket(self):
# check status
# check type
pass
def validate_problem_ticket(self):
# check status
# check type
pass
def validate_request_ticket(self):
# check status
# check type
# raise ValidationError('Test to see what it looks like')
pass

View File

@ -1,11 +1,8 @@
# Generated by Django 5.0.7 on 2024-08-26 05:21
# Generated by Django 5.0.7 on 2024-08-27 07:46
import access.fields
import access.models
import core.models.ticket.change_ticket
import core.models.ticket.markdown
import core.models.ticket.problem_ticket
import core.models.ticket.request_ticket
import core.models.ticket.ticket
import core.models.ticket.ticket_comment
import django.db.models.deletion
@ -59,7 +56,7 @@ class Migration(migrations.Migration):
'ordering': ['id'],
'permissions': [('add_ticket_request', 'Can add a request ticket'), ('change_ticket_request', 'Can change any request ticket'), ('delete_ticket_request', 'Can delete a request ticket'), ('import_ticket_request', 'Can import a request ticket'), ('purge_ticket_request', 'Can purge a request ticket'), ('triage_ticket_request', 'Can triage all request ticket'), ('view_ticket_request', 'Can view all request ticket'), ('add_ticket_incident', 'Can add a incident ticket'), ('change_ticket_incident', 'Can change any incident ticket'), ('delete_ticket_incident', 'Can delete a incident ticket'), ('import_ticket_incident', 'Can import a incident ticket'), ('purge_ticket_incident', 'Can purge a incident ticket'), ('triage_ticket_incident', 'Can triage all incident ticket'), ('view_ticket_incident', 'Can view all incident ticket'), ('add_ticket_problem', 'Can add a problem ticket'), ('change_ticket_problem', 'Can change any problem ticket'), ('delete_ticket_problem', 'Can delete a problem ticket'), ('import_ticket_problem', 'Can import a problem ticket'), ('purge_ticket_problem', 'Can purge a problem ticket'), ('triage_ticket_problem', 'Can triage all problem ticket'), ('view_ticket_problem', 'Can view all problem ticket'), ('add_ticket_change', 'Can add a change ticket'), ('change_ticket_change', 'Can change any change ticket'), ('delete_ticket_change', 'Can delete a change ticket'), ('import_ticket_change', 'Can import a change ticket'), ('purge_ticket_change', 'Can purge a change ticket'), ('triage_ticket_change', 'Can triage all change ticket'), ('view_ticket_change', 'Can view all change ticket')],
},
bases=(models.Model, core.models.ticket.change_ticket.ChangeTicket, core.models.ticket.problem_ticket.ProblemTicket, core.models.ticket.request_ticket.RequestTicket, core.models.ticket.markdown.TicketMarkdown),
bases=(models.Model, core.models.ticket.markdown.TicketMarkdown),
),
migrations.CreateModel(
name='RelatedTickets',

View File

@ -1,38 +0,0 @@
from django.db import models
from access.models import TenancyObject
class CommentCommonFields(models.Model):
class Meta:
abstract = True
id = models.AutoField(
blank=False,
help_text = 'Comment ID Number',
primary_key=True,
unique=True,
verbose_name = 'Number',
)
created = AutoCreatedField()
modified = AutoCreatedField()
class Comment(
TenancyObject,
CommentCommonFields,
):
class Meta:
ordering = [
'ticket',
'created',
'id',
]

View File

@ -0,0 +1,19 @@
class TicketMarkdown:
"""Ticket and Comment markdown functions
Intended to be used for all areas of a tickets, projects and comments.
"""
def render_markdown(self, markdown):
# Requires context of ticket for ticket markdown
# Requires context of ticket for comment
# requires context of project for project task comment
pass

View File

@ -6,10 +6,8 @@ from django.forms import ValidationError
from access.fields import AutoCreatedField
from access.models import TenancyObject, Team
from .change_ticket import ChangeTicket
from .markdown import TicketMarkdown
from .problem_ticket import ProblemTicket
from .request_ticket import RequestTicket
from project_management.models.projects import Project
@ -119,9 +117,6 @@ class TicketCommonFields(models.Model):
class Ticket(
TenancyObject,
TicketCommonFields,
ChangeTicket,
ProblemTicket,
RequestTicket,
TicketMarkdown,
):
@ -753,3 +748,10 @@ class RelatedTickets(TenancyObject):
related_name = 'to_ticket_id',
verbose_name = 'Related Ticket',
)
@property
def parent_object(self):
""" Fetch the parent object """
return self.from_ticket_id

View File

@ -398,6 +398,14 @@ class TicketComment(
return query
@property
def parent_object(self):
""" Fetch the parent object """
return self.ticket
@property
def threads(self):

View File

@ -21,23 +21,37 @@ class Add(AddView):
form_class = TicketForm
model = Ticket
permission_required = [
'itam.add_device',
]
template_name = 'form.html.j2'
def get_dynamic_permissions(self):
return [
str('core.add_ticket_' + self.kwargs['ticket_type']),
]
def get_initial(self):
return {
'organization': UserSettings.objects.get(user = self.request.user).default_organization,
initial = super().get_initial()
initial.update({
'type_ticket': self.kwargs['ticket_type'],
}
})
return initial
def form_valid(self, form):
form.instance.is_global = False
return super().form_valid(form)
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['request'] = self.request
return kwargs
def get_success_url(self, **kwargs):
if self.kwargs['ticket_type'] == 'request':
@ -64,9 +78,12 @@ class Change(ChangeView):
model = Ticket
permission_required = [
'itim.change_cluster',
]
def get_dynamic_permissions(self):
return [
str('core.change_ticket_' + self.kwargs['ticket_type']),
]
def get_context_data(self, **kwargs):
@ -78,6 +95,12 @@ class Change(ChangeView):
return context
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['request'] = self.request
return kwargs
def get_initial(self):
return {
'type_ticket': self.kwargs['ticket_type'],
@ -103,12 +126,16 @@ class Index(OrganizationPermission, generic.ListView):
model = Ticket
permission_required = [
'django_celery_results.view_taskresult',
]
template_name = 'core/ticket/index.html.j2'
def get_dynamic_permissions(self):
return [
str('core.view_ticket_' + self.kwargs['ticket_type']),
]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@ -152,10 +179,6 @@ class View(ChangeView):
model = Ticket
permission_required = [
'itam.view_device',
]
template_name = 'core/ticket.html.j2'
form_class = DetailForm
@ -163,6 +186,14 @@ class View(ChangeView):
context_object_name = "ticket"
def get_dynamic_permissions(self):
return [
str('core.view_ticket_' + self.kwargs['ticket_type']),
]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)

View File

@ -14,6 +14,7 @@ prepare:
${ACTIVATE_VENV};
pip install -r website-template/gitlab-ci/mkdocs/requirements.txt;
pip install -r gitlab-ci/lint/requirements.txt;
pip install -r gitlab-ci/mkdocs/requirements.txt;
pip install -r requirements.txt;
pip install -r requirements_test.txt;
npm install markdownlint-cli2;