diff --git a/CHANGELOG.md b/CHANGELOG.md index 2391f2a..92b885e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # VINCE Changelog +Version 2.0.7 2023-03-20 + +* Security updates Django to 3.2.18 CVE-2023-24580, Remove python-futures (no longer used) GH Issues #91 #90 (Dependabot) +* Support User Approve Request (UAR) new workflow for User joining Vendor Group GH Issue #94 +* Allow Tracking ID's to be added to Cases when user belongs to multiple groups (CaseTracking) reported by VINCE user. +* Move from initial to instance on Form Class inits() to modify existing data in Models/Forms pair +* Move more browser UI information to async data requests, less templates. +* Remove `marquee`, `command` and `style` tags from supported markdown_helpers lib.vince.markdown_helpers - reported by VINCE user. + + Version 2.0.6 2023-01-23 * Removed Edit Vulnerability button superfluous GHIssue #77 diff --git a/README.md b/README.md index 278f323..6b95c28 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ VINCEPUB application provides publicly available publications and reports that u users. Each application can also be further protected by network access controls as desired to reduce the risk of exposure. -[](./VINCE_Infrastructure.png) +[](https://github.com/CERTCC/VINCE/raw/main/Vince_Infrastructure.png) ### Local Install @@ -81,7 +81,7 @@ reduce the risk of exposure. 2. Create a virtual environment and install requirements ``` cd bigvince -mkvirtualenv --python=/usr/local/bin/python3.6 bigvince (python3 -m venv env) +mkvirtualenv bigvince source env/bin/activate pip install -r requirements.txt ``` diff --git a/bigvince/settings_.py b/bigvince/settings_.py index 644dadd..f5d2ba5 100644 --- a/bigvince/settings_.py +++ b/bigvince/settings_.py @@ -56,7 +56,7 @@ ROOT_DIR = environ.Path(__file__) - 3 # any change that requires database migrations is a minor release -VERSION = "2.0.6" +VERSION = "2.0.7" # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/ diff --git a/cdk/lambda/CreateDatabases/requests/__version__.py b/cdk/lambda/CreateDatabases/requests/__version__.py index 9844f74..59fab78 100644 --- a/cdk/lambda/CreateDatabases/requests/__version__.py +++ b/cdk/lambda/CreateDatabases/requests/__version__.py @@ -4,11 +4,11 @@ __title__ = 'requests' __description__ = 'Python HTTP for Humans.' -__url__ = 'http://python-requests.org' -__version__ = '2.22.0' -__build__ = 0x022200 +__url__ = 'https://requests.readthedocs.io' +__version__ = '2.27.1' +__build__ = 0x022701 __author__ = 'Kenneth Reitz' __author_email__ = 'me@kennethreitz.org' __license__ = 'Apache 2.0' -__copyright__ = 'Copyright 2019 Kenneth Reitz' +__copyright__ = 'Copyright 2023 Kenneth Reitz' __cake__ = u'\u2728 \U0001f370 \u2728' diff --git a/lib/vince/markdown_helpers.py b/lib/vince/markdown_helpers.py index 489997f..2bc7efa 100644 --- a/lib/vince/markdown_helpers.py +++ b/lib/vince/markdown_helpers.py @@ -34,6 +34,10 @@ import logging from bs4 import BeautifulSoup + +unsafe = {"style","marquee","command"} +generally_xss_safe = [v for v in generally_xss_safe if not v in unsafe] + logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) diff --git a/vince/static/vince/css/overrides.css b/vince/static/vince/css/overrides.css index c8ac0c8..bfd11cc 100644 --- a/vince/static/vince/css/overrides.css +++ b/vince/static/vince/css/overrides.css @@ -119,3 +119,18 @@ nav.cdown li.not_affected { background-color: #3adb76; } +.new-vendor { + padding-left: 4px; +} +.p-inline-block { + display:inline-block; +} +.p-inline { + display:inline; +} +span.trackorg::before { + content: "["; +} +span.trackorg::after { + content: "]"; +} diff --git a/vince/static/vince/css/style.css b/vince/static/vince/css/style.css index 12f675e..4d62af0 100644 --- a/vince/static/vince/css/style.css +++ b/vince/static/vince/css/style.css @@ -714,7 +714,7 @@ ul { #login-footer{ height:70px; - background-color:#FFF; + background-color:#525356; border: solid 1px white; max-width: 600px; width: 100%; @@ -3730,6 +3730,7 @@ h4 a i { padding: 0 .25em; font-size: 14px; color: #666; + cursor:pointer; } .edit-delete-hover { @@ -3756,7 +3757,7 @@ h4 a i { } .sent-by-me { - background-color: #7eb3af; + background-color: #777; color: #fff; } diff --git a/vince/static/vince/js/email.js b/vince/static/vince/js/email.js index f554590..dfc11b9 100644 --- a/vince/static/vince/js/email.js +++ b/vince/static/vince/js/email.js @@ -91,7 +91,18 @@ $(document).ready(function() { $('form').submit(function () { window.removeEventListener('beforeunload', onBeforeUnload); }); - + $('#emailform').on('submit', function() { + let emails = $('input[name="to"]').val().split(","); + let remail = /^([\w-\.]+@([\w-]+\.)+[\w-]{2,4})?$/; + for(let i = 0; i < emails.length; i++) { + if(!remail.test(emails[i])) { + alert("Email entry " + emails[i] + " is invalid! \n" + + "Enter valid email address before submitting."); + return false; + } + } + return true; + }); var options = {} var selector = 'input[id^=id_contact]' diff --git a/vince/static/vince/js/tickets.js b/vince/static/vince/js/tickets.js index 99fcc72..f35c92c 100644 --- a/vince/static/vince/js/tickets.js +++ b/vince/static/vince/js/tickets.js @@ -583,7 +583,11 @@ $(document).ready(function() { $("#msgvendor").removeClass("hidden"); } else if (data['action_link']) { $("#msgvendor").addClass("hidden"); - $("#msgabutton").replaceWith("Send Email"); + let send_email = $("
") + .append($("").addClass("button primary") + .prop("href",data.action_link) + .html("Send Email")).html() + $("#msgabutton").replaceWith(send_email); } else { $("#msgvendor").addClass("hidden"); $("#msgabutton").prop("disabled", true); @@ -615,8 +619,28 @@ $(document).ready(function() { } }); } - - + function msgadminform_async() { + $('#msgadminform').on('submit',function(e) { + e.preventDefault(); + $('body').css({opacity: 0.5}); + $.post(this.action,$(this).serialize(),function(d) { + console.log(d); + $('#msgadminform .modal-body').html('

