feat(core): Add permission checking to Tickets form
ref: #250 #252 #96 #93 #95 #90 #115
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@ -9,3 +9,9 @@ artifacts/
|
||||
volumes/
|
||||
build/
|
||||
pages/
|
||||
node_modules/
|
||||
.markdownlint-cli2.jsonc
|
||||
.markdownlint.json
|
||||
package-lock.json
|
||||
package.json
|
||||
**.junit.xml
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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']
|
||||
|
172
app/core/forms/validate_ticket.py
Normal file
172
app/core/forms/validate_ticket.py
Normal 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
|
@ -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',
|
||||
|
@ -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',
|
||||
]
|
19
app/core/models/ticket/markdown.py
Normal file
19
app/core/models/ticket/markdown.py
Normal 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
|
@ -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
|
||||
|
@ -398,6 +398,14 @@ class TicketComment(
|
||||
|
||||
return query
|
||||
|
||||
|
||||
@property
|
||||
def parent_object(self):
|
||||
""" Fetch the parent object """
|
||||
|
||||
return self.ticket
|
||||
|
||||
|
||||
@property
|
||||
def threads(self):
|
||||
|
||||
|
@ -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)
|
||||
|
1
makefile
1
makefile
@ -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;
|
||||
|
Reference in New Issue
Block a user