Submit completed

') + .append(JSON.stringify(d,null,'\t')); + }).fail(function() { + $('#msgadminform .modal-body').html('

Submission Failed!

') + .append("See console log for details"); + console.log(arguments); + }).done(function() { + $('#msgadminform .modal-footer').html(''); + setTimeout(function() { + $("#adddependency").foundation('close'); + location.reload(); + }, 900); + }); + return false; + }); + } $(document).on("click", "#msgadmin", function(event) { event.preventDefault(); var url = $(this).attr("href"); @@ -628,6 +652,7 @@ $(document).ready(function() { adddepmodal.html(data).foundation('open'); $.getJSON("/vince/api/vendors/", function(data) { vend_auto(data); + msgadminform_async(); }); }, error: function(xhr, status) { diff --git a/vince/templates/vince/include/changelog.html b/vince/templates/vince/include/changelog.html index f91c320..f614c30 100644 --- a/vince/templates/vince/include/changelog.html +++ b/vince/templates/vince/include/changelog.html @@ -32,7 +32,11 @@ {% for revision in revisions %}
-
+ {% if revision == vulnote.current_revision %} +
+ {% else %} +
+ {% endif %}
{% if revision == vulnote.current_revision %} diff --git a/vince/templates/vince/new_email.html b/vince/templates/vince/new_email.html index 33a36be..253072b 100644 --- a/vince/templates/vince/new_email.html +++ b/vince/templates/vince/new_email.html @@ -30,7 +30,7 @@

New Email

-
{% csrf_token %} + {% csrf_token %} {% if form.errors %}

{% if form.errors.items|length == 1 %}Please correct the error below.{% else %}Please correct the errors below.{% endif %} diff --git a/vince/templates/vince/teams.html b/vince/templates/vince/teams.html index 2790b52..02ca329 100644 --- a/vince/templates/vince/teams.html +++ b/vince/templates/vince/teams.html @@ -34,7 +34,7 @@

Coordination Teams

-
+
{% autoescape off %}{{ team|teamlogo:"card-profile-stats-intro-pic" }}{% endautoescape %}
-
- - -
- -{% endblock %} diff --git a/vinny/templates/vinny/admin.html b/vinny/templates/vinny/admin.html index 61620f3..417246c 100644 --- a/vinny/templates/vinny/admin.html +++ b/vinny/templates/vinny/admin.html @@ -18,10 +18,9 @@

User Management {% if vendor_name %}for {{ vendor_name }}{% endif %}

-
+
{% for message in messages %} -
{{ message }}
{% endfor %}
@@ -54,12 +53,49 @@

User Management {% if vendor_name %}for {{ vendor_name }}{% endif %}

+ +
+
+
+

Users Requesting Access

+
+
+
+
+
+ Abuse request from user to join our group. + The following user requested to join our group. We do not recognize + this user or his username/email address. Please take action as appropriate. + + User : $username + + Name: $full_name + + Group Requested: $vendor_name + +
+ + + + + + + + + + + +
UsernameFull NameRequested atAction
+
+
@@ -159,7 +195,7 @@
+
+
+ + + +
+ +
+ + {% endblock %} diff --git a/vinny/templates/vinny/sendmsg.html b/vinny/templates/vinny/sendmsg.html index 77c412c..655f122 100644 --- a/vinny/templates/vinny/sendmsg.html +++ b/vinny/templates/vinny/sendmsg.html @@ -24,15 +24,6 @@

Inbox

- -
-
- {% for message in messages %} -
{{ message }}
- {% endfor %} -
-
-
@@ -78,7 +69,12 @@

New Message

{% elif field.name == "vendor" %}
- Please provide the name of the organization you are requesting access to. If you are the first person to join this VINCE group, we perform a two-person validation process to verify your status at the organization. If the organization is already an active group in VINCE, we will ask the group administrator to add you to the group. If you know the group administrator for your organization, it may be quicker to ask them directly to add you to the group. + Please select the name of your organization under Vendor Name. + Provide Justification for your access to the organization. + If your organization is not listed, + please provide the additional information to speed-up the + new organization validation process and to expedite your + onboarding promptly.
{% endif %}
diff --git a/vinny/urls.py b/vinny/urls.py index 96d12be..9043c17 100644 --- a/vinny/urls.py +++ b/vinny/urls.py @@ -85,6 +85,7 @@ path('auto/api/vlookup/', views.VendorLookupView.as_view(), name='vendorlookup'), path('auto/api/vendors/', views.autocomplete_vendor, name='auto_vendor'), path('auto/api/users/', views.autocomplete_users, name='auto_user'), + path('api/userapprove/', views.userapproverequest, {"caller": "vinny"},name='userapprove'), re_path('^auto/api/coord/(?P\d+)/$', views.autocomplete_coordinators, name='auto_coord'), path('sendmsg/', views.SendMessageView.as_view(), name='sendmsg'), path('sendmsg/user/', views.SendMessageUserView.as_view(), name='sendmsguser'), @@ -111,7 +112,7 @@ re_path('^case/(?P[0-9]+)?/mute/$', views.MuteCaseView.as_view(), name='mute'), re_path('^case/(?P[0-9]+)/add/document/$', views.CaseDocumentCreateView.as_view(), name='addfile'), re_path('^case/(?P[0-9]+)/rm/(?P[0-9]+)/document/$', views.RemoveFileView.as_view(), name='rmfile'), - re_path('^case/(?P[0-9]+)/add/track/$', views.CaseAddTrackingView.as_view(), name='addtrack'), + re_path('^case/tracking/$', views.casetracking, name='casetracking'), re_path('^case/(?P[0-9]+)/status/update/$', views.UpdateStatusView.as_view(), name='update_status'), re_path('^case/(?P[0-9]+)/report/$', views.CaseRequestView.as_view(), name='cr'), re_path('^case/(?P[0-9]+)/vuls/$', views.VulnerabilityDetailView.as_view(), name='vuls'), diff --git a/vinny/views.py b/vinny/views.py index bdcf4ef..86130b8 100644 --- a/vinny/views.py +++ b/vinny/views.py @@ -39,6 +39,7 @@ from django.http import HttpResponse, Http404, JsonResponse, HttpResponseNotAllowed, HttpResponseServerError, HttpResponseForbidden, HttpResponseRedirect, HttpResponseBadRequest from django.core.validators import validate_email from django.core.exceptions import ValidationError, PermissionDenied +from django.core import serializers from django.utils.translation import ugettext as _ from django.utils import timezone from django.db.models import Case as DBCase @@ -67,6 +68,7 @@ from vinny.forms import * from vinny.lib import vince_comm_send_sqs, send_sns, send_sns_json, send_usermention_notification, new_track_ticket, send_post_email, user_is_admin, user_has_access from random import randint +from os import urandom from django.template.defaulttags import register from django.urls import reverse, reverse_lazy from cogauth.views import TokenMixin, GetUserMixin, PendingTestMixin @@ -361,7 +363,8 @@ def object_to_json_response(obj, status=200): data=obj, status=status, safe=False, json_dumps_params={'ensure_ascii': False}, ) - +#A similar method exists for all VinceComm users exists +#below autocomplete_allvendors() @login_required(login_url="vinny:login") @user_passes_test(is_in_group_vincetrack, login_url='vinny:login') def autocomplete_vendor(request): @@ -396,9 +399,156 @@ def autocomplete_allvendors(request): qs = VinceCommContact.objects.filter(vendor_type='Vendor',active=True).values('pk','vendor_name') ed = ED(base64.b64encode(settings.SECRET_KEY.encode())) #Hide uuid and pk fields - allow usage in specific Cases only - data = list(map(lambda x: {"euid": ed.encrypt(str(x['pk'])), "vendor_name" : x["vendor_name"]}, qs)) + vendors = list(map(lambda x: {"euid": ed.encrypt(str(x['pk'])), "vendor_name" : x["vendor_name"]}, qs)) + groups = request.user.groups.filter(groupcontact__contact__vendor_type="Vendor").exclude(groupcontact__isnull=True) + my_groups = [] + for ug in groups: + my_groups.append({"euid": ed.encrypt(str(ug.groupcontact.contact.pk)), "vendor_name": ug.groupcontact.contact.vendor_name}) + data = {"vendors": vendors, "my_vendors": my_groups} return HttpResponse(json.dumps(data,default=str), 'application/json') +@login_required(login_url="vinny:login") +@user_passes_test(is_not_pending, login_url="vinny:login") +def userapproverequest(request, *args, **kwargs): + res = {"uar": [], "args": args, "kwargs": kwargs} + #when this method is called from vince/urls.py + if kwargs and "caller" in kwargs and kwargs["caller"] == "vince": + #superflous call to `using` vincecomm for code clarity only + vinnyuser = User.objects.using("vincecomm").filter(username=request.user.username).first() + if vinnyuser and is_in_group_vincetrack(vinnyuser): + qall = UserApproveRequest.objects.all().order_by('-created_at') + if request.method == "GET": + spage = request.GET.get("page","1") + if spage.isdigit(): + page = int(spage) + else: + page = 1 + pageSize = 20 + pager = Paginator(qall,pageSize) + if page > pager.num_pages: + res["uar_error"] = f"Page given is too big {page} > {pager.num_pages}" + page = pager.num_pages + qs_admin = pager.page(page).object_list + res["uar_count"] = qall.count() + res["uar_page"] = page + res["uar_pageSize"] = pageSize + else: + qs_admin = qall + else: + qs_admin = UserApproveRequest.objects.none() + else: + qs_admin = UserApproveRequest.objects.filter(contact__in=VinceCommGroupAdmin.objects.filter(email__email=request.user.username,contact__active=True).values_list('contact__id', flat=True),status=UserApproveRequest.Status.UNKNOWN) + #POST is an update record check if the user is admin and do the operation + if request.method == "POST": + if request.POST.get('pk'): + qupdate = qs_admin.filter(contact__pk=request.POST.get('pk'),user__username=request.POST.get('username')) + if len(qupdate) == 1: + rec = qupdate.first() + rec.completed = timezone.now() + if request.POST.get('status') != None and request.POST.get('status').isnumeric(): + rec.status = int(request.POST.get('status')) + rec.save() + recjson = serializers.serialize('json',[rec]) + return HttpResponse(recjson, 'application/json') + return HttpResponse('[]', 'application/json') + for q in qs_admin: + justification = vinceutils.deepGet(q,'thread.latest_message.content') + thread_id = vinceutils.deepGet(q,'thread.pk') + thread_url = '#' + if thread_id: + thread_url = reverse('vinny:thread_detail', args=[thread_id]) + res["uar"].append({"username": q.user.username, + "full_name": f"{q.user.first_name} {q.user.last_name}", + "vendor": q.contact.vendor_name, "pk": q.contact.pk, + "justification": justification, + "thread_url": thread_url, + "created_at": q.created_at + }) + qs = UserApproveRequest.objects.filter(user=request.user,status=UserApproveRequest.Status.UNKNOWN) + for q in qs: + res["uar"].append({"username": q.user.username,"vendor": q.contact.vendor_name}) + return HttpResponse(json.dumps(res,default=str), 'application/json') + + +@login_required(login_url="vinny:login") +@user_passes_test(is_not_pending, login_url="vinny:login") +def casetracking(request): + """ Case Tracking manager as simple view """ + def responder(res): + return HttpResponse(json.dumps(res,default=str), 'application/json') + def recobj(rin): + ret = {} + fields = {"tracker": "tracking", + "track_id": "pk", + "group_id": "group.pk", + "trackorg": "group.groupcontact.contact.vendor_name", + "added_by": "added_by.vinceprofile.vince_username", + "dateupdated": "dateupdated"} + for x in fields: + ret[x] = vinceutils.deepGet(rin,fields[x]) + return ret + rec = [] + if request.method == "POST": + if request.POST.get('tracker'): + tracking = request.POST.get('tracker') + if request.POST.get('pk'): + #CaseTracking update just use primarykey and groups to find it + match = CaseTracking.objects.filter(group__in=request.user.groups.all(),pk=request.POST.get('pk')).first() + if match: + match.tracking = tracking + match.save() + rec.append(recobj(match)) + else: + err = {"error": "No matching Tracking information found"} + return responder(err) + elif request.POST.get('case_id'): + #CaseTracking Insert + case = Case.objects.filter(pk=request.POST.get('case_id')).first() + if not case: + err = {"error": "No matching Case found"} + return responder(err) + + csms = _my_groups_for_case(request.user,case) + if csms.count() > 1: + if request.POST.get('group_id'): + csm = csms.filter(group__pk=request.POST.get('group_id')).first() + else: + err = {"error": "Please select your Organization"} + return responder(err) + else: + csm = csms.first() + if not csm: + err = {"error": "Organization not found"} + return responder(err) + newrec = CaseTracking(case=case, + tracking=tracking, + group=csm.group, + added_by=request.user) + try: + newrec.save() + except Exception as e: + logger.warning(f"Failed to create a new CaseTracking record Error: {e}") + err = {"error": "Record not recorded, possible duplicate!"} + return responder(err) + rec.append(recobj(newrec)) + elif request.GET.get('case_id'): + case = Case.objects.filter(pk=request.GET.get('case_id')).first() + if case: + for cm in _my_groups_for_case(request.user,case): + #Create casetracking record and add tracking info if present. + ctrec = {"group_id": cm.group.pk, + "trackorg": cm.get_member_name()} + ct = CaseTracking.objects.filter(group=cm.group,case=case).first() + if ct: + ctrec.update(recobj(ct)) + rec.append(ctrec) + else: + err = {"error": "No matching Case found"} + return responder(err) + res = {"trackings": rec} + return responder(res) + + @login_required(login_url="vinny:login") @user_passes_test(is_in_group_vincetrack, login_url='vinny:login') @@ -1309,6 +1459,24 @@ def test_func(self): def post(self, request, *args, **kwargs): logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") my_group = get_object_or_404(VinceCommContact, id=self.kwargs.get('vendor_id')) + if request.POST.get("action") and request.POST.get("requestor_id") : + if request.POST.get("action") == "reject": + #Create a Track ticket and send emails to admin + logger.info("Code not ready for this part") + safe_id = request.POST.get("requestor_id") + try: + ed = ED(base64.b64encode(settings.SECRET_KEY.encode())) + requestor_id = ed.decrypt(safe_id) + except Exception as e: + logger.info(f"Decryption of the safe_id failed {safe_id} with error {e}") + return JsonResponse({'error': 'Requestor could not be found'}) + requestor = list(User.objects.filter(id=requestor_id).values('username','first_name','last_name')) + if len(requestor) == 1: + return JsonResponse(requestor[0]) + else: + return JsonResponse({"error": "User not found"}) + + # This doesn't use the email form used in other places in the app, so we have to strip() fields here users = request.POST.get('adduser') if users: @@ -1962,7 +2130,6 @@ def get_form_kwargs(self): }) return kwargs - class SendMessageView(LoginRequiredMixin, TokenMixin, PendingTestMixin, generic.CreateView): template_name = "vinny/sendmsg.html" login_url = "vinny:login" @@ -1972,7 +2139,27 @@ def form_valid(self, form): logger.debug(f"{self.__class__.__name__} post: {self.request.POST} is valid") files = [self.request.FILES.get('attachment[%d]' % i) for i in range (0, len(self.request.FILES))] logger.debug(files) + try: + if self.request.POST.get("vendor_euid"): + vendor_euid = self.request.POST.get("vendor_euid") + ed = ED(base64.b64encode(settings.SECRET_KEY.encode())) + vendor_pk = ed.decrypt(str(vendor_euid)).decode() + setattr(form,"vendor_pk",vendor_pk) + except Exception as e: + logger.info(f"Failed to decrypt session based vendor euid error {e}") ticket = form.save(files) + if ticket and hasattr(ticket,"error"): + msg = "Message could not be sent" + if hasattr(ticket,"info") and type(ticket.info) == dict: + if "error" in ticket.info: + msg = ticket.info["error"] + if "thread_url" in ticket.info: + thread_url = ticket.info["thread_url"] + msg = msg + f" Click here to see it" + messages.error( + self.request, + _(f"Error: {msg}")) + return JsonResponse(ticket.info, status=200) messages.success( self.request, _("Your message has been sent.")) @@ -1983,11 +2170,10 @@ def get_context_data(self, **kwargs): context['group_admin'] = _my_group_admin(self.request.user) context['unread_msg_count'] = 0 context['msgtype'] = self.kwargs.get('type', 1) - csmembers = CaseMember.objects.filter(case__id=self.kwargs.get('case'), coordinator=True).exclude(group__groupcontact__vincetrack=False).values_list('group__groupcontact__contact__vendor_name', flat=True) - logger.debug(f"Case membership of {self.request.user} is {csmembers}") try: if self.kwargs.get('case'): members = list(CaseMember.objects.filter(case__id=self.kwargs.get('case'), coordinator=True).exclude(group__groupcontact__vincetrack=False).values_list('group__groupcontact__contact__vendor_name', flat=True)) + logger.debug(f"Case membership of {self.request.user} is {members}") members = [i for i in members if i] context['coord'] = ", ".join(members) except: @@ -2002,9 +2188,8 @@ def get_initial(self): def get_form_kwargs(self): kwargs = super(SendMessageView, self).get_form_kwargs() - kwargs.update({ - "user": self.request.user, - }) + updates = {"user": self.request.user} + kwargs.update(updates) return kwargs @@ -2144,63 +2329,7 @@ def form_valid(self, form): self.request.user.username, None, "Vendor Uploaded File") return redirect('vinny:case', self.kwargs['pk']) -class CaseAddTrackingView(LoginRequiredMixin, TokenMixin, UserPassesTestMixin, FormView): - model = CaseTracking - template_name = 'vinny/addtracking.html' - form_class = AddTrackingForm - def test_func(self): - self.case = get_object_or_404(Case, id=self.kwargs['pk']) - test = _is_my_case(self.request.user, self.kwargs['pk']) - if test: - return PendingTestMixin.test_func(self) - else: - return False - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context['case'] = self.case - context['title'] = "Add Tracking Number to Case" - return context - - def get_form(self): - try: - my_group = _my_group_id_for_case(self.request.user, self.case) - ct = CaseTracking.objects.get(case=self.case, group=my_group) - return self.form_class(instance=ct, **self.get_form_kwargs()) - except CaseTracking.DoesNotExist: - return self.form_class(**self.get_form_kwargs()) - - def get_form_kwargs(self): - kwargs = super(CaseAddTrackingView, self).get_form_kwargs() - kwargs.update({ - "case": self.case, - "user": self.request.user, - "group": _my_group_id_for_case(self.request.user, self.case) - - }) - return kwargs - - def form_invalid(self, form): - logger.debug(f"{self.__class__.__name__} errors: {form.errors}") - return super().form_invalid(form) - - def form_valid(self, form): - doc = form.save() - context = {} - context['vuid'] = self.case.vu_vuid - context['tracking'] = form.cleaned_data['tracking'] - try: - if self.case.team_owner.coordinatorsettings.team_signature: - context['team_signature']= self.case.team_owner.coordinatorsettings.team_signature - else: - context['team_signature']= self.DEFAULT_EMAIL_SIGNATURE - except: - context['team_signature']= self.DEFAULT_EMAIL_SIGNATURE - - if context['tracking']: - send_templated_mail("new_tracking", context, [self.request.user.email]) - return redirect('vinny:case', self.kwargs['pk']) class UserCardView(LoginRequiredMixin, TokenMixin, PendingTestMixin, generic.DetailView): template_name = 'vinny/usercard.html' @@ -2337,7 +2466,10 @@ def get(self, request, *args, **kwargs): data = json.dumps(members) mimetype='application/json' return HttpResponse(data, mimetype) - + + + + class CaseView(LoginRequiredMixin, TokenMixin, UserPassesTestMixin, generic.TemplateView): template_name = "vinny/case.html" login_url="vinny:login" @@ -2436,7 +2568,6 @@ def get_context_data(self, **kwargs): context['showupdatestatus'] = True else: context['showupdatestatus'] = False - context['tracking'] = CaseTracking.objects.filter(case=case, group=cm.group).first() context['auto_members'] = [] #_case_participants(case)[:50] context['simulation'] = cm @@ -2467,8 +2598,6 @@ def get_context_data(self, **kwargs): #if this person belongs to more than 1 vendor, don't present them with the form, make them #choose which vendor to submit status for context['multivendor'] = True - else: - context['tracking'] = CaseTracking.objects.filter(case=case, group=_my_group_id_for_case(self.request.user, case)).first() for member in members: if member.seen == False: @@ -4015,7 +4144,6 @@ def get(self, request, *args, **kwargs): raise Http404 - class UpdateVendorStatusAPIView(generics.GenericAPIView): permission_classes = (IsAuthenticated,CaseAccessPermission,PendingUserPermission) @@ -4335,4 +4463,4 @@ def finalize_response(self, request, response, *args, **kwargs): if response.status_code == 200 and request.headers.get('Origin') and request.headers.get('Origin').find("https://") > -1: response["Access-Control-Allow-Origin"] = request.headers.get('Origin') return response - +