From d0dbbc97c271aa907b6d3f8f8de03b5264fc6b5a Mon Sep 17 00:00:00 2001 From: Vijay Sarvepalli Date: Fri, 16 Dec 2022 11:10:53 -0500 Subject: [PATCH] Version 2.0.3 (#76) * Updates to version 1.50.6 * Updated Django 3.2 version 2.0.3 --- CHANGELOG.md | 13 + bigvince/settings_.py | 50 +- bigvince/storage_backends.py | 21 +- cogauth/backend.py | 79 ++- cogauth/templates/cogauth/mfa.html | 1 - cogauth/templates/cogauth/mfarequired.html | 3 +- cogauth/views.py | 52 +- lib/vince/chainqueryset.py | 37 ++ lib/vince/markdown_helpers.py | 2 +- requirements.txt | 138 ++-- vince/forms.py | 1 + vince/mailer.py | 93 +-- vince/middleware.py | 9 +- vince/models.py | 50 +- vince/static/vince/css/overrides.css | 50 ++ vince/static/vince/js/case_search.js | 15 +- vince/static/vince/js/dashboard.js | 52 -- vince/static/vince/js/teamdash.js | 53 -- vince/static/vince/js/tickets.js | 165 ++--- vince/static/vince/js/vince.js | 377 ++++++++--- vince/static/vince/js/vinny_dashboard.js | 72 +-- vince/templates/vince/base_site.html | 1 + vince/templates/vince/searchcases.html | 6 + vince/templates/vince/searchresults.html | 3 + vince/templates/vince/ticket.html | 4 +- vince/templates/vince/ticket_activity.html | 19 +- vince/templates/vince/ticket_table.html | 2 +- vince/views.py | 599 +++++++----------- vincepub/models.py | 9 +- vincepub/serializers.py | 17 +- vincepub/views.py | 90 ++- vinny/forms.py | 10 +- vinny/mailer.py | 5 +- vinny/serializers.py | 34 +- vinny/static/vinny/js/case_search.js | 123 ---- vinny/static/vinny/js/inbox.js | 115 +--- vinny/static/vinny/js/vincecomm.js | 205 +++++- vinny/static/vinny/js/vinny_dashboard.js | 129 +--- vinny/templates/vinny/404.html | 6 +- vinny/templates/vinny/base.html | 166 +++++ .../templates/vinny/confirm_email_change.html | 4 +- vinny/templates/vinny/dashboard.html | 11 +- vinny/templates/vinny/dropmenu.html | 2 +- vinny/templates/vinny/inbox.html | 86 +-- vinny/templates/vinny/include/cases.html | 19 +- vinny/templates/vinny/myreports.html | 46 +- vinny/templates/vinny/offcanvas.html | 2 +- vinny/templates/vinny/searchcases.html | 8 +- vinny/templates/vinny/searchresults.html | 10 +- vinny/urls.py | 1 + vinny/views.py | 479 +++++++------- 51 files changed, 1894 insertions(+), 1650 deletions(-) create mode 100644 lib/vince/chainqueryset.py create mode 100644 vince/static/vince/css/overrides.css mode change 100755 => 100644 vince/views.py create mode 100644 vinny/templates/vinny/base.html diff --git a/CHANGELOG.md b/CHANGELOG.md index 94bf2bd..b010bc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # VINCE Changelog + +## Version 2.0.3: 2022-12-14 + +* Major upgrade to Django 3.2 LTS target end byt 2024. Fixes related to Django upgrade in all libraries. +* Aded new QuerySet Paging library for performance extend chain with chainqs for QuerySet +* Asynchronous calls for most vinny/views via JSON through asyncLoad class +* Provide API Views 404 with JSON generic error +* Allow Session or API Token authentication to support API access from browser +* Provide better HTML text on access/permission violations by User. +* Fixes to CVE management API with CVE services 2.1 and CVEJSON5 support +* CSAF enchancements including TLP setup. Pending Customer engagement details publishing. +* Fix number of logging to include relevant data as part of log message + ## Version 1.50.6: 2022-11-04 * Allow Vendor Association when Ticket is associated with a Case diff --git a/bigvince/settings_.py b/bigvince/settings_.py index db37cb8..845db1a 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 = "1.50.6" +VERSION = "2.0.3" # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/ @@ -756,3 +756,51 @@ def get_secret(secret_arn): #allowed options: "prod", "test", "dev" CVE_SERVICES_API = os.environ.get("CVE_SERVICES_API", "test") + +#Django 3 and 4 upgrade +DEFAULT_AUTO_FIELD = "django.db.models.AutoField" + +#TLP related statements Note TLP2.0 says WHITE is replaced by CLEAR BUT CSAF2.0 is in TLP1.0 +#https://github.com/oasis-tcs/csaf/issues/591 +CSAF_DISTRIBUTION_OPTIONS = { + "RED": { + "distribution": { + "text": "For the eyes and ears of individual recipients only, no further disclosure.", + "tlp": { + "label": "RED", + "url": "https://www.first.org/tlp/" + } + } + }, + "AMBER": { + "distribution": { + "text": "Limited disclosure, recipients can only spread this on a need-to-know basis within their organization and its clients.", + "tlp": { + "label": "AMBER", + "url": "https://www.first.org/tlp/" + } + } + }, + "GREEN": { + "distribution": { + "text": "Limited disclosure, recipients can spread this within their community.", + "tlp": { + "label": "GREEN", + "url": "https://www.first.org/tlp/" + } + } + }, + "WHITE": { + "distribution": { + "text": "Recipients can spread this to the world, there is no limit on disclosure. ", + "tlp": { + "label": "WHITE", + "url": "https://www.first.org/tlp/" + } + } + } +} +#Choose how VINCE's private and public CSAF documents are mapped with TLP +#If you choose to disable TLP statements in CSAF comment out the MAP dictionary below +CSAF_TLP_MAP = { "PUBLIC": "WHITE", "PRIVATE": "AMBER" } + diff --git a/bigvince/storage_backends.py b/bigvince/storage_backends.py index a22ce49..c5b64df 100644 --- a/bigvince/storage_backends.py +++ b/bigvince/storage_backends.py @@ -39,11 +39,12 @@ class PrivateMediaStorage(S3Boto3Storage): #region_name = "us-east-1" custom_domain=False acl='private' + bucket_name = getattr(settings, 'PRIVATE_BUCKET_NAME') def __init__(self, *args, **kwargs): - kwargs['bucket'] = getattr(settings, 'PRIVATE_BUCKET_NAME') - kwargs['default_acl']='private' - kwargs['acl'] = 'private' + #kwargs['bucket'] = getattr(settings, 'PRIVATE_BUCKET_NAME') + #kwargs['default_acl']='private' + #kwargs['acl'] = 'private' #if hasattr(settings, 'ATTACHMENT_URL'): # kwargs['custom_domain'] = settings.ATTACHMENT_URL #else: @@ -59,11 +60,12 @@ class SharedMediaStorage(S3Boto3Storage): #region_name = "us-east-1" acl = 'private' custom_domain = False + bucket_name = getattr(settings, 'VINCE_SHARED_BUCKET') def __init__(self, *args, **kwargs): - kwargs['bucket'] = getattr(settings, 'VINCE_SHARED_BUCKET') - kwargs['default_acl']='private' - kwargs['acl'] = 'private' + #kwargs['bucket'] = getattr(settings, 'VINCE_SHARED_BUCKET') + #kwargs['default_acl']='private' + #kwargs['acl'] = 'private' #if hasattr(settings, 'VC_ATTACHMENT_URL'): # kwargs['custom_domain'] = settings.VC_ATTACHMENT_URL #else: @@ -77,9 +79,10 @@ class VRFReportsStorage(S3Boto3Storage): custom_domain=False region_name = settings.AWS_REGION acl = 'private' + bucket_name = getattr(settings, 'S3_INCOMING_REPORTS') def __init__(self, *args, **kwargs): - kwargs['bucket'] = getattr(settings, 'S3_INCOMING_REPORTS') - kwargs['default_acl']='private' - kwargs['acl'] = 'private' + #kwargs['bucket'] = getattr(settings, 'S3_INCOMING_REPORTS') + #kwargs['default_acl']='private' + #kwargs['acl'] = 'private' super(VRFReportsStorage, self).__init__(*args, **kwargs) diff --git a/cogauth/backend.py b/cogauth/backend.py index 465c028..d6f021b 100644 --- a/cogauth/backend.py +++ b/cogauth/backend.py @@ -33,7 +33,10 @@ from django.contrib.auth.backends import ModelBackend from django.contrib.auth import get_user_model from django.contrib.auth.hashers import make_password -from django.utils.six import iteritems +try: + from django.utils.six import iteritems +except: + from six import iteritems from django.contrib.auth.models import User from lib.warrant import Cognito from lib.warrant.exceptions import ForceChangePasswordException, SoftwareTokenException, SMSMFAException @@ -69,7 +72,7 @@ class CognitoUser(Cognito): def get_user_obj(self, username=None, attribute_list=[], metadata={}, attr_map={}): user_attrs = cognito_to_dict(attribute_list,CognitoUser.COGNITO_ATTRS) django_fields = [f.name for f in CognitoUser.user_class._meta.get_fields()] - logger.debug(user_attrs) + logger.debug(f"User attributes in Cognito is {user_attrs}") extra_attrs = {} # need to iterate over a copy for k, v in user_attrs.copy().items(): @@ -86,16 +89,15 @@ def get_user_obj(self, username=None, attribute_list=[], metadata={}, attr_map={ setattr(user.vinceprofile, k, v) #logger.debug(f"{k}:{v}") user.vinceprofile.save() - except: - logger.debug(traceback.format_exc()) - logger.debug("vinceprofile probably doesn't exist") + except Exception as e: + logger.debug(f"Vinceprofile probably doesn't exist for user {username}, error returned {e}") elif settings.VINCE_NAMESPACE == "vince": try: for k, v in extra_attrs.items(): setattr(user.usersettings, k, v) user.usersettings.save() except: - logger.debug("usersettings probably doesn't exist") + logger.debug(f"usersettings for {username} probably doesn't exist") else: try: user = CognitoUser.user_class.objects.get(username=username) @@ -103,22 +105,21 @@ def get_user_obj(self, username=None, attribute_list=[], metadata={}, attr_map={ setattr(user, k, v) user.save() except CognitoUser.user_class.DoesNotExist: - logger.debug("USER DOES NOT EXIST") + logger.debug(f"USER {username} DOES NOT EXIST") user = None if user: try: for k, v in extra_attrs.items(): setattr(user.vinceprofile, k, v) user.vinceprofile.save() - except: - logger.debug("vinceprofile probably does not exist") - logger.debug(traceback.format_exc()) + except Exception as e: + logger.debug(f"vinceprofile probably does not exist for {username}, returned error is {e}") try: for k, v in extra_attrs.items(): setattr(user.usersettings, k, v) user.usersettings.save() except: - logger.debug("usersettings probably doesn't exist") + logger.debug(f"usersettings probably doesn't exist for {username}") return user class CognitoAuthenticate(ModelBackend): @@ -133,10 +134,8 @@ def authenticate(self, request, username=None, password=None): username=username) try: - logger.debug("trying to authenticate %s" % username) + logger.debug(f"trying to authenticate {username}") cognito_user.authenticate(password) - logger.debug(cognito_user) - except ForceChangePasswordException: request.session['FORCEPASSWORD']=True request.session['username']=username @@ -156,16 +155,14 @@ def authenticate(self, request, username=None, password=None): return None except (Boto3Error, ClientError) as e: error_code = e.response['Error']['Code'] - logger.debug("error: {}".format(e)) - logger.debug(error_code) - logger.debug("ERROR AUTHENTICATING") + logger.debug(f"error authenticating user {username} error: {e} {error_code}") if error_code == "PasswordResetRequiredException": - logger.debug("reset password") + logger.debug(f"reset password needed for {username}") request.session['RESETPASSWORD']=True request.session['username']=username return None if error_code == "UserNotConfirmedException": - logger.debug("this user did not confirm their account") + logger.debug(f"User {username} did not confirm their account") #get user user = User.objects.filter(username=username).first() if user: @@ -242,10 +239,9 @@ def authenticate(self, request, username=None, password=None): # now we have a cognito user - set session variables and return if cognito_user: user = cognito_user.get_user() - logger.debug("USER IS...") - logger.debug(user) + logger.debug(f"USER IS {user}") else: - logger.debug("RETURNING NONE") + logger.debug("No user found for Cognito auth returning None") return None if user: @@ -254,10 +250,33 @@ def authenticate(self, request, username=None, password=None): request.session['REFRESH_TOKEN'] = cognito_user.refresh_token #request.session.save() - logger.debug("USER IS AUTHENTICATED!") + logger.info(f"USER {user} IS AUTHENTICATED!") return user +class CognitoAuthenticateAPI(ModelBackend): + + def authenticate(self, request): + """For rest_framework if successfully authenticated using CognitoAuth + the response wil include a tuple (request.user,request.auth) + In case of Session based authentications + request.user will be a Django User instance. + request.auth will be None. + https://www.django-rest-framework.org/api-guide/authentication/ + """ + try: + user = CognitoAuthenticate().authenticate(request) + if user: + logger.info(f"API authentication using session for User {user} success") + return user, None + else: + logger.warn(f"Failed API authentication using session for User {user} ") + raise exceptions.AuthenticationFailed(_('Invalid API session attempted')) + except Exception as e: + logger.warn(f"Failed API authentication for session error is {e}") + raise exceptions.AuthenticationFailed(_('Invalid API no session or token header was provided')) + + class HashedTokenAuthentication(TokenAuthentication): @@ -287,11 +306,17 @@ def authenticate_credentials(self, key): try: token = model.objects.select_related('user').get(key=hashed_key) except model.DoesNotExist: + logger.warn(f"Failed API auth for token that does not exist {key}") raise exceptions.AuthenticationFailed(_('Invalid token.')) - + except Exception as e: + logger.warn(f"Failed API auth for token Error {e}") + raise exceptions.AuthenticationFailed(_('Unknown Token error.')) + if not token.user.is_active: + logger.warn(f"Failed API auth for {token.user} user is inactive or deleted") raise exceptions.AuthenticationFailed(_('User inactive or deleted.')) + logger.info(f"Success user {token.user} is authenticated using API Token ") return (token.user, token) @@ -301,7 +326,7 @@ class JSONWebTokenAuthentication(BaseAuthentication): def authenticate(self, request): """Entrypoint for Django Rest Framework""" jwt_token = self.get_jwt_token(request) - logger.debug(jwt_token) + logger.debug(f"JSOWEb token ookup for {jwt_token}") if jwt_token is None: return None @@ -311,13 +336,13 @@ def authenticate(self, request): jwt_payload = token_validator.validate(jwt_token) except TokenError: raise exceptions.AuthenticationFailed() - logger.debug(jwt_payload) + logger.debug(f"JSONWeb returned payload is {jwt_payload}") username=jwt_payload['email'] user = User.objects.get(username=username) return (user, jwt_token) def get_jwt_token(self, request): - logger.debug(request.headers) + logger.debug(f"Collected headers for JSONWwebToken as {request.headers}") auth = get_authorization_header(request).split() if not auth or smart_text(auth[0].lower()) != "bearer": return None diff --git a/cogauth/templates/cogauth/mfa.html b/cogauth/templates/cogauth/mfa.html index 1c806dc..b7d7a6f 100644 --- a/cogauth/templates/cogauth/mfa.html +++ b/cogauth/templates/cogauth/mfa.html @@ -1,7 +1,6 @@ {% extends "vince/login.html" %} {% load widget_tweaks %} {% block title %}VINCE{% endblock %} - {% block content_title %}{% endblock %} {% block content %} diff --git a/cogauth/templates/cogauth/mfarequired.html b/cogauth/templates/cogauth/mfarequired.html index 93dd078..c094410 100644 --- a/cogauth/templates/cogauth/mfarequired.html +++ b/cogauth/templates/cogauth/mfarequired.html @@ -2,7 +2,8 @@ {% load i18n static %} - +{% block extrahead %} +{% endblock %} {% block content_title %}

VINCE Multi-factor authentication

diff --git a/cogauth/views.py b/cogauth/views.py index 990b330..41bfeb0 100644 --- a/cogauth/views.py +++ b/cogauth/views.py @@ -194,7 +194,6 @@ def post(self, request, *args, **kwargs): return render(request, 'cogauth/sms.html', {'form': form}) def form_valid(self, form): - logger.debug("in form valid") coguser = self.get_user() client= boto3.client('cognito-idp', region_name=settings.COGNITO_REGION) u = Cognito(settings.COGNITO_USER_POOL_ID, settings.COGNITO_APP_ID, @@ -212,7 +211,7 @@ def form_valid(self, form): try: u.send_verification(attribute='phone_number') except (Boto3Error, ClientError) as e: - logger.debug(traceback.format_exc()) + logger.debug(f"Error returned in cogauth as {e}") return redirect("cogauth:limitexceeded") return redirect("cogauth:verify_phone") @@ -230,14 +229,12 @@ def get_context_data(self, **kwargs): client= boto3.client('cognito-idp', region_name=settings.COGNITO_REGION) response = client.associate_software_token( AccessToken=coguser.access_token) - logger.debug(response) context['secretcode'] = response['SecretCode'] context['form'] = TOTPForm() context['qrtext'] = f"otpauth://totp/VINCE:{self.request.user.username}?secret={context['secretcode']}&issuer=VINCE" return context def form_valid(self, form): - logger.debug("in form valid") coguser = self.get_user() client= boto3.client('cognito-idp', region_name=settings.COGNITO_REGION) try: @@ -320,9 +317,8 @@ def post(self, request, *args, **kwargs): logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") password = self.request.POST.get('password', None) if password: - logger.debug("trying to auth...") + logger.debug(f"Trying to authenticate {self.request.user.username}") user = authenticate(request, username=self.request.user.username, password=password) - logger.debug(user) if (user is None) and request.session.get('MFAREQUIRED'): request.session['CHANGEMFA'] = True request.session.save() @@ -418,7 +414,7 @@ class EnableMFAView(LoginRequiredMixin,TokenMixin,GetUserMixin,TemplateView): def dispatch(self, request, *args, **kwargs): self.cognito_user = self.get_user() if self.cognito_user.mfa: - logger.debug("MFA already enabled") + logger.debug("MFA already enabled for self.request.user") self.request.user.vinceprofile.multifactor = True self.request.user.vinceprofile.save() return redirect("vinny:dashboard") @@ -448,7 +444,6 @@ class IndexView(TemplateView): def get_context_data(self, **kwargs): context = super(IndexView, self).get_context_data(**kwargs) code = self.request.GET.get('code', False) - logger.debug(code) if code: headers={'Content-Type': 'application/x-www-form-urlencoded'} data = { @@ -490,7 +485,6 @@ def get_success_url(self): def get_context_data(self, **kwargs): context = super(COGLoginView, self).get_context_data(**kwargs) - logger.debug("IN COGLOGIN") if settings.DEBUG: context['token_login'] = True @@ -514,18 +508,16 @@ def post(self, request, *args, **kwargs): else: self.request.session['LAST_LOGIN'] = "New" auth_login(request, user) - logger.debug("Checking permissions") - logger.debug(self.request.user.username) - logger.debug(self.request.user.is_authenticated) + logger.debug(f"Checking permissions for user {self.request.user.username} - is authenticated ? {self.request.user.is_authenticated} ") cognito_check_permissions(self.request) return super().form_valid(form) #return redirect("vinny:dashboard") else: if request.session.get('FORCEPASSWORD', False): - logger.debug("REDIRECT") + logger.debug(f"Redirecting due to force password change for {self.request.user}") return redirect('cogauth:password_register') if request.session.get('MFAREQUIRED', False): - logger.debug("REDIRECT") + logger.debug(f"Redirecing for MFA, user is {self.request.user}") if next_url: url = reverse(settings.MFA_REDIRECT_URL) + f"?next={next_url}" return redirect(url) @@ -639,7 +631,7 @@ def form_valid(self, form): u"Your code has expired. Try again." ])) else: - logger.debug(traceback.format_exc()) + logger.debug(f"Error in cognito-idp validation {e}") form._errors.setdefault("code", ErrorList([ u"Your code is incorrect.." ])) @@ -692,7 +684,7 @@ def form_valid(self, form): messages.error(self.request, e.response['Error']['Message']) return redirect("cogauth:login") else: - logger.debug(traceback.format_exc()) + logger.debug(f"Error in cognito-idp access token validation {e}") messages.error(self.request, e.response['Error']['Message']) form._errors.setdefault("code", ErrorList([ u"An error occurred when verifying your code." @@ -758,7 +750,7 @@ def form_valid(self, form): messages.error(self.request, e.response['Error']['Message']) return redirect("cogauth:login") else: - logger.debug(traceback.format_exc()) + logger.debug(f"Error in MFA validation validation {e}") messages.error(self.request, e.response['Error']['Message']) form._errors.setdefault("code", ErrorList([ u"An error occurred when verifying your code." @@ -792,18 +784,19 @@ def form_valid(self, form): next_url = self.request.GET.get('next') if next_url: - logger.debug(next_url) + logger.debug(f"NEXT URL provided by GET request {next_url}") try: if is_safe_url(next_url,set(settings.ALLOWED_HOSTS),True): return redirect(next_url) else: return redirect(settings.LOGIN_REDIRECT_URL) - except: + except Exception as e: + logger.debug(f"Error in redirection validator {e}") pass - logger.debug("redirecting...") + logger.debug(f"Redirecting to default LOGIN_URL {settings.LOGIN_REDIRECT_URL}") return redirect(settings.LOGIN_REDIRECT_URL) else: - logger.debug(traceback.format_exc()) + logger.debug(f"Login redirection error no tokens provided, error is {e}") form._errors.setdefault("code", ErrorList([ u"Your code is incorrect." ])) @@ -861,14 +854,10 @@ def form_valid(self, form): if self.request.session.get('CONFIRM_ID'): username = User.objects.using('vincecomm').filter(id=self.request.session.get('CONFIRM_ID')).first() try: - logger.debug(form.cleaned_data['code']) - logger.debug(username.username) c.confirm_sign_up(form.cleaned_data['code'], username=username.username) username.vinceprofile.email_verified=True username.vinceprofile.save() except ClientError as e: - logger.debug(e.response['Error']['Code']) - logger.debug(e.response['Error']['Message']) return render(self.request, 'cogauth/account_error.html', {'error_msg':e.response['Error']['Message']}) del(self.request.session['CONFIRM_ID']) if self.request.session.get('SERVICE'): @@ -992,7 +981,7 @@ def form_valid(self, form): return redirect('cogauth:password_change') else: form._errors.setdefault("username", ErrorList([ - u"Error Occurred. Please contact System Administrator." + u"Error Occurred. Please contact cert@cert.org" ])) return super().form_invalid(form) @@ -1046,7 +1035,7 @@ def form_valid(self, form): r = requests.post(GOOGLE_VERIFY_URL, data=data) result = r.json() if result['success']: - logger.debug("successful recaptcha validation") + logger.debug("Successful recaptcha validation") else: logger.debug(result) logger.debug("invalid recaptcha validation") @@ -1057,8 +1046,9 @@ def form_valid(self, form): dup = User.objects.using('vincecomm').filter(email__iexact = form.cleaned_data['email']) if dup: + reset_url = reverse('cogauth:init_password_reset') form._errors.setdefault("email", ErrorList([ - u'Email already exists. Usernames are CASE SENSITIVE. Or did you forget your password? Reset your password.' + f'Email already exists. Usernames are CASE SENSITIVE. Or did you forget your password? Reset your password.' ])) return super().form_invalid(form) @@ -1076,8 +1066,9 @@ def form_valid(self, form): f"Password not accepted: {e.response['Error']['Message']}"])) return super().form_invalid(form) elif e.response['Error']['Code'] == 'UsernameExistsException': + reset_url = reverse('cogauth:init_password_reset') form._errors.setdefault("email", ErrorList([ - u'Email already exists. Did you forget your password? Reset your password.' + f'Email already exists. Did you forget your password? Reset your password.' ])) return super().form_invalid(form) else: @@ -1136,7 +1127,6 @@ def get_context_data(self, **kwargs): def form_valid(self, form): c = get_cognito(self.request) - logger.debug(form.cleaned_data) self.request.user.first_name = form.cleaned_data['first_name'] self.request.user.last_name = form.cleaned_data['last_name'] self.request.user.vinceprofile.preferred_username=form.cleaned_data['preferred_username'] @@ -1150,9 +1140,7 @@ def form_valid(self, form): #set new timezone self.request.session['timezone'] = self.request.user.vinceprofile.timezone - logger.debug(user_attrs) c.update_profile(user_attrs) - logger.debug(form.cleaned_data) messages.success(self.request,'You have successfully updated your profile.') return super(UpdateProfileView, self).form_valid(form) diff --git a/lib/vince/chainqueryset.py b/lib/vince/chainqueryset.py new file mode 100644 index 0000000..2592ac9 --- /dev/null +++ b/lib/vince/chainqueryset.py @@ -0,0 +1,37 @@ +from itertools import islice, chain + +class chainqs(object): + """ + Chains multiple querysets (possibly of different models) and behaves as + one queryset. Supports minimal methods needed for use with + django.core.paginator. Replace chain() with this chainqs + """ + + def __init__(self, *subquerysets): + self.querysets = subquerysets + + def count(self): + """ + Performs a .count() for all subquerysets and returns the number of + records as an integer. + """ + return sum(qs.count() for qs in self.querysets) + + def _clone(self): + "Returns a clone of this queryset chain" + return self.__class__(*self.querysets) + + def _all(self): + "Iterates records in all subquerysets" + return chain(*self.querysets) + + def __getitem__(self, ndx): + """ + Retrieves an item or slice from the chained set of results from all + subquerysets. + """ + if type(ndx) is slice: + return list(islice(self._all(), ndx.start, ndx.stop, ndx.step or 1)) + else: + return islice(self._all(), ndx, ndx+1).next() + diff --git a/lib/vince/markdown_helpers.py b/lib/vince/markdown_helpers.py index a01f930..2bb45db 100644 --- a/lib/vince/markdown_helpers.py +++ b/lib/vince/markdown_helpers.py @@ -45,7 +45,7 @@ def __init__(self, users=None, **kwargs): md.Extension.__init__(self) self.user_list = users - def extendMarkdown(self, mde, md_globals): + def extendMarkdown(self, mde, md_globals={}): mde.inlinePatterns["user_mentions"] = UserMentionInlinePattern(self.UM_RE, users=self.user_list) class UserMentionInlinePattern(md.inlinepatterns.Pattern): diff --git a/requirements.txt b/requirements.txt index 555a173..be6ef40 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,68 +1,84 @@ -amqp==2.4.2 -appdirs==1.4.3 -asgiref==3.2.3 -asn1crypto==1.4.0 -awscli==1.20.57 -beautifulsoup4==4.8.2 -billiard==3.6.0.0 -bleach==3.3.1 -bleach-whitelist==0.0.10 +amqp==5.1.1 +appdirs==1.4.4 +asgiref==3.5.2 +asn1crypto==1.5.1 +async-timeout==4.0.2 +attrs==22.1.0 +awscli==1.27.11 +beautifulsoup4==4.11.1 +billiard==4.0.2 +bleach==5.0.1 +bleach-whitelist==0.0.11 boto==2.49.0 -boto3==1.18.57 -botocore==1.21.57 -certifi==2021.10.8 -cffi==1.13.2 -chardet==3.0.4 -charset-normalizer==2.0.9 -colorama==0.4.3 -cryptography==3.3.2 -cvelib==0.7.0 -click==8.0.4 -dictdiffer==0.8.1 -Django==2.2.28 -django-appconf==1.0.3 -django-countries==5.3.3 -django-environ==0.4.5 -django-qr-code==1.1.0 -django-ses==1.0.3 -django-storages==1.7.1 -django-widget-tweaks==1.4.3 -djangorestframework==3.11.2 -docutils==0.14 -ecdsa==0.13.3 -envs==1.3 -fs==2.4.11 +boto3==1.26.11 +botocore==1.29.11 +cached-property==1.5.2 +certifi==2022.9.24 +cffi==1.15.1 +chardet==5.0.0 +charset-normalizer==2.1.1 +click==8.1.3 +colorama==0.4.4 +cryptography==38.0.3 +cvelib==1.1.0 +Deprecated==1.2.13 +dictdiffer==0.9.0 +Django==3.2.16 +django-appconf==1.0.5 +django-countries==7.4.2 +django-environ==0.9.0 +django-extensions==3.2.1 +django-qr-code==3.1.1 +django-ses==3.2.2 +django-storages==1.13.1 +django-widget-tweaks==1.4.12 +djangorestframework==3.14.0 +docutils==0.16 +ecdsa==0.18.0 +envs==1.4 +fs==2.4.16 fs-s3fs==1.1.1 -future==0.17.1 -idna==3.3 -jmespath==0.9.3 -kombu==4.5.0 +future==0.18.2 +idna==3.4 +importlib-metadata==5.0.0 +importlib-resources==5.10.0 +install==1.3.5 +jmespath==1.0.1 +jsonschema==4.17.0 +kombu==5.2.4 M2Crypto==0.36.0 Markdown==3.1 -pinax-messages==2.0.2 -pip-autoremove==0.9.1 -psycopg2-binary==2.8.2 +packaging==21.3 +pinax-messages==3.0.0 +pip-autoremove==0.10.0 +pkgutil_resolve_name==1.3.10 +psycopg2-binary==2.9.5 pyasn1==0.4.8 -pycparser==2.19 -pycryptodome==3.14.1 -PyJWT==2.4.0 -python-dateutil==2.8.0 -python-gnupg==0.4.5 -python-jose==3.2.0 -pytz==2019.3 +pycparser==2.21 +pycryptodome==3.15.0 +pydantic==1.10.2 +PyJWT==2.6.0 +pyparsing==3.0.9 +pyrsistent==0.19.2 +python-dateutil==2.8.2 +python-gnupg==0.5.0 +python-jose==3.3.0 +pytz==2022.6 PyYAML==5.4.1 -qrcode==6.1 -redis==3.2.1 -requests==2.26.0 -rsa==4.7 -s3transfer==0.5.0 -simplejson==3.17.0 -six==1.12.0 -soupsieve==1.9.5 -sqlparse==0.3.0 -urllib3==1.26.7 -vine==1.3.0 -watchtower==1.0.6 +qrcode==7.3.1 +redis==4.3.4 +requests==2.28.1 +rsa==4.7.2 +s3transfer==0.6.0 +segno==1.5.2 +simplejson==3.18.0 +six==1.16.0 +soupsieve==2.3.2.post1 +sqlparse==0.4.3 +typing_extensions==4.4.0 +urllib3==1.26.12 +vine==5.0.0 +watchtower==3.0.0 webencodings==0.5.1 -setuptools - +wrapt==1.14.1 +zipp==3.10.0 diff --git a/vince/forms.py b/vince/forms.py index bbc917f..cd5481e 100644 --- a/vince/forms.py +++ b/vince/forms.py @@ -757,6 +757,7 @@ class CaseFilterForm(forms.Form): choices=(), required=False, widget=forms.CheckboxSelectMultiple(attrs={'class': 'ul_nobullet'})) + changes_to_publish = forms.BooleanField(required = False,label = 'changes_to_publish') def __init__(self, *args, **kwargs): super(CaseFilterForm, self).__init__(*args, **kwargs) diff --git a/vince/mailer.py b/vince/mailer.py index e245b62..5e867fc 100644 --- a/vince/mailer.py +++ b/vince/mailer.py @@ -32,7 +32,10 @@ import pytz from smtplib import SMTPException from django.conf import settings -from django.utils import six +try: + from django.utils import six +except: + import six from django.utils.safestring import mark_safe from vince.models import Ticket, Contact, VulnerabilityCase, CaseAction, CaseAssignment, AdminPGPEmail, VTDailyNotification, TicketQueue, CalendarEvent, FollowUp, EmailTemplate, EmailContact from vinny.models import Case, VTCaseRequest, VinceCommGroupAdmin, VinceCommContact, GroupContact, VinceCommEmail @@ -89,24 +92,30 @@ def get_vt_case_path(case_context): def is_case_action(title): logger.debug(f"TITLE IS {title}") - + + tkt_text = str(_("Email Bounce")) + + if tkt_text in title: + logger.warning(f"Email is a bounce returning 0 {tkt_text}") + return 0 + tkt_text = str(_("Vendor statement")) if tkt_text in title: #vendor status update - return 4 + return CaseAction.lookup('Status Change') tkt_text = str(_("New Message")) if tkt_text in title: # message - return 3 + return CaseAction.lookup('Message') tkt_text = str(_("Email")) if tkt_text in title: #new case task - return 8 + return CaseAction.lookup('Task Activity') return 0 @@ -137,9 +146,7 @@ def get_html_preference(user): def check_user_availability(user): # is this user OOF? - logger.debug(date.today()) today = date.today() - logger.debug(user) return CalendarEvent.objects.filter(event_id=CalendarEvent.OOF, date__range=(today,today), user=user).exists() def create_oof_ticket(user, action, ticket=None, case=None): @@ -224,7 +231,7 @@ def send_updateticket_mail(followup, files=None, user=None): if not prefs: # this user doesn't want email return - if 2 in list(prefs): + if 2 in prefs: # this user wants a daily summary of this vtdn = VTDailyNotification(user=ticket.assigned_to, action_type=action, @@ -337,7 +344,7 @@ def send_newticket_mail(followup, files, user=None): assigned_users = CaseAssignment.objects.filter(case=ticket.case) context.update(safe_case_context(ticket.case)) for asuser in assigned_users: - if asuser.assigned.email not in messages_sent_to and check_email_preferences(CaseAction.TASK_ACTIVITY, asuser.assigned): + if asuser.assigned.email not in messages_sent_to and check_email_preferences(CaseAction.lookup("Task Activity"), asuser.assigned): context['emailtitle'] = "New Task in Case %s" % ticket.case.get_vuid() send_ticket_mail( 'case_new_ticket', @@ -396,36 +403,35 @@ def send_newticket_mail(followup, files, user=None): def check_email_preferences(action_type, user): - if action_type == 1: - # Generic Case Change - pref = user.usersettings.settings.get('email_case_changes', ['1']) - elif action_type == 2: - # New Posts - pref = user.usersettings.settings.get('email_new_posts', ['1']) - elif action_type == 3: - # New Messages - pref = user.usersettings.settings.get('email_new_messages', ['1']) - elif action_type == 4: - # New Status Updates - pref = user.usersettings.settings.get('email_new_status', ['1']) - elif action_type == 8: - pref = user.usersettings.settings.get('email_tasks', ['1']) - else: - pref = [] - - if type(pref) is not list: - if pref == True: - pref = ['1'] + """ Check if email and user action require either an + immediate email or a digest email on a daily basis """ + + if not user.is_active: + logger.warning("User {user.username} is inactive returning []") + return [] + + pref_type = CaseAction.USER_ACTION_MAP.get(action_type,None) + + if pref_type == None: + #pref_type does not map to any user actions requiring + #sending of an email + return [] + #pref_type will be belong to user prefence such as + #('email_case_changes', 'email_tasks') + prefs = user.usersettings.settings.get(pref_type,[1]) + + #pref can be True or a list of integers. If pref is True + #turn this into array [1] send email but not in daily digest mode + if type(prefs) is not list: + if prefs: + prefs = [1] else: - pref = [] + prefs = [] - # now turn them all into ints - pref = [int(i) for i in pref] - - return pref + return prefs def emailable_action(action): - if action.action_type in [0, 4, 7, 8, 9, 11, 12]: + if action.action_type in CaseAction.EMAILABLE_ACTIONS: return False return True @@ -456,7 +462,7 @@ def send_updatecase_mail(action, new_user=None): return # this is an assignment update pref = check_email_preferences(action.action_type, new_user) - if 1 in list(pref): + if 1 in pref: context['case']['assignee'] = action.user send_case_mail( "case_assigned_to", @@ -475,11 +481,10 @@ def send_updatecase_mail(action, new_user=None): vtdn.save() return - oof = True + #Assume everyone is OOF unless we get at least one user + alloof = True for user in assigned_users: - logger.debug(user.assigned) if user.assigned not in [action.user, new_user]: - logger.debug("HERE") pref = check_email_preferences(action.action_type, user.assigned) if 1 in pref: if get_html_preference(user.assigned): @@ -496,12 +501,12 @@ def send_updatecase_mail(action, new_user=None): #if assigned users are OOF if not(check_user_availability(user.assigned)): - logger.debug("HELLO") - oof = False - # at least 1 user is not oof + logger.debug(f"User {user.assigned} is Out Of Office") + alloof = False + - if (len(assigned_users) and oof): - logger.debug("HOW???") + if (len(assigned_users) and alloof): + logger.debug(f"All assigned users are Out of Office {assigned_users}") #all assigned users are oof, so cut a triage ticket to alert #someone that something happened create_oof_ticket(user.assigned, action.title, None, case) diff --git a/vince/middleware.py b/vince/middleware.py index 229a871..e48899c 100644 --- a/vince/middleware.py +++ b/vince/middleware.py @@ -31,7 +31,6 @@ from django.utils import timezone from django.http import HttpResponseRedirect from django.urls import resolve -from urllib.parse import urlparse import logging import pytz import traceback @@ -50,12 +49,8 @@ def process_request(self, request): host = None server = None path = request.get_full_path() - if request.session.session_key: - #logger.debug(request.META.get('HTTP_REFERER')) - #referer = request.META.get('HTTP_REFERER') - #referer = urlparse(referer) - #logger.debug(referer.netloc) - logger.debug(f"Path is {path}") + #if request.session.session_key: + #logger.debug(f"Path is {path}") scheme = "http" if not request.is_secure() else "https" try: diff --git a/vince/models.py b/vince/models.py index 66edf49..08e5d05 100644 --- a/vince/models.py +++ b/vince/models.py @@ -45,6 +45,9 @@ import uuid import re import traceback +#Django 3 and up +from django.db.models import JSONField +import io logger = logging.getLogger(__name__) @@ -129,10 +132,16 @@ def _get_triage(self): triage = property(_get_triage) -class OldJSONField(fields.JSONField): +class OldJSONField(JSONField): + """ This was due to legacy support in Django 2.2. from_db_value + should be explicitily sepcified when extending JSONField """ + def db_type(self, connection): return 'json' + def from_db_value(self, value, expression, connection): + return value + # Create your models here. GROUP_TYPE = ( ('srmail', 'SRMail List'), @@ -667,7 +676,9 @@ class Ticket(models.Model): CLOSED_STATUS = 4 DUPLICATE_STATUS = 5 IN_PROGRESS_STATUS = 6 - + #Maximum related activity to display + MAX_ACTIVITY = 20 + STATUS_CHOICES = ( (OPEN_STATUS, _('Open')), (REOPENED_STATUS, _('Reopened')), @@ -891,6 +902,9 @@ def _can_be_resolved(self): can_be_resolved = property(_can_be_resolved) def get_actions(self): + #zero or negative max_activity is unlimited + if self.MAX_ACTIVITY > 0: + self.followup_set.order_by('-date')[:self.MAX_ACTIVITY] return self.followup_set.order_by('-date') @@ -2124,9 +2138,12 @@ def _get_assigned_to(self): get_assigned_to = property(_get_assigned_to) def _get_status_html(self): + changed = "" + if self.changes_to_publish: + changed = "U" if self.status == self.ACTIVE_STATUS: if self.published: - return f"{self.get_status_display()} Published" + return f"{self.get_status_display()} Published{changed}" return f"{self.get_status_display()}" else: @@ -2319,9 +2336,8 @@ class CaseAction(Action): """ - TASK_ACTIVITY = 8 - ACTION_TYPE = ( + (0, "Generic"), (1, "VinceTrack"), (2, "Post"), (3, "Message"), @@ -2329,13 +2345,27 @@ class CaseAction(Action): (5, "VinceComm Artifact"), (6, "Threads"), (7, "Vendor Viewed"), - (TASK_ACTIVITY, "Task Activity"), + (8, "Task Activity"), (9, "Publish Vul Note"), (10, "Status Change Notify"), (11, "Edit Post"), (12, "Post Removed"), ) + #Actions that can be assigned or re-assigned + ASSIGN_ACTIONS = [0,1,9] + + #Actions that can trigger an email + EMAILABLE_ACTIONS = [0, 4, 7, 8, 9, 11, 12] + + #Actions map to email_preference_types in user preferences + #User preferences is a pickled object in User.usersettings + USER_ACTION_MAP = { 1: 'email_case_changes', + 2: 'email_new_posts', + 3: 'email_new_messages', + 4: 'email_new_status', + 8: 'email_tasks'} + action_type = models.IntegerField( default=0 ) @@ -2380,6 +2410,12 @@ def publishactions(cls, case): action_type=9 ).order_by('-date') + def lookup(search): + """ Lookup a action type by a number or by a name """ + if type(search) == int: + return next((x[1] for x in CaseAction.ACTION_TYPE if x[0] == search), None) + else: + return next((x[0] for x in CaseAction.ACTION_TYPE if x[1] == search), None) # message = models.IntegerField( # _('VinceComm Message ID'), @@ -3708,7 +3744,7 @@ class CVEAllocation(models.Model): assigner = models.EmailField( max_length=254, - default=settings.CONTACT_EMAIL) + default='cert@cert.org') cve_name = models.CharField( _('CVE ID'), diff --git a/vince/static/vince/css/overrides.css b/vince/static/vince/css/overrides.css new file mode 100644 index 0000000..7001e1c --- /dev/null +++ b/vince/static/vince/css/overrides.css @@ -0,0 +1,50 @@ +.login #container { + position: absolute; + top: 50%; + bottom: 0; + left: 0; + right: 0; +} +.login #container input:not([type=button]):not([type=submit]) { + background-color: transparent; + color: #333; +} +/* Add darkmode */ +.blackbody #container { + +} + +@media only screen and (max-width: 600px) { + /* Force headernav to top of screen on mobile*/ + .headernav { + top: 0px !important; + } + /* Let scroll out menu cover all the screen*/ + #offCanvas.off-canvas.is-open { + height: auto !important; + } + #offCanvas ul.vertical.menu > li.menu-close { + position: fixed; + right: 15%; + top: 0px; + border: none; + padding-top: 0px; + } + #header { + height: auto !important; + } + #header .topbar-redbar .h2.subtitle { + display: none !important; + } + .mainbody .app-top-panel { + padding: 2px !important; + } + h2 { + font-size: 1.5em; + font-weight: bold; + } + + .login #container { + top: 0%; + } +} diff --git a/vince/static/vince/js/case_search.js b/vince/static/vince/js/case_search.js index 3310f58..cdf10f5 100644 --- a/vince/static/vince/js/case_search.js +++ b/vince/static/vince/js/case_search.js @@ -113,7 +113,15 @@ $(document).ready(function() { form.attachEvent("submit", searchTickets); } else { form.addEventListener("submit", searchTickets); - } + }; + + $('#changes_to_publish').on('click',function() { + /* If a Case is Updated (updates requiring a re-publish) it is assumed + it has already been Published first. The status=2 is Published search */ + if($('#changes_to_publish').prop('checked')) + $('#id_status_2').prop('checked',true); + searchTickets(); + }); $("#filter_by_dropdown_select_all_0").click(function(){ $("#id_status input[type=checkbox]").prop('checked', $(this).prop('checked')); @@ -171,6 +179,11 @@ $(document).ready(function() { $("#id_team input[value="+val+"]").prop('checked', false); searchTickets(); }); + + $(document).on("click",'.removechanges_to_publish', function(event) { + $('#changes_to_publish').prop('checked',false); + searchTickets(); + }); searchTickets(); diff --git a/vince/static/vince/js/dashboard.js b/vince/static/vince/js/dashboard.js index f99e53a..ffae605 100644 --- a/vince/static/vince/js/dashboard.js +++ b/vince/static/vince/js/dashboard.js @@ -239,56 +239,4 @@ $(document).ready(function() { }); - var lines = 12; //Choose how many lines you would like to initially show - var buttonspacing = 0; //Choose Button Spacing - var buttonside = 'left'; //Choose the Side the Button will display on: 'right' or 'left' \ - - var animationspeed = 1000; //Choose Animation Speed (Milliseconds) - //Do not edit below - var lineheight = 0; - if ($('.text_content').css("line-height")) { - lineheight = $('.text_content').css("line-height").replace("px", ""); - } - startheight = lineheight * lines; - var shortheight = $('.textheightshort').css('max-height'); - // Instead take the max-height set on the textheightshort/textheightlong attributes - //$('.text_container').css({'max-height' : startheight }); - - var buttonheight = $('.showfull').height(); - $('div.long_text_container').css({'padding-bottom' : (buttonheight + buttonspacing ) }); - - if(buttonside == 'right'){ - $('.showfull').css({'bottom' : (buttonspacing / 2), 'right' : buttonspacing }); - } else{ - $('.showfull').css({'bottom' : (buttonspacing / 2), 'left' : buttonspacing }); - } - - $('.moretext').on('click', function(){ - var newheight = $(this).parent('div.long_text_container').find('div.text_content').height(); - $(this).parent('div.long_text_container').find('div.text_container').animate({'max-height' : newheight }, animationspeed ); - $(this).hide().siblings('.lesstext').show(); - $(this).next().next('.scrollnext').fadeIn(); - - }); - - $('.lesstext').on('click', function(){ - var shortelem = $(this).parent('div.long_text_container').find('div.text_container').hasClass('textheightshort'); - var newheight = startheight; - if (shortelem) { - newheight = shortheight; - } - var h = $(this).parent('div.long_text_container').find('div.text_content').height(); - $(this).parent('div.long_text_container').find('div.text_container').animate({'max-height' : newheight }, animationspeed ); - $(this).hide().siblings('.moretext').show(); - $(this).next('.scrollnext').fadeOut(); - }); - - $('div.long_text_container').each(function(){ - if( $(this).find('div.text_content').height() > $(this).find('div.text_container').height() ){ - $(this).find('.showfull.moretext').show(); - - } - }); - - }); diff --git a/vince/static/vince/js/teamdash.js b/vince/static/vince/js/teamdash.js index f31905c..826a85c 100644 --- a/vince/static/vince/js/teamdash.js +++ b/vince/static/vince/js/teamdash.js @@ -242,57 +242,4 @@ $(document).ready(function() { $(".dashtkt").hide(); $(".dashpostdiv").show(); }); - - var lines = 12; //Choose how many lines you would like to initially show - var buttonspacing = 0; //Choose Button Spacing - var buttonside = 'left'; //Choose the Side the Button will display on: 'right' or 'left' \ - - var animationspeed = 1000; //Choose Animation Speed (Milliseconds) - //Do not edit below - var lineheight = 0; - if ($('.text_content').css("line-height")) { - lineheight = $('.text_content').css("line-height").replace("px", ""); - } - startheight = lineheight * lines; - var shortheight = $('.textheightshort').css('max-height'); - // Instead take the max-height set on the textheightshort/textheightlong attributes - //$('.text_container').css({'max-height' : startheight }); - - var buttonheight = $('.showfull').height(); - $('div.long_text_container').css({'padding-bottom' : (buttonheight + buttonspacing ) }); - - if(buttonside == 'right'){ - $('.showfull').css({'bottom' : (buttonspacing / 2), 'right' : buttonspacing }); - } else{ - $('.showfull').css({'bottom' : (buttonspacing / 2), 'left' : buttonspacing }); - } - - $('.moretext').on('click', function(){ - var newheight = $(this).parent('div.long_text_container').find('div.text_content').height(); - $(this).parent('div.long_text_container').find('div.text_container').animate({'max-height' : newheight }, animationspeed ); - $(this).hide().siblings('.lesstext').show(); - $(this).next().next('.scrollnext').fadeIn(); - - }); - - $('.lesstext').on('click', function(){ - var shortelem = $(this).parent('div.long_text_container').find('div.text_container').hasClass('textheightshort'); - var newheight = startheight; - if (shortelem) { - newheight = shortheight; - } - var h = $(this).parent('div.long_text_container').find('div.text_content').height(); - $(this).parent('div.long_text_container').find('div.text_container').animate({'max-height' : newheight }, animationspeed ); - $(this).hide().siblings('.moretext').show(); - $(this).next('.scrollnext').fadeOut(); - }); - - $('div.long_text_container').each(function(){ - if( $(this).find('div.text_content').height() > $(this).find('div.text_container').height() ){ - $(this).find('.showfull.moretext').show(); - - } - }); - - }); diff --git a/vince/static/vince/js/tickets.js b/vince/static/vince/js/tickets.js index d2aa9fa..2fa5cb4 100644 --- a/vince/static/vince/js/tickets.js +++ b/vince/static/vince/js/tickets.js @@ -1,31 +1,31 @@ /*######################################################################### -# VINCE -# -# Copyright 2022 Carnegie Mellon University. -# -# NO WARRANTY. THIS CARNEGIE MELLON UNIVERSITY AND SOFTWARE ENGINEERING -# INSTITUTE MATERIAL IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON -# UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER EXPRESSED OR IMPLIED, -# AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR -# PURPOSE OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE -# MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND -# WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT. -# -# Released under a MIT (SEI)-style license, please see license.txt or contact -# permission@sei.cmu.edu for full terms. -# -# [DISTRIBUTION STATEMENT A] This material has been approved for public -# release and unlimited distribution. Please see Copyright notice for non-US -# Government use and distribution. -# -# Carnegie Mellon®, CERT® and CERT Coordination Center® are registered in the -# U.S. Patent and Trademark Office by Carnegie Mellon University. -# -# This Software includes and/or makes use of Third-Party Software each subject -# to its own license. -# -# DM21-1126 -######################################################################## + # VINCE + # + # Copyright 2022 Carnegie Mellon University. + # + # NO WARRANTY. THIS CARNEGIE MELLON UNIVERSITY AND SOFTWARE ENGINEERING + # INSTITUTE MATERIAL IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON + # UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER EXPRESSED OR IMPLIED, + # AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR + # PURPOSE OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE + # MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND + # WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT. + # + # Released under a MIT (SEI)-style license, please see license.txt or contact + # permission@sei.cmu.edu for full terms. + # + # [DISTRIBUTION STATEMENT A] This material has been approved for public + # release and unlimited distribution. Please see Copyright notice for non-US + # Government use and distribution. + # + # Carnegie Mellon®, CERT® and CERT Coordination Center® are registered in the + # U.S. Patent and Trademark Office by Carnegie Mellon University. + # + # This Software includes and/or makes use of Third-Party Software each subject + # to its own license. + # + # DM21-1126 + ######################################################################## */ @@ -85,7 +85,7 @@ function del_tag(ticketcc_taggle, tag, modal){ $.post(url, {'state': 0, 'csrfmiddlewaretoken': csrftoken, 'tag': tag, 'del_tag':true}, function (data) { console.log("success removing"); - }) + }) .fail(function (data) { if (data['error']) { alert("An error occurred while trying to delete this tag: " + data['error']); @@ -123,7 +123,7 @@ $(document).ready(function() { var old_tags = JSON.parse(document.getElementById('old_tags').textContent); var other_tags = JSON.parse(document.getElementById('other_tags').textContent); /*var subscribed_users = JSON.parse(document.getElementById('subscribed_users').textContent); - var assignable = JSON.parse(document.getElementById('assignable').textContent);*/ + var assignable = JSON.parse(document.getElementById('assignable').textContent);*/ var ticketcc_taggle = new Taggle('ticketcc_taggle', { tags: old_tags, tagFormatter: function(li) { @@ -149,18 +149,18 @@ $(document).ready(function() { }, }); auto(other_tags, ticketcc_taggle, adddepmodal); - /*$.getJSON(window.href, { - 'subscribed_users': true, - 'csrfmiddlewaretoken': csrftoken - }, function (data) { - subscribed_users = data['subscribed_users']; - for (i=0; i < subscribed_users.length; i++) { - console.log(subscribed_users[i]); - ticketcc_taggle.add(subscribed_users[i]); - } - auto(data['assignable_users'], ticketcc_taggle); - });*/ - } + /*$.getJSON(window.href, { + 'subscribed_users': true, + 'csrfmiddlewaretoken': csrftoken + }, function (data) { + subscribed_users = data['subscribed_users']; + for (i=0; i < subscribed_users.length; i++) { + console.log(subscribed_users[i]); + ticketcc_taggle.add(subscribed_users[i]); + } + auto(data['assignable_users'], ticketcc_taggle); + });*/ + } $(document).on("click", '#reassign', function(event) { @@ -395,7 +395,7 @@ $(document).ready(function() { $(this).closest('div').prev().prev().prev('textarea').replaceWith(p); $(this).closest('div').hide(); $(this).closest('div').prev().show(); - + }); $(document).on("click", ".followup-cancel", function(event) { @@ -463,7 +463,7 @@ $(document).ready(function() { } }); } else { return; } - + }); $(document).on("click", "#quickclose", function(event) { @@ -502,71 +502,21 @@ $(document).ready(function() { }); } }); - - var lines = 12; //Choose how many lines you would like to initially show - var buttonspacing = 0; //Choose Button Spacing - var buttonside = 'left'; //Choose the Side the Button will display on: 'right' or 'left' - var animationspeed = 1000; //Choose Animation Speed (Milliseconds) - //Do not edit below - var lineheight = 0; - if ($('.text_content').css("line-height")) { - lineheight = $('.text_content').css("line-height").replace("px", ""); - } - startheight = lineheight * lines; - var shortheight = $('.textheightshort').css('max-height'); - // Instead take the max-height set on the textheightshort/textheightlong attributes - //$('.text_container').css({'max-height' : startheight }); - - var buttonheight = $('.showfull').height(); - $('div.long_text_container').css({'padding-bottom' : (buttonheight + buttonspacing ) }); - - if(buttonside == 'right'){ - $('.showfull').css({'bottom' : (buttonspacing / 2), 'right' : buttonspacing }); - } else{ - $('.showfull').css({'bottom' : (buttonspacing / 2), 'left' : buttonspacing }); - } - - $('.moretext').on('click', function(){ - var newheight = $(this).parent('div.long_text_container').find('div.text_content').height(); - $(this).parent('div.long_text_container').find('div.text_container').animate({'max-height' : newheight }, animationspeed ); - $(this).hide().siblings('.lesstext').show(); - $(this).next().next('.scrollnext').fadeIn(); - - }); - - $('.lesstext').on('click', function(){ - var shortelem = $(this).parent('div.long_text_container').find('div.text_container').hasClass('textheightshort'); - var newheight = startheight; - if (shortelem) { - newheight = shortheight; - } - var h = $(this).parent('div.long_text_container').find('div.text_content').height(); - $(this).parent('div.long_text_container').find('div.text_container').animate({'max-height' : newheight }, animationspeed ); - $(this).hide().siblings('.moretext').show(); - $(this).next('.scrollnext').fadeOut(); - }); - - $('div.long_text_container').each(function(){ - if( $(this).find('div.text_content').height() > $(this).find('div.text_container').height() ){ - $(this).find('.showfull.moretext').show(); - - } - }); $('.scrollnext').click(function () { var scrollnext = $(this).parent().parent().parent().next().offset(); if (scrollnext) { - $("html, body").animate({ + $("html, body").animate({ scrollTop: $(this).parent().parent().parent().next().offset().top - }, 600); + }, 600); } else { $("html, body").animate({ scrollTop: $(this).parent().offset().top - }, 600); + }, 600); } return false; }); - + $('.edit-btn').qtip({ content: 'Reply to Email', style: {classes: 'qtip-youtube'}, @@ -581,25 +531,24 @@ $(document).ready(function() { } } }); - - + + $('.email-detail').each(function () { $(this).qtip({ - content: $(this).attr("title"), - style: {classes: 'qtip-youtube'}, + content: $(this).attr("title"), + style: {classes: 'qtip-youtube'}, position: { corner: { - target:'bottomLeft', - tooltip: 'bottomLeft' + target:'bottomLeft', + tooltip: 'bottomLeft' }, adjust: { - x:-60, - y:5 + x:-60, + y:5 } } }); }); - $(document).keypress(function(event) { var keycode = (event.keyCode ? event.keyCode : event.which); if(keycode == '13'){ @@ -687,5 +636,5 @@ $(document).ready(function() { }); } }); - -}) +}); + diff --git a/vince/static/vince/js/vince.js b/vince/static/vince/js/vince.js index dc7b961..cdb0425 100644 --- a/vince/static/vince/js/vince.js +++ b/vince/static/vince/js/vince.js @@ -1,31 +1,31 @@ /*######################################################################### -# VINCE -# -# Copyright 2022 Carnegie Mellon University. -# -# NO WARRANTY. THIS CARNEGIE MELLON UNIVERSITY AND SOFTWARE ENGINEERING -# INSTITUTE MATERIAL IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON -# UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER EXPRESSED OR IMPLIED, -# AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR -# PURPOSE OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE -# MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND -# WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT. -# -# Released under a MIT (SEI)-style license, please see license.txt or contact -# permission@sei.cmu.edu for full terms. -# -# [DISTRIBUTION STATEMENT A] This material has been approved for public -# release and unlimited distribution. Please see Copyright notice for non-US -# Government use and distribution. -# -# Carnegie Mellon®, CERT® and CERT Coordination Center® are registered in the -# U.S. Patent and Trademark Office by Carnegie Mellon University. -# -# This Software includes and/or makes use of Third-Party Software each subject -# to its own license. -# -# DM21-1126 -######################################################################## + # VINCE + # + # Copyright 2022 Carnegie Mellon University. + # + # NO WARRANTY. THIS CARNEGIE MELLON UNIVERSITY AND SOFTWARE ENGINEERING + # INSTITUTE MATERIAL IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON + # UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER EXPRESSED OR IMPLIED, + # AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR + # PURPOSE OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE + # MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND + # WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT. + # + # Released under a MIT (SEI)-style license, please see license.txt or contact + # permission@sei.cmu.edu for full terms. + # + # [DISTRIBUTION STATEMENT A] This material has been approved for public + # release and unlimited distribution. Please see Copyright notice for non-US + # Government use and distribution. + # + # Carnegie Mellon®, CERT® and CERT Coordination Center® are registered in the + # U.S. Patent and Trademark Office by Carnegie Mellon University. + # + # This Software includes and/or makes use of Third-Party Software each subject + # to its own license. + # + # DM21-1126 + ######################################################################## */ /* Global functions for VINCETrack */ @@ -38,15 +38,14 @@ function getCookie(name) { var cookie = jQuery.trim(cookies[i]); // Does this cookie string begin with the name we want? if (cookie.substring(0, name.length + 1) === (name + '=')) { - cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + cookieValue = decodeURIComponent(cookie.substring(name.length + + 1)); break; } } } return cookieValue; } - - function copyToClipboard(text) { var $temp = $(""); $("body").append($temp); @@ -54,16 +53,17 @@ function copyToClipboard(text) { document.execCommand("copy"); $temp.remove(); } +/* Across the board loader functions */ function lockunlock(f,divmain,divhtml) { if(f) { - /* Show search is in progress */ + /* Show search is in progress */ $(divmain).css({opacity:0.5}); - if($(divhtml + ' > .loading').length < 1) + if($(divhtml).find('.loading').length < 1) $(divhtml).prepend($('#hiddenloading').html()); } else { - /* Back to normal */ - $(divmain).css({opacity:1}); - $(divhtml + ' > .loading').remove(); + /* Back to normal */ + $(divmain).css({opacity:1}); + $(divhtml).find('.loading').remove(); } } function delaySearch(callfun,wait) { @@ -71,29 +71,143 @@ function delaySearch(callfun,wait) { return (...args) => { clearTimeout(timeout); timeout = setTimeout( - function () { + function () { callfun.apply(this, args); - }, wait); + }, wait); }; } + +function asyncLoad(fdiv,furl,fmethod,pdiv,formpost,silent) { + /* asyncload from furl(URL) to fdiv(Content div identifier) whose + pdiv(Parent div identified) using fmethod(GET or POST) + in the case of POST use the formpost(Form identified) that + should be serialized. The var silent is now suppressed. + */ + if(!furl) + furl = fdiv.getAttribute("href"); + if(!furl) { + console.log("This div is not valid for async load return"); + return; + } + if(!fmethod) { + if(fdiv.getAttribute("method")) + fmethod = fdiv.getAttribute("method") + else + fmethod = "GET"; + } + if(!pdiv) { + if(fdiv.getAttribute("parentdiv")) + pdiv = fdiv.getAttribute("parentdiv"); + else + pdiv = fdiv; + } + let fdata = null; + if(fmethod == "POST") { + if (!formpost) { + if(fdiv.getAttribute("form")) + formpost = fdiv.getAttribute("form"); + } + if($(formpost).serialize()) + fdata = $(formpost).serialize(); + } + lockunlock(true,pdiv,fdiv); + window.txhr = $.ajax({ + url : furl, + type: fmethod, + data: fdata, + success: function(data) { + lockunlock(false,pdiv,fdiv); + $(fdiv).html(data); + }, + error: function() { + lockunlock(false,pdiv,fdiv); + console.log(arguments); + /* The var silent is no longer being used */ + $(fdiv).html("Content failed to be collected for display! "+ + "See console log for details."); + }, + complete: function() { + /* Just safety net */ + lockunlock(false,pdiv,fdiv); + window.txhr = null; + } + }); +} + +function clickAsyncLoad(e) { + /* Async loader for buttons will use data attributes + data-qparams="all=true" data-divid="ticket_activity" + */ + let el = e.target; + if($(el).data("divid")) { + let fdiv = document.getElementById($(el).data("divid")); + let pdiv = fdiv; + if(fdiv.getAttribute('href')) { + let furl = fdiv.getAttribute('href'); + if($(el).attr("data-completeurl") == furl) { + console.log("Already loaded content as per URL return"); + return true; + } + let fmethod = "GET" + if ($(el).data("qparams")) + furl = furl + "?" + $(el).data("qparams"); + if($(el).data("method")) + fmethod = $(el).data("method"); + if($(el).data("parentdiv")) + pdiv = $(el).data("parentdiv"); + let formpost = null; + if($(el).data("form")) + formpost = $(el).data("form"); + asyncLoad(fdiv,furl,fmethod,pdiv,formpost,true); + /* Mark download as complete */ + $(el).attr("data-completeurl",furl); + } + } +} +function pageAsyncLoad(e) { + /* Async loader for buttons will use closest div.asyncload */ + let el = e.target; + let page = parseInt($(el).attr('next')); + if(isNaN(page)) { + console.log(el); + console.log("Invalid page number returning " + $(el).attr('next')); + return; + } + let jfdiv = $(el).closest('div.asyncload'); + if(jfdiv.length == 1 && page) { + let fdiv = jfdiv[0]; + let furl = fdiv.getAttribute('href'); + let fmethod = fdiv.getAttribute('method'); + if(fmethod == 'POST') { + if(fdiv.getAttribute("form")) { + let formpost = fdiv.getAttribute("form"); + $(formpost).find('input[name="page"]').val(page); + } + } else { + furl = furl + "?page=" + String(page); + } + asyncLoad(fdiv,furl,fmethod); + } +} + $(function () { /*$('span[title]').qtip({ - style: {classes: 'qtip-youtube'} - });*/ + style: {classes: 'qtip-youtube'} + });*/ /* - $('span').tooltip({ - tooltipClass: 'tooltipster-default' - }); - - $('i').tooltip({ - tooltipClass: 'tooltipster-default' - }); + $('span').tooltip({ + tooltipClass: 'tooltipster-default' + }); + + $('i').tooltip({ + tooltipClass: 'tooltipster-default' + }); - $('button').tooltip({ - tooltipClass: 'tooltipster-default' - }); -*/ + $('button').tooltip({ + tooltipClass: 'tooltipster-default' + }); + */ $('[vince-tooltip]').tooltip ({ tooltipClass: 'vince-tooltip-class', @@ -122,29 +236,150 @@ $(function () { /* scroll button */ $(window).scroll(function () { - if ($(this).scrollTop() > 100) { - $('.scrollup').fadeIn(); - } else { - $('.scrollup').fadeOut(); - } - }); - $('.scrollup').click(function () { - $("html, body").animate({ - scrollTop: 0 - }, 600); - return false; - }); - $(document).keyup(function(e) { - if (e.key === "Escape") { - if(window.txhr && 'abort' in window.txhr) { - console.log("Aborting search because user hit Escape"); - window.txhr.abort(); - window.txhr = null; - } - } + if ($(this).scrollTop() > 100) { + $('.scrollup').fadeIn(); + } else { + $('.scrollup').fadeOut(); + } + }); + $('.scrollup').click(function () { + $("html, body").animate({ + scrollTop: 0 + }, 600); + return false; }); + function post_content_refresh(ctx) { + /* This function runs on all divs that can use SHOW_MORE + and SHOW_LESS*/ + if(!ctx) + ctx = document.body; + var lines = 12; + var buttonspacing = 0; + var buttonside = 'left'; + var animationspeed = 1000; + var lineheight = 0; + if ($('.text_content',ctx).css("line-height")) { + lineheight = $('.text_content',ctx).css("line-height"). + replace("px", ""); + } + var startheight = lineheight * lines; + var shortheight = $('.textheightshort',ctx).css('max-height'); + var buttonheight = $('.showfull',ctx).height(); + $('div.long_text_container',ctx).css( + {'padding-bottom' : (buttonheight + buttonspacing ) }); -}); + if(buttonside == 'right'){ + $('.showfull',ctx).css( + {'bottom' : (buttonspacing / 2), 'right' : buttonspacing }); + } else{ + $('.showfull',ctx).css( + {'bottom' : (buttonspacing / 2), 'left' : buttonspacing }); + } + + $('.moretext',ctx).on('click', function(){ + var newheight = $(this).parent('div.long_text_container'). + find('div.text_content').height(); + $(this).parent('div.long_text_container'). + find('div.text_container'). + animate({'max-height' : newheight }, animationspeed ); + $(this).hide().siblings('.lesstext').show(); + $(this).next().next('.scrollnext').fadeIn(); + + }); + $('.lesstext',ctx).on('click', function(){ + var shortelem = $(this).parent('div.long_text_container'). + find('div.text_container').hasClass('textheightshort'); + var newheight = startheight; + if (shortelem) { + newheight = shortheight; + } + var h = $(this).parent('div.long_text_container'). + find('div.text_content').height(); + $(this).parent('div.long_text_container'). + find('div.text_container'). + animate({'max-height' : newheight }, animationspeed ); + $(this).hide().siblings('.moretext').show(); + $(this).next('.scrollnext').fadeOut(); + }); + $('div.long_text_container',ctx).each(function(){ + var elm = $(this).find('div.text_content'); + if( elm.height() > $(this).find('div.text_container').height()){ + $(this).find('.showfull.moretext').show(); + } + }); + } + /* Run post_content_refresh once and then on any divs that are + dynamically generated */ + post_content_refresh(); + function mutation_refresh(mu,ob) { + console.log(arguments); + if(mu.length && mu[0].target) { + if($(mu[0].target).attr("onmutate")) { + post_content_refresh(mu[0].target); + } + } + } + $('.asyncrefresh').each(function() { + console.log(arguments); + let ob = new MutationObserver(mutation_refresh); + ob.observe(this,{childList:true}); + }); + $(document).keyup(function(e) { + if (e.key === "Escape") { + if(window.txhr && 'abort' in window.txhr) { + console.log("Aborting search because user hit Escape"); + window.txhr.abort(); + window.txhr = null; + } + } + }); + /* All asyncload class div with autoload should be populated async on + document.ready() */ + $('div.asyncload.autoload').each(function(_,fdiv) { + asyncLoad(fdiv); + }); + /* Create async onclick loaders via buttons if any */ + $('.asyncclick').on("click", clickAsyncLoad); + /* Create async page loaders if any */ + $(document).on("click",".asyncpage",pageAsyncLoad); + function loadiftarget(formpost,e) { + $(formpost).find('input[name="page"]').val(1); + let fdivid = $(formpost).attr("targetdivid") + if(fdivid && document.getElementById(fdivid)) { + e.preventDefault(); + e.stopPropagation(); + asyncLoad(document.getElementById(fdivid)); + } + } + $('.asyncform').on("submit keyup keypress",function(e) { + let keyCode = e.keyCode || e.which; + if (keyCode === 13) { + e.preventDefault(); + e.stopPropagation(); + return false; + } + loadiftarget(e.target,e); + }); + $('.asyncdelaysearch').on("keyup keypress", delaySearch(function(e) { + let keyCode = e.keyCode || e.which; + if (keyCode === 13) { + e.preventDefault(); + e.stopPropagation(); + return false; + } + loadiftarget($(e.target).closest('form'),e); + },1000)); + $('.asyncform input').not('.asyncdelaysearch').on("change",function(e) { + loadiftarget($(e.target).closest('form'),e); + }); + $('.select_all_checkbox').on('click',function(e) { + $(e.target).closest('.select_all_group') + .find('input[type="checkbox"]') + .prop('checked',$(e.target).prop('checked')); + }); + + +}); diff --git a/vince/static/vince/js/vinny_dashboard.js b/vince/static/vince/js/vinny_dashboard.js index b81ca09..f14c1b4 100644 --- a/vince/static/vince/js/vinny_dashboard.js +++ b/vince/static/vince/js/vinny_dashboard.js @@ -28,73 +28,7 @@ ######################################################################## */ +/* +Moved all async works to vincecomm.js -function searchThreads(e, newpage) { - var csrftoken = getCookie('csrftoken'); - - if (e) { - e.preventDefault(); - } - var page = 1; - if (newpage) { - page = newpage; - } - - var url = $("#searchform").attr("action"); - lockunlock(true,'div.mainbody,div.vtmainbody','#casecontainer'); - window.txhr = $.ajax({ - url : url, - type: "POST", - data: $('#searchform').serialize(), - success: function(data) { - lockunlock(false,'div.mainbody,div.vtmainbody','#casecontainer'); - $("#casecontainer").html(data); - }, - error: function() { - lockunlock(false,'div.mainbody,div.vtmainbody','#casecontainer'); - console.log(arguments); - alert("Search failed or canceled! See console log for details."); - }, - complete: function() { - /* Just safety net */ - lockunlock(false,'div.mainbody,div.vtmainbody','#casecontainer'); - window.txhr = null; - } - }); -} - - -$(document).ready(function() { - var filter_msg = document.getElementById("filter_threads"); - if (filter_msg) { - filter_msg.addEventListener("keyup",delaySearch(function(event) { - searchThreads(event); - },1000)); - } - - $("input[id^='id_owner_']").change(function() { - searchThreads(); - }); - - $("#filter_by_dropdown_select_all_0").click(function(){ - $("input[type=checkbox]").prop('checked', $(this).prop('checked')); - searchThreads(); - - }); - - var form = document.getElementById('searchform'); - if (form) { - if (form.attachEvent) { - form.attachEvent("submit", searchThreads); - } else { - form.addEventListener("submit", searchThreads); - } - } - - $(document).on("click", '.search_notes', function(event) { - var page = $(this).attr('next'); - $("#id_page").val(page); - searchThreads(0, page); - }); - -}); + */ diff --git a/vince/templates/vince/base_site.html b/vince/templates/vince/base_site.html index 54fa45d..a504af6 100644 --- a/vince/templates/vince/base_site.html +++ b/vince/templates/vince/base_site.html @@ -6,6 +6,7 @@ + {{ form.media }} {% endblock %} {% block title %}VINCE{% endblock %} diff --git a/vince/templates/vince/searchcases.html b/vince/templates/vince/searchcases.html index fc06325..1b3c8ca 100644 --- a/vince/templates/vince/searchcases.html +++ b/vince/templates/vince/searchcases.html @@ -37,6 +37,12 @@

Cases

{% render_field form.status %} +
+
+ + +
diff --git a/vince/templates/vince/searchresults.html b/vince/templates/vince/searchresults.html index c70ad25..ee90514 100644 --- a/vince/templates/vince/searchresults.html +++ b/vince/templates/vince/searchresults.html @@ -5,6 +5,9 @@
Search parameters:
+ {% if form.changes_to_publish.value %} + U Updated + {% endif %} {% if form.tag.value %} {{form.tag.value}} {% endif %} diff --git a/vince/templates/vince/ticket.html b/vince/templates/vince/ticket.html index 046749c..c4ce6b9 100644 --- a/vince/templates/vince/ticket.html +++ b/vince/templates/vince/ticket.html @@ -87,8 +87,8 @@

Artifacts


-
-{% include 'vince/ticket_activity.html' %} +
+

{% url 'vince:update' ticket.id as the_url %} diff --git a/vince/templates/vince/ticket_activity.html b/vince/templates/vince/ticket_activity.html index cd068fb..eeac2a5 100644 --- a/vince/templates/vince/ticket_activity.html +++ b/vince/templates/vince/ticket_activity.html @@ -3,7 +3,22 @@ {% load ticket_to_link %}
-

{% trans "Activity" %}

+

+ {% trans "Activity" %} + + {% if more %} + [Showing {{ ticket.MAX_ACTIVITY }} of + {{ ticket.followup_set.count }}] + + {% else %} + [{{ ticket.followup_set.count }}] + {% endif %} + +

@@ -12,7 +27,7 @@

{% trans "Activity" %}

{% for followup in ticket.get_actions %}
- +
{% autoescape off %} {% if followup.user %} diff --git a/vince/templates/vince/ticket_table.html b/vince/templates/vince/ticket_table.html index bb37a18..c0c0848 100644 --- a/vince/templates/vince/ticket_table.html +++ b/vince/templates/vince/ticket_table.html @@ -72,7 +72,7 @@

{{ ticket.title|escape|email_to_user }}

{{ user.usersettings.preferred_username }}
-
+
diff --git a/vince/views.py b/vince/views.py old mode 100755 new mode 100644 index 56b13b7..ecedf69 --- a/vince/views.py +++ b/vince/views.py @@ -98,6 +98,31 @@ logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) + +def get_cve_info(context,acc): + context['cve_service'] = acc + if not acc.active: + context['account_error'] = "Account has been deactivated either manually, or due to authentication failures" + return context + cve_lib = cvelib.CveApi(acc.email, acc.org_name, acc.api_key, env=settings.CVE_SERVICES_API) + if cve_lib.ping() is None: + try: + context['account'] = cve_lib.show_user(acc.email) + context['org'] = cve_lib.show_org() + context['quota'] = cve_lib.quota() + context['cve_users'] = list(cve_lib.list_users()) + except Exception as e: + context['account_error'] = str(e) + if hasattr(e, 'response') and e.response: + r = e.response + if hasattr('status_code',r) and r.status_code == 401: + logger.warning(f"Authorization failed for {acc.email}, disabling this CVE API account to avoid further errors") + acc.active = False + acc.save() + else: + context['service_down'] = 1 + return context + def object_to_json_response(obj, status=200): """ Given an object, returns an HttpResponse object with a JSON serialized @@ -345,7 +370,7 @@ def autocomplete_contact(request, name): emails = ",".join(emails) pgp_keys = list(ContactPgP.objects.filter(contact=contact, revoked=False).exclude(pgp_key_data__isnull=True).exclude(pgp_key_data__exact='').values_list('pgp_key_id', flat=True)) pgp_key_info = list(ContactPgP.objects.filter(contact=contact, revoked=False).values_list('pgp_key_id', 'pgp_email', 'pgp_key_data','startdate', 'enddate')) - logger.debug(pgp_key_info) + logger.debug(f"PGP information sent for {contact} is {pgp_key_info}") pgp_key_data = "" if len(pgp_keys) == 1: pgp_key_data = ContactPgP.objects.filter(contact=contact, revoked=False).exclude(pgp_key_data__isnull=True).exclude(pgp_key_data__exact='').first() @@ -505,7 +530,6 @@ def autocomplete_case_references(request, pk): if r["url"] not in references: references.append(r["url"]) data = json.dumps(references) - logger.debug(data) return HttpResponse(data, 'application/json') else: raise PermissionDenied() @@ -691,7 +715,7 @@ def _add_group_permissions(email, vince_user): user.vinceprofile.pending = False user.vinceprofile.save() else: - logger.debug("No VINCE user with this email address") + logger.debug(f"No VINCE user with this email address {email}") def _remove_group_permissions(email, vinny_contact, vince_user): # now do we have a current VINCE user with this email? @@ -858,7 +882,7 @@ def process_query_for_tags(s): def process_query(s, live=True): query = re.sub(r'[!\'()|&<>]', ' ', s).strip() # get rid of empty quotes - query = re.sub(r'""', '', s) + query = re.sub(r'""', '', query) if query == '"': return None if query.startswith(settings.CASE_IDENTIFIER): @@ -906,7 +930,7 @@ def test_func(self): return is_in_group_vincetrack(self.request.user) def post(self, request, *args, **kwargs): - logger.debug(self.request.POST) + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") reminder = get_object_or_404(VinceReminder, id=self.request.POST.get('id')) if self.request.POST.get('later'): reminder.alert_date = timezone.now() + timedelta(days=1) @@ -957,7 +981,7 @@ def form_invalid(self, form): return JsonResponse({'errors':form.errors}, status=401) def post(self, request, *args, **kwargs): - logger.debug(f"NewReminder Post: {self.request.POST}") + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") form = ReminderForm(request.POST) assignable_users = User.objects.filter(is_active=True, groups__name='vince').exclude(id=self.request.user.id).order_by(User.USERNAME_FIELD) form.fields['user'].choices = [(self.request.user.id, 'Myself')] + [(u.id, u.get_full_name()) for u in assignable_users] @@ -1012,7 +1036,7 @@ def post(self, request, *args, **kwargs): return self.form_invalid(form) def form_valid(self, form): - logger.debug(self.request.POST) + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") newtickets = self.request.POST.getlist('new_tickets[]') newtix = [] uptix = [] @@ -1266,7 +1290,6 @@ def test_func(self): return False def get(self, request, *args, **kwargs): - logger.debug(self.kwargs['path']) try: attachment = Attachment.objects.filter(uuid=self.kwargs['path']).first() # if the UUID is not valid, this will throw a ValidationError @@ -1338,10 +1361,8 @@ def get_context_data(self, **kwargs): return context def post(self, request, *args, **kwargs): + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") file = get_object_or_404(VinceFile, id=self.kwargs['pk']) - - logger.debug("IN UNATTACH FILE!!!!") - post = file.post vulnote = file.vulnote #getattachment @@ -1351,7 +1372,7 @@ def post(self, request, *args, **kwargs): file.to_remove = True file.save() else: - logger.debug("REMOVE FILE!!!") + logger.debug(f"Removing File {vt.file}") vt.file.file.delete(save=False) vt.file.delete() @@ -1821,7 +1842,6 @@ def get_context_data(self, **kwargs): if unassigned: initial['owner'] = ['0'] status = self.request.GET.getlist('status') - logger.debug(status) tag = self.request.GET.get('tag') if tag: initial['tag'] = tag @@ -1926,7 +1946,6 @@ def get_context_data(self, **kwargs): return context def form_valid(self, form): - #logger.debug("IN FORM AVALID") ticket = get_object_or_404(Ticket, id=self.kwargs['pk']) artifact = form.save(ticket=ticket, user=self.request.user) tags = self.request.POST.getlist('taggles[]') @@ -2095,7 +2114,6 @@ def post(self, request, *args, **kwargs): reverse=True) paginator = Paginator(activity, 10) - print(paginator.page(1)) return render(request, self.template_name, {'activity': activity, 'total': activity.count(), 'case': case, 'allow_edit': True}) @@ -2162,6 +2180,9 @@ def post(self, request, *args, **kwargs): cases = list(CaseAssignment.objects.filter(assigned__groups__in=groups).values_list('case', flat=True)) res = res.filter(id__in=cases) + if 'changes_to_publish' in self.request.POST: + res = res.filter(vulnote__date_published__isnull=False,changes_to_publish=True) + if self.request.POST.get('tag'): tags = process_query_for_tags(self.request.POST['tag']) casetags = CaseTag.objects.filter(tag__in=tags).values_list('case__id', flat=True) @@ -2339,7 +2360,7 @@ def post(self, request, *args, **kwargs): if ticket.case: # does the new group have access to this case? if CasePermissions.objects.filter(case=ticket.case, group=new_group, group_read=True, group_write=True).exists(): - logger.debug("new_group has access to case") + logger.debug(f"new_group {new_group} has access to case {ticket.case}") # this case can be read/write by the group - so change it to appropriate case queue new_queue = QueuePermissions.objects.filter(queue__queue_type=2, group__in=[new_group], group_read=True, group_write=True).first() if new_queue: @@ -2407,7 +2428,7 @@ def post(self, request, *args, **kwargs): self.request, _(f"Ticket has been reassigned to {new_group.name}")) return redirect("vince:dashboard") - logger.debug(form.errors) + logger.debug(f"Errors in {self.__class__.__name__} is {form.errors}") if error: messages.error( self.request, @@ -2433,7 +2454,6 @@ def test_func(self): return False def form_valid(self, form): - logger.debug("IN TICKET VALID") ticket = get_object_or_404(Ticket, id=self.kwargs['pk']) ticket.resolution = form.cleaned_data['resolution'] ticket.save() @@ -2466,7 +2486,6 @@ def test_func(self): return False def form_valid(self, form): - logger.debug("IN TICKET VALID") ticket = form.save() #if form.cleaned_data['case']: # queue = get_case_case_queue(form.cleaned_data['case']) @@ -2482,8 +2501,8 @@ def form_valid(self, form): if contactformset.is_valid(): try: contactformset.save() - except: - logger.debug("this was probably unchanged, I don't know why this fails") + except Exception as e: + logger.debug(f"This was probably unchanged, I don't know why this fails, Error returned is {e}") pass return HttpResponseRedirect(ticket.get_absolute_url()) @@ -2652,9 +2671,9 @@ def get_context_data(self, **kwargs): context['team'] = user_groups[0].name context['other_teams'] = user_groups.exclude(id=user_groups[0].id) user_groups=[user_groups[0]] - logger.debug(user_groups) + logger.debug(f"User {self.request.user} is in {user_groups}") assignable_users = User.objects.filter(is_active=True, groups__in=user_groups).order_by(User.USERNAME_FIELD).distinct() - logger.debug(assignable_users) + logger.debug(f"User {self.request.user} can be assigned to {assignable_users}") readable_queues = TicketQueue.objects.filter(queuepermissions__group__in=user_groups, queuepermissions__group_write=True).distinct() if self.request.POST: @@ -2759,7 +2778,6 @@ def get_context_data(self, **kwargs): return context form = None - logger.debug(queue) if self.request.POST: form = CreateCaseRequestForm(self.request.POST) @@ -2775,8 +2793,8 @@ def get_context_data(self, **kwargs): if "ticket_id" in self.kwargs: ticket = get_object_or_404(Ticket, id=self.kwargs['ticket_id']) - description = ticket.description - cr = CaseRequest(product_name=description) + cr = {} + cr['description'] = ticket.description if ticket.case: cr['product_name'] = ticket.case.product_name cr['product_version'] = ticket.case.product_version @@ -2862,18 +2880,15 @@ def post(self, request, *args, **kwargs): user = self.request.user unmute = False settings = user.usersettings.settings - logger.debug(user.usersettings.settings) if user.usersettings.settings.get('muted_cases'): + logger.debug(f"User {user} has muted cases {muted_cases}") muted_cases = user.usersettings.settings['muted_cases'] - logger.debug(muted_cases) if case.id in muted_cases: #this case has already been muted, unmute this case: muted_cases.remove(case.id) - logger.debug(muted_cases) settings.update({'muted_cases': muted_cases}) user.usersettings.settings = settings user.usersettings.save() - logger.debug(user.usersettings.settings) unmute = True else: muted_cases.append(case.id) @@ -2885,7 +2900,7 @@ def post(self, request, *args, **kwargs): settings.update({'muted_cases': [case.id]}) user.usersettings.settings = settings user.usersettings.save() - logger.debug(user.usersettings.settings) + if unmute: button = " Mute Reminders" @@ -3093,7 +3108,7 @@ def post(self, request, *args, **kwargs): vc_vulnote = share_vulnote(vulnote) ca = CaseAction(case=vulnote.case, title="Shared Vulnerability Note in VinceComm", user=self.request.user, vulnote=vulnote.current_revision, - action_type=1) + action_type=CaseAction.lookup('VinceTrack')) ca.save() vc_case = Case.objects.filter(vince_id=vulnote.case.id).first() vc_case.note = vc_vulnote @@ -3180,8 +3195,8 @@ def post(self, request, *args, **kwargs): bucket.copy(copy_source, vulnote.case.vuid + "_" + vt_file.file.filename) vt_file.shared = True vt_file.save() - except: - logger.debug(traceback.format_exc()) + except Exception as e: + logger.debug(f"Error in {self.__class__.__name__} error is {e}") messages.error( self.request, _(f"Error copying file {vt_file.file.filename}. Publish failed.")) @@ -3224,8 +3239,8 @@ def post(self, request, *args, **kwargs): try: publish_vul_note(vu_info, f"vu_{vulnote.case.vuid}.json") - except: - logger.debug(traceback.format_exc()) + except Exception as e: + logger.debug(f"Error in {self.__class__.__name__} error is {e}") send_error_sns(vulnote.case.vu_vuid, "Publishing vul note", traceback.format_exc()) messages.error( self.request, @@ -3238,7 +3253,7 @@ def post(self, request, *args, **kwargs): ca = CaseAction(case=vulnote.case, title="Published Vulnerability Note", user=self.request.user, vulnote=vulnote.current_revision, - action_type=9) + action_type=CaseAction.lookup("Publish Vul Note")) ca.save() #mark published in vincecomm - this will run through and copy the note and status @@ -3366,7 +3381,7 @@ def copy_vulnote(vulnote, database): if vul.deleted: vp_vul = NoteVulnerability.objects.using(database).filter(case_increment=vul.case_increment, note=vpnote.vulnote).first() if vp_vul: - logger.debug("DELETE VULNERABILITY!!!") + logger.debug(f"Deleting Vul Note {vp_vul}") vp_vul.delete() continue # only copy over vulnerabilities that have a CVE @@ -3392,7 +3407,7 @@ def copy_vulnote(vulnote, database): (vtvendor.statement_date != vp_vendor.statement_date) or (vtvendor.addendum != vp_vendor.addendum)): updated = True - logger.debug("VENDOR CHANGED STATEMENT") + logger.debug(f"Vendor has changed Statement Vendor: {vp_vendor} with {vtvendor.statement}") vp_vendor, created = Vendor.objects.using(database).update_or_create( note=vpnote.vulnote, @@ -3439,7 +3454,7 @@ def copy_vulnote(vulnote, database): # need to update the date for this vendor vp_vendor.dateupdated = timezone.now() vp_vendor.save(using=database) - logger.debug("UPDATED VENDOR STATEMENT FOR NEW TIME %s" % vp_vendor.vendor) + logger.debug(f"Updated vendor statement for Vendor: {vp_vendor.vendor}") else: vpnote = VulnerabilityNote(content = vulnote.current_revision.content, @@ -3497,7 +3512,7 @@ def copy_vulnote(vulnote, database): vp_vulstatus.statement = status.statement vp_vulstatus.save(using=database) else: - logger.debug("why is the NoteVulnerability not there?") + logger.debug(f"Strange error: NoteVulnerability not found for {vpnote}") # now make it public vpnote.published = True vpnote.save(using=database) @@ -3545,7 +3560,6 @@ def get_context_data(self, **kwargs): context['review'] = reviews.first() if reviews.count() > 1: context['next'] = reviews[1] - logger.debug(context['next']) context['reviews'] = reviews.exclude(id=context['review'].id) return context @@ -3568,9 +3582,7 @@ def get_context_data(self, **kwargs): context['review'] = review #get reviews for this ticket: if review.ticket: - logger.debug("HERE") context['reviews'] = VulNoteReview.objects.filter(ticket=review.ticket, complete=True, date_complete__lte=review.date_complete).exclude(id=review.id).order_by('-date_complete') - logger.debug(context['reviews']) if context['reviews']: context['next'] = context['reviews'].first() return context @@ -3646,13 +3658,10 @@ def get_context_data(self, **kwargs): def post(self, request, *args, **kwargs): logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") - form = VulNoteReviewForm(self.request.POST) - logger.debug(form) vulnote = get_object_or_404(VulNote, id=self.kwargs['pk']) rev = VulNoteReview() if form.is_valid(): - logger.debug("FORM IS VALID") #is this an edit? check = VulNoteReview.objects.filter(vulnote=vulnote.current_revision.id, reviewer=self.request.user, @@ -3678,7 +3687,7 @@ def post(self, request, *args, **kwargs): assigned_to=self.request.user, title__icontains="vulnerability note for publishing").first() if tkt: - logger.debug("WE HAVE A REVIEW TICKET") + logger.debug(f"Review Ticket found for {tkt}") rev.ticket = tkt rev.save() if rev.complete: @@ -3729,8 +3738,7 @@ def post(self, request, *args, **kwargs): return JsonResponse({'redirect':reverse('vince:case', args=[vulnote.case.id]) + '#vulnote'}, status=200) else: - logger.debug("FORM IS INVALID") - logger.debug(form.errors) + logger.debug(f"Form in {self.__class__.__name__} is invalid error is {form.errors}") return JsonResponse({'error':form.errors}, status=401) @@ -3939,7 +3947,6 @@ def form_valid(self, form): rev.user_message = form.cleaned_data['summary'] rev.references = form.cleaned_data['references'] rev.deleted = False - logger.debug(rev) rev.set_from_request(self.request) vulnote.add_revision(rev) @@ -3948,14 +3955,13 @@ def form_valid(self, form): date = timezone.now(), user=self.request.user, comment=form.cleaned_data['summary'], - action_type = 1) + action_type = CaseAction.lookup('VinceTrack')) action.save() vulnote.case.changes_to_publish = True vulnote.case.save() artifacts = list(map(int, self.request.POST.getlist('artifacts[]'))) - logger.debug(artifacts) # record the artifacts that have been added to the vulnote arts = get_all_artifacts(vulnote.case) @@ -3967,7 +3973,6 @@ def form_valid(self, form): artifact.save() formvuls = list(map(int, self.request.POST.getlist('vuls[]'))) - logger.debug(formvuls) vuls = Vulnerability.casevuls(vulnote.case) for vul in vuls: if vul.id in formvuls: @@ -4055,11 +4060,10 @@ def form_valid(self, form): date = timezone.now(), user=self.request.user, comment=self.request.POST['summary'], - action_type = 1) + action_type = CaseAction.lookup('VinceTrack')) action.save() artifacts = list(map(int, self.request.POST.getlist('artifacts[]'))) - logger.debug(artifacts) # record the artifacts that have been added to the vulnote arts = get_all_artifacts(vulnote.case) @@ -4071,7 +4075,6 @@ def form_valid(self, form): artifact.save() formvuls = list(map(int, self.request.POST.getlist('vuls[]'))) - logger.debug(formvuls) vuls = Vulnerability.casevuls(vulnote.case) for vul in vuls: if vul.id in formvuls: @@ -4197,7 +4200,6 @@ def get(self, request, *args, **kwargs): 'comment': '' } kwargs['ticket_id']=self.kwargs['pk'] - logger.debug("REDIRECT!!!") return update_ticket(request, self.kwargs['pk']) if 'assign' in request.GET: if not(has_queue_write_access(self.request.user, ticket.queue)): @@ -4208,9 +4210,7 @@ def get(self, request, *args, **kwargs): 'title': ticket.title, 'comment': '' } - logger.debug(request.GET['assign']) kwargs['ticket_id']=self.kwargs['pk'] - logger.debug("REDIRECT!!!") return update_ticket(request, self.kwargs['pk']) elif 'close' in request.GET and ticket.status == Ticket.RESOLVED_STATUS: if not(has_queue_write_access(self.request.user, ticket.queue)): @@ -4291,8 +4291,14 @@ def test_func(self): def get_context_data(self, **kwargs): context = super(TicketActivityView, self).get_context_data(**kwargs) - context['ticketpage']=1 + context['ticketpage'] = 1 context['ticket'] = get_object_or_404(Ticket, id=self.kwargs['pk']) + context['more'] = False + if context['ticket'].followup_set.count() > Ticket.MAX_ACTIVITY: + if self.request.GET.get('all',None): + context['ticket'].MAX_ACTIVITY = 0 + else: + context['more'] = True return context class TicketAutoAssign(LoginRequiredMixin, TokenMixin, UserPassesTestMixin, generic.FormView): @@ -4335,8 +4341,7 @@ def test_func(self): def get_subscribers(ticket_id): ticket = get_object_or_404(Ticket, id=ticket_id) users = [ticketcc.user.usersettings.preferred_username for ticketcc in ticket.ticketcc_set.all()] - logger.debug("subscribed_users") - logger.debug(ticket.ticketcc_set.all()) + logger.debug(f"Subscribed_users for {ticket_id} are {str(ticket.ticketcc_set.all())}") allusers = [ user.usersettings.preferred_username for user in User.objects.filter(is_active=True, groups__name='vince').exclude(usersettings__preferred_username__isnull=True)] return JsonResponse({'subscribed_users': users, 'assignable_users':allusers}, status=200) @@ -4379,7 +4384,6 @@ def get(self, request, *args, **kwargs): if 'take' in request.GET: if not(has_queue_write_access(self.request.user, ticket.queue)): raise PermissionDenied() - logger.debug("UPDATE TICKET!") request.POST = { 'owner': request.user.id, 'title': ticket.title, @@ -4391,7 +4395,6 @@ def get(self, request, *args, **kwargs): if not(has_queue_write_access(self.request.user, ticket.queue)): raise PermissionDenied() - logger.debug("ASSIGN TICKET!") if request.GET['assign']: request.POST = { 'owner': request.GET['assign'], @@ -4430,7 +4433,7 @@ def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs) def post(self, request, *args, **kwargs): - logger.debug(f"{self.__class__} post!") + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") ticket = get_object_or_404(Ticket, id=self.kwargs['pk']) kwargs['ticket_id']=self.kwargs['pk'] @@ -4500,7 +4503,6 @@ def get_context_data(self, **kwargs): #context['ticketcc_list'] = [ ticketcc.user.username for ticketcc in context['ticket'].ticketcc_set.all() ] context['old_tags'] = [tag.tag for tag in context['ticket'].tickettag_set.all()] - logger.debug(context['old_tags']) context['other_tags'] = [tag.tag for tag in TagManager.objects.filter(tag_type=1).filter(Q(team__in=user_groups)|Q(team__isnull=True)).exclude(tag__in=context['old_tags']).order_by('tag').distinct('tag')] #context['other_tags'] = [tag.tag for tag in TicketTag.objects.exclude(ticket=context['ticket']).order_by('tag').distinct('tag')] context['form'] = AddArtifactForm() @@ -4571,8 +4573,8 @@ def post(self, request, *args, **kwargs): if not title: # no change happened return HttpResponseRedirect(case.get_absolute_url()) - logger.debug("create case action") - ca = CaseAction(case=case, user=self.request.user, comment=comment, title=title, action_type=1) + logger.debug(f"Create Case action for {case} from user {request.user.username}") + ca = CaseAction(case=case, user=self.request.user, comment=comment, title=title, action_type=CaseAction.lookup('VinceTrack')) ca.save() return HttpResponseRedirect(case.get_absolute_url()) @@ -4599,7 +4601,7 @@ def add_tag(case_id, user, tag): user=user) fup.save() else: - logger.debug("invalid tag - tag doesn't exist in tag manager") + logger.debug(f"Invalid tag - tag {tag} doesn't exist in tag manager") return JsonResponse({'tag': tag, 'case': case.id, 'error': "Invalid Tag."}, status=401) else: return JsonResponse({'tag': tag, 'case': case.id, 'error': "Tag is too long. Max 50 characters."}, status=401) @@ -4627,7 +4629,7 @@ def addComment(case_id, user, comment): comment = comment.replace('X-HELPDESK-COMMENT-VERBATIM', '{% verbatim %}{%').replace( 'X-HELPDESK-COMMENT-ENDVERBATIM', '%}{% endverbatim %}') - ca = CaseAction(case_id=case_id, user=user, comment=comment, title=f"{user.usersettings.vince_username} added a comment", action_type=1) + ca = CaseAction(case_id=case_id, user=user, comment=comment, title=f"{user.usersettings.vince_username} added a comment", action_type=CaseAction.lookup('VinceTrack')) ca.save() class CloseTicketandTagView(LoginRequiredMixin, TokenMixin, UserPassesTestMixin, generic.FormView): @@ -4652,8 +4654,8 @@ def get_context_data(self, **kwargs): context['ticket'] = get_object_or_404(Ticket, id=self.kwargs['ticket_id']) cr = CaseRequest.objects.filter(ticket_ptr_id=self.kwargs['ticket_id']).first() - referer = self.request.META.get('HTTP_REFERER') - logger.debug(f"REFERER IS {referer}") + referer = self.request.META.get('HTTP_REFERER',"") + logger.debug(f"HTTP Referrer is {referer}") #is this ticket from a VINCE user? if context['ticket'].submitter_email: @@ -4695,8 +4697,8 @@ def form_valid(self, form): ticket.close_reason = form.cleaned_data['close_choice'] ticket.save() # look up cr in vincecomm - referer = self.request.META.get('HTTP_REFERER') - logger.debug(f"REFERER IS {referer}") + referer = self.request.META.get('HTTP_REFERER',"") + logger.debug(f"HTTP_Referrer header is {referer}") cr = CaseRequest.objects.filter(id=ticket.id).first() vtcr = None if cr: @@ -4753,7 +4755,7 @@ def form_valid(self, form): crfup.save() elif int(form.cleaned_data['send_email']) == 3: - logger.debug("send message to VINCE User") + logger.debug(f"send message to VINCE User {vc_user}") user_lookup = User.objects.using('vincecomm').filter(email=ticket.submitter_email).first() sender = User.objects.using('vincecomm').filter(email=self.request.user.email).first() subject = f"[{ticket.ticket_for_url}] {email_template.subject} {ticket.title}" @@ -4794,13 +4796,11 @@ def test_func(self): return has_queue_write_access(self.request.user, ticket.queue) def post(self, request, *args, **kwargs): - logger.debug("IN UPDATE TICKET VIEW") - logger.debug(self.request.POST) + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") if request.POST.get('add_subscriber'): return UpdateTicketView.add_subscriber(request.POST.get('user'),kwargs['ticket_id']) elif request.POST.get('del_subscriber'): - logger.debug(kwargs['ticket_id']) - logger.debug(request.POST.get('user')) + logger.debug(f"Request to delete subscriber {request.POST.get('del_subscriber')}") return UpdateTicketView.del_subscriber(request.POST.get('user'), kwargs['ticket_id']) elif request.POST.get('add_tag'): return UpdateTicketView.add_tag(request.POST.get('tag').lower(), kwargs['ticket_id'], self.request.user) @@ -4813,7 +4813,7 @@ def post(self, request, *args, **kwargs): @staticmethod def add_subscriber(username, ticket_id): """ Add a subscriber to a ticket """ - logger.debug("IN ADD SUBSCRIBER") + logger.debug(f"Adding Subsciber {username} to Ticket ID: {ticket_id}") user = get_object_or_404(User, usersettings__preferred_username__iexact=username) ticket = get_object_or_404(Ticket, id=ticket_id) if TicketCC.objects.filter(ticket=ticket, user=user): @@ -4853,7 +4853,7 @@ def add_tag(tag, ticket_id, user): user=user) fup.save() else: - logger.debug("invalid tag - tag doesn't exist in tag manager") + logger.debug(f"invalid tag - tag {tag} doesn't exist in tag manager") return JsonResponse({'tag': tag, 'ticket': ticket.id, 'error': "Invalid Tag."}, status=401) else: return JsonResponse({'tag': tag, 'ticket': ticket.id, 'error': "Tag is too long. Max 50 characters."}, status=401) @@ -4909,8 +4909,7 @@ def test_func(self): def post(self, request, *args, **kwargs): - logger.debug("IN UPDATE Followup VIEW") - logger.debug(self.request.POST) + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") comment = request.POST['comment'] action = get_object_or_404(Action, id=self.kwargs['pk']) @@ -5092,7 +5091,7 @@ def form_valid(self, form): case.owner = self.request.user case.save() - ca = CaseAction(case=case, user=self.request.user, title=title, action_type=1) + ca = CaseAction(case=case, user=self.request.user, title=title, action_type=CaseAction.lookup('VinceTrack')) ca.save() return HttpResponseRedirect(reverse("vince:case", args=[case.id])+"#details") @@ -5311,7 +5310,7 @@ def get_context_data(self, **kwargs): context = super(VendorViewDetailView, self).get_context_data(**kwargs) vendor = get_object_or_404(VulnerableVendor, id=self.kwargs['pk']) if vendor.seen: - context['timeseen'] = CaseAction.objects.filter(case=vendor.case, vendor=vendor, action_type=7).first() + context['timeseen'] = CaseAction.objects.filter(case=vendor.case, vendor=vendor, action_type=CaseAction.lookup('Vendor Viewed')).first() vc_case = Case.objects.using('vincecomm').filter(vuid=vendor.case.vuid).first() #get users in group @@ -5355,12 +5354,12 @@ def get_context_data(self, **kwargs): if context['case'].team_owner: if (self.request.user.groups.filter(id=context['case'].team_owner.id).exists()): # present form - logger.debug("This user is a member of the current owner group") + logger.debug(f"This user {self.request.user} is a member of the current owner group.") context['form'] = True else: context['group_to_approve'] = context['case'].team_owner elif has_case_write_access(self.request.user, context['case']): - logger.debug("This case doesn't have an owner but this user has write access") + logger.debug(f"This case doesn't have an owner but this user {self.request.user} has write access") context['form'] = True else: context['group_to_approve'] = context['case'].team_owner @@ -5369,7 +5368,7 @@ def get_context_data(self, **kwargs): # Lead Suggested means that the new group must approve transfer group = Group.objects.filter(groupsettings__contact__vendor_name=cr.user_name).first() if (self.request.user.groups.filter(id=group.id).exists()): - logger.debug("This user is a member of the group proposed owner team") + logger.debug(f"This user {self.request.user} is a member of the group proposed owner team") # present form context['form'] = True else: @@ -5407,7 +5406,7 @@ def post(self, request, *args, **kwargs): _transfer_case(case, new_group) ca = CaseAction(case=case, title=f"Case ownership transferred to {new_group.name}", - user=self.request.user, action_type=1) + user=self.request.user, action_type=CaseAction.lookup('VinceTrack')) ca.save() #add this user to the case assignment so at least SOMEBODY is assigned @@ -5424,7 +5423,7 @@ def post(self, request, *args, **kwargs): for c in ca: if not(c.assigned.groups.filter(id=new_group.id).exists()): #if the assignee isn't in the group - remove them - act = CaseAction(case=case, title=f"Removing {c.assigned.usersettings.preferred_username} from case assignment due to team transfer", action_type=1, user=self.request.user) + act = CaseAction(case=case, title=f"Removing {c.assigned.usersettings.preferred_username} from case assignment due to team transfer", action_type=CaseAction.lookup('VinceTrack'), user=self.request.user) c.delete() act.save() @@ -5486,7 +5485,7 @@ def post(self, request, *args, **kwargs): ca = CaseAction(case=case, title=f"Case ownership transfer to {new_group.name} rejected.", comment=self.request.POST.get('reason'), - user=self.request.user, action_type=1) + user=self.request.user, action_type=CaseAction.lookup('VinceTrack')) ca.save() # get ticket @@ -5584,16 +5583,16 @@ def post(self, request, *args, **kwargs): new_group = Group.objects.filter(id=form.cleaned_data['team']).first() # does this group already have write access? if CasePermissions.objects.filter(group_write=True, group=new_group, case=case).exists(): - logger.debug("New group already has write access") + logger.debug(f"New group {new_group} already has write access to {case}") if has_case_write_access(self.request.user, case): - logger.debug("User has write access") + logger.debug(f"User {self.request.user} has write access to Case {case}") # just change owner case.team_owner=new_group case.owner = self.request.user case.save() #create log ca = CaseAction(case=case, title=f"Case ownership transferred to {new_group.name}", - user=self.request.user, action_type=1) + user=self.request.user, action_type=CaseAction.lookup('VinceTrack')) ca.save() _transfer_case(case, new_group) @@ -5652,16 +5651,16 @@ def post(self, request, *args, **kwargs): #this new group doesn't have write access #does this user belong in the proposed owner's group if self.request.user.groups.filter(id=new_group.id).exists(): - logger.debug(f"User already belongs in proposed group {new_group.name}") + logger.debug(f"User {self.request.user} already belongs in proposed group {new_group.name}") #is this person already in the current owner's group? if has_case_write_access(self.request.user, case): - logger.debug("User has write access so just do it") + logger.debug(f"User {self.request.user} has write access so fair to proceed") case.team_owner=new_group case.owner = self.request.user case.save() #create log ca = CaseAction(case=case, title=f"Case ownership transferred to {new_group.name}", - user=self.request.user, action_type=1) + user=self.request.user, action_type=CaseAction.lookup('VinceTrack')) ca.save() _transfer_case(case, new_group) @@ -5764,7 +5763,8 @@ def get_context_data(self, **kwargs): #Assume we have not vrf_url unless we get one from below methods context['vrf_url'] = None context['vincecomm_link'] = None - if hasattr(context['case'],'caserequest'): + + if hasattr(context['case'],'case_request') and context['case'].case_request: if hasattr(context['case'].case_request,'caserequest'): context['cr'] = context['case'].case_request.caserequest if hasattr(context['cr'],'vrf_id') and context['cr'].vrf_id: @@ -5772,7 +5772,7 @@ def get_context_data(self, **kwargs): vc_cr = VTCaseRequest.objects.filter(vrf_id=context['cr'].vrf_id).first() if vc_cr: context['vincecomm_link'] = reverse("vinny:cr_report", args=[vc_cr.id]) - elif hasattr(context['case'],'case_request'): + else: context['ticket'] = context['case'].case_request if hasattr(context['ticket'],'vrf_id') and context['ticket'].vrf_id: context['vrf_url'] = download_vrf(context['ticket'].vrf_id) @@ -5794,7 +5794,7 @@ def get_context_data(self, **kwargs): # need this for task reassignment context['assignable_users'] = User.objects.filter(is_active=True, groups__name='vince') context['assignable_usersjs'] = [{0: 'Unassigned'}] + [{obj.id:obj.usersettings.preferred_username} for obj in context['assignable_users']] - ca = Action.objects.select_related('caseaction').filter(caseaction__case=context['case'], caseaction__action_type__in=[0,1,9]) + ca = Action.objects.select_related('caseaction').filter(caseaction__case=context['case'], caseaction__action_type__in=CaseAction.ASSIGN_ACTIONS) ta = Action.objects.select_related('followup').filter(followup__ticket__case=context['case']) activity = ca | ta @@ -5824,7 +5824,6 @@ def get_context_data(self, **kwargs): context['vulsjs'] = [obj.as_dict() for obj in context['vuls']] context['vendors'] = VulnerableVendor.casevendors(context['case']).order_by('contact__vendor_name') context['vendorgroups'] = VulnerableVendor.casevendors(context['case']).exclude(from_group__isnull=True).distinct('from_group') - logger.debug(context['vendorgroups']) context['participants'] = CaseParticipant.objects.filter(case=context['case']).order_by('user_name') context['participantsjs'] = [obj.as_dict() for obj in context['participants']] @@ -5845,7 +5844,6 @@ def get_context_data(self, **kwargs): form = CaseCommunicationsFilterForm() form.fields['vendor'].choices = [ (u.id, u.contact.vendor_name) for u in context['vendors']] - logger.debug(form.fields['vendor'].choices) form.fields['participants'].choices = [ (u.id, u.participant.vinceprofile.vince_username) for u in vc_case_participants] context['form'] = form @@ -5936,14 +5934,14 @@ def post(self, request, *args, **kwargs): date = timezone.now(), user = self.request.user, comment = comment, - action_type=1) + action_type=CaseAction.lookup('VinceTrack')) action.save() messages.success( self.request, _("All statements have been successfully approved.")) if send_email: - logger.warning("This would send vendor approval emails") + logger.warning("This would send vendor approval emails. Currently disabled") #send_vendor_approval_emails(send_email, case) if change_made: @@ -5971,8 +5969,12 @@ def test_func(self): def get_context_data(self, **kwargs): context = super(RedirectVinny, self).get_context_data(**kwargs) next_url = self.request.GET.get('next') - context['action'] = next_url - logger.debug("redirecting to: " + next_url) + if is_safe_url(next_url,set(settings.ALLOWED_HOSTS),True): + context['action'] = next_url + logger.debug(f"redirecting to: {next_url}") + else: + context['action'] = reverse("vinny:dashboard") + logger.info(f"The next_url {next_url} is not valid, replacing the next_url field with default VinceComm Dashboard page") return context class VinnyTokens(LoginRequiredMixin, TokenMixin, UserPassesTestMixin, generic.TemplateView): @@ -5989,16 +5991,15 @@ class TokenLogin(GetUserMixin, generic.TemplateView): template_name = 'vince/index.html' def post(self, request, *args, **kwargs): - logger.debug("in tokenlogin") if (token_verify(self.request.POST['access_token'])): request.session['ACCESS_TOKEN'] = self.request.POST['access_token'] request.session['REFRESH_TOKEN'] = self.request.POST['refresh_token'] #request.session.save() groups = self.get_token_groups() - logger.debug(f"token has groups {groups}") + user = self.get_user() + logger.debug(f"Token login for has {user} who has groups {groups}") if groups: if settings.COGNITO_ADMIN_GROUP in groups: - user = self.get_user() request.session['timezone'] = user.timezone user = authenticate(self.request, username=user.email) if user: @@ -6115,7 +6116,6 @@ def test_func(self): return False def form_valid(self, form): - logger.debug("IN FORM AVALID") case = get_object_or_404(VulnerabilityCase, id=self.kwargs['pk']) if 'notify_id' in self.kwargs: notification = VendorNotificationContent.objects.filter(id=self.kwargs['notify_id']).first() @@ -6147,7 +6147,7 @@ def form_valid(self, form): user = self.request.user, date = timezone.now(), comment="", - action_type=1) + action_type=CaseAction.lookup('VinceTrack')) action.save() artifacts = list(map(int, self.request.POST.getlist('artifacts[]'))) @@ -6168,7 +6168,6 @@ def form_valid(self, form): vf.save() formvuls = list(map(int, self.request.POST.getlist('vuls[]'))) - logger.debug(formvuls) vuls = Vulnerability.casevuls(case) for vul in vuls: if vul.id in formvuls: @@ -6229,7 +6228,6 @@ def get(self, request, *args, **kwargs): for u in users: assignments.append(u.assigned.usersettings.vince_username) assignable_users = [ u.usersettings.preferred_username for u in User.objects.filter(is_active=True, groups__name='vince')] - logger.debug(assignable_users); return JsonResponse({'response': 'success', 'case_assigned_to': assignments, 'assignable_users': assignable_users}, status=200) def post(self, request, *args, **kwargs): @@ -6241,7 +6239,7 @@ def post(self, request, *args, **kwargs): if user: ca = CaseAction(case=case, title=f"User {user.usersettings.preferred_username} assigned to case", - user=self.request.user, action_type=1) + user=self.request.user, action_type=CaseAction.lookup('VinceTrack')) ca.save() #do this after the case action, so the assignee doesn't get 2 emails assignment = CaseAssignment(assigned=user, @@ -6253,8 +6251,7 @@ def post(self, request, *args, **kwargs): #is this user a part of one of the caseparticipants - otherwise add them contacts = user.groups.exclude(groupsettings__contact__isnull=True).values_list('groupsettings__contact__id', flat=True) cps = CaseParticipant.objects.filter(case=case).filter(Q(user_name=user.email)|Q(contact__in=contacts)) - logger.debug("CHECK CASE PARTICIPANTS") - logger.debug(cps) + logger.debug(f"Checking Case participants for {case} which is {cps}") if not cps: #this user isn't a part of any of the CaseParticipants, so add them now cp, created = CaseParticipant.objects.update_or_create(case=case, @@ -6274,7 +6271,7 @@ def post(self, request, *args, **kwargs): if user: ca = CaseAction(case=case, title=f"User {user.usersettings.preferred_username} removed from case assignment", - user=self.request.user, action_type=1) + user=self.request.user, action_type=CaseAction.lookup('VinceTrack')) ca.save() assignment = CaseAssignment.objects.filter(assigned=user, case=case) if assignment: @@ -6302,7 +6299,7 @@ def test_func(self): return False def post(self, request, *args, **kwargs): - logger.debug(request.POST) + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") participant = get_object_or_404(CaseParticipant, id=self.kwargs['pk']) participant.coordinator = self.request.POST.get('coordinator') cm = CaseMember.objects.filter(vince_id = participant.id).first() @@ -6335,14 +6332,13 @@ def get_context_data(self, **kwargs): return context def post(self, request, *args, **kwargs): - logger.debug(request.POST) + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") case = get_object_or_404(VulnerabilityCase, id=request.POST['case_id']) users = request.POST.getlist('users[]') for user in users: if user.startswith('Group:'): groupname = user[7:] - logger.debug(groupname) group = ContactGroup.objects.filter(name=groupname).first() members = GroupMember.objects.filter(group__name=groupname) for member in members: @@ -6353,7 +6349,7 @@ def post(self, request, *args, **kwargs): user_name=member.contact.vendor_name, defaults = {'added_by': self.request.user}) ca = CaseAction(case=case, title=f"Added Group {groupname} to Case", - user=self.request.user, action_type=1) + user=self.request.user, action_type=CaseAction.lookup('VinceTrack')) ca.save() groupdups = GroupMember.objects.filter(group=group).values_list('group_member', flat=True) duplicates = GroupDuplicate.objects.filter(group__in=groupdups).values_list('group', flat=True) @@ -6369,7 +6365,7 @@ def post(self, request, *args, **kwargs): user_name=member.contact.vendor_name, defaults = {'added_by': self.request.user}) ca = CaseAction(case=case, title=f"Added Group {subgroup.name} to Case", - user=self.request.user, action_type=1) + user=self.request.user, action_type=CaseAction.lookup('VinceTrack')) ca.save() @@ -6382,7 +6378,7 @@ def post(self, request, *args, **kwargs): defaults = { 'added_by':self.request.user}) ca = CaseAction(case=case, title=f"Added Participant {vc_user.username} to Case", - user=self.request.user, action_type=1) + user=self.request.user, action_type=CaseAction.lookup('VinceTrack')) ca.save() else: #search contacts @@ -6395,7 +6391,7 @@ def post(self, request, *args, **kwargs): 'group':True, 'added_by':self.request.user}) ca = CaseAction(case=case, title=f"Added Participant Group {user} to Case", - user=self.request.user, action_type=1) + user=self.request.user, action_type=CaseAction.lookup('VinceTrack')) ca.save() else: # this is a new user we are inviting @@ -6404,7 +6400,7 @@ def post(self, request, *args, **kwargs): defaults= { 'added_by':self.request.user}) ca = CaseAction(case=case, title=f"Invited New Participant {user} to Case", - user=self.request.user, action_type=1) + user=self.request.user, action_type=CaseAction.lookup('VinceTrack')) ca.save() @@ -6438,7 +6434,7 @@ def test_func(self): def post(self, request, *args, **kwargs): vendor = get_object_or_404(VulnerableVendor, id=self.kwargs['pk']) - logger.debug(self.request.POST) + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") new_date = self.request.POST.get('new_date') if new_date: vendor.contact_date = new_date @@ -6457,13 +6453,12 @@ def test_func(self): return False def post(self, request, *args, **kwargs): - logger.debug(request.POST) + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") vendor = get_object_or_404(VulnerableVendor, id=self.kwargs['pk']) context = {} context['vendor'] = vendor context['new_date'] = self.request.POST.get("new_date") first_notify_date = VendorNotification.objects.filter(vendor=vendor).order_by('notify_date') - logger.debug(first_notify_date) if first_notify_date: context['first_notify_date'] = first_notify_date[0] @@ -6513,9 +6508,7 @@ def add_vendor_to_case(case, contact, user, group=None): old_vendor.save() # is this vendor tagged? if contact.contacttag_set.count() > 0: - logger.debug(old_vendor.tagged) if old_vendor.tagged: - logger.debug("JUST SETTING THIS TICKET TO OPEN!") # this vendor was already tagged, just repopen it old_vendor.tagged.status=Ticket.OPEN_STATUS old_vendor.tagged.save() @@ -6610,7 +6603,7 @@ def test_func(self): return False def post(self, request, *args, **kwargs): - logger.debug(request.POST) + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") case = get_object_or_404(VulnerabilityCase, id=request.POST['case_id']) vendors = request.POST.getlist('vendors[]') error = False @@ -6619,7 +6612,6 @@ def post(self, request, *args, **kwargs): for vendor in vendors: if vendor.startswith('Group:'): groupname = vendor[7:] - logger.debug(groupname) group = ContactGroup.objects.filter(name=groupname).first() members = GroupMember.objects.filter(group__name=groupname) for member in members: @@ -6649,7 +6641,7 @@ def post(self, request, *args, **kwargs): ca = CaseAction(case=case, title=f"Added {len(contacts_added)} Vendors to Case", comment = f"{contactsstr}", user=self.request.user, - action_type=1) + action_type=CaseAction.lookup('VinceTrack')) ca.save() if error: return JsonResponse({'status':'false', 'message': f'Could not find matching vendor name {error}.'}, status=400) @@ -6683,8 +6675,6 @@ def form_invalid(self, form): return super().form_invalid(form) def form_valid(self, form): - logger.debug("IN VENDOR VUL STMT") - logger.debug(self.request.POST) vendor = get_object_or_404(VulnerableVendor, id=self.kwargs['vendor_id']) vul = get_object_or_404(Vulnerability, id=self.kwargs['pk']) status = VendorStatus.objects.filter(vul=vul, vendor=vendor).first() @@ -6723,8 +6713,6 @@ def form_valid(self, form): def get_context_data(self, **kwargs): context = super(VendorVulStatement, self).get_context_data(**kwargs) - logger.debug("IN HERE") - logger.debug(self.kwargs) vul = get_object_or_404(Vulnerability, id=self.kwargs['pk']) status = VendorStatus.objects.filter(vul=vul, vendor=self.kwargs['vendor_id']).first() if status: @@ -6740,7 +6728,6 @@ def get_context_data(self, **kwargs): def update_status(vendor, request): - logger.debug(request.POST) affected = request.POST.getlist('affected') unknown = request.POST.getlist('unknown') unaffected = request.POST.getlist('unaffected') @@ -6748,14 +6735,12 @@ def update_status(vendor, request): vcuser = User.objects.using('vincecomm').filter(username=request.user.username).first() if affected: for vul in affected: - logger.debug(int(vul)) vul_obj = Vulnerability.objects.filter(id=int(vul)).first() status = VendorStatus.objects.update_or_create(vendor=vendor, vul=vul_obj, defaults={'status':1, 'user_approved':request.user, 'approved':True, 'user':request.user.username}) cv = CaseVulnerability.objects.filter(vince_id=int(vul)).first() member = get_casemember_from_vc(vendor, vul_obj.case) - logger.debug(cv) if member: status = CaseMemberStatus.objects.update_or_create(member=member, vulnerability=cv, defaults={'status':1, 'user':vcuser}) @@ -6800,7 +6785,6 @@ def form_invalid(self, form): return super().form_invalid(form) def form_valid(self, form): - logger.debug(self.request.POST) vendor = get_object_or_404(VulnerableVendor, id=self.kwargs['pk']) update_status(vendor, self.request) change = True @@ -7022,7 +7006,7 @@ def post(self, request, *args, **kwargs): self.case = cp.case.id ca = CaseAction(case=cp.case, title="Removed Participant from Case", comment="Participant %s removed from case" % cp.user_name, - user=self.request.user, action_type=1) + user=self.request.user, action_type=CaseAction.lookup('VinceTrack')) ca.save() remove_participant_vinny_case(cp.case, cp) cp.delete() @@ -7044,12 +7028,11 @@ def test_func(self): def get(self, request, *args, **kwargs): cp = get_object_or_404(CaseParticipant, id=self.kwargs['cp']) - logger.debug("HEREEEEEEEE") self.case = cp.case.id ca = CaseAction(case=cp.case, title="Removed Participant from Case", comment="Participant %s removed from case" % cp.user_name, - user=self.request.user, action_type=1) + user=self.request.user, action_type=CaseAction.lookup('VinceTrack')) ca.save() remove_participant_vinny_case(cp.case, cp) @@ -7072,7 +7055,7 @@ def test_func(self): return False def post(self, request, *args, **kwargs): - logger.debug(self.request.POST) + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") case = get_object_or_404(CaseParticipant, id=self.kwargs['pk']) form = NotificationForm(self.request.POST) templates = EmailTemplate.objects.filter(body_only=True) @@ -7081,8 +7064,8 @@ def post(self, request, *args, **kwargs): if form.is_valid(): try: form.save(cp=case) - except: - logger.debug(traceback.format_exc()) + except Exception as e: + logger.debug(f"Error when saving form om {self.__class__.__name__} with error {e}") messages.error( request, _(f"Invalid Email address: SMTP Recipients Refused {case.user_name}")) @@ -7150,7 +7133,7 @@ def post(self, request, *args, **kwargs): remove_vendor_vinny_case(case, vendor.contact, self.request.user) ca = CaseAction(case=case, title=f"All vendors removed from case", user=self.request.user, - action_type=1) + action_type=CaseAction.lookup('VinceTrack')) ca.save() messages.success( self.request, @@ -7190,7 +7173,7 @@ def post(self, request, *args, **kwargs): ca = CaseAction(case=case, title=f"Removed Vendor {vendor_name} from Case", user=self.request.user, - action_type=1) + action_type=CaseAction.lookup('VinceTrack')) ca.save() remove_vendor_vinny_case(case, vendor.contact, self.request.user) #messages.success( @@ -7235,10 +7218,9 @@ def test_func(self): return False def post(self, request, *args, **kwargs): - logger.debug(self.request.POST) + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") case = get_object_or_404(VulnerabilityCase, id=self.kwargs['pk']) vendors = self.request.POST.getlist('vendors[]') - logger.debug(vendors) vendors = VulnerableVendor.objects.filter(id__in=vendors).order_by('vendor') initial = {'subject': f"{case.vu_vuid}: New Vulnerability Report"} if case.template: @@ -7266,18 +7248,14 @@ def test_func(self): return False def post(self, request, *args, **kwargs): - logger.debug(self.request.POST) - logger.debug("IN EDITVENDORCASELIST") + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") case = get_object_or_404(VulnerabilityCase, id=self.kwargs['pk']) form = VendorNotificationForm(self.request.POST) templates = EmailTemplate.objects.filter(body_only=True) form.fields['email_template'].choices = [('', '--------')] + [(q.id, q.template_name) for q in templates] if form.is_valid(): - logger.debug("form is valid") vendors = self.request.POST.get('vendors') - logger.debug(vendors) vendors = vendors.split(',') - logger.debug(vendors) context = {'case': case.vuid, 'user': self.request.user.get_username() } email = form.save(user=self.request.user) @@ -7306,7 +7284,7 @@ def post(self, request, *args, **kwargs): comment = "Notified the following vendors of the case: " + "\n ".join(contact_names) ca = CaseAction(case=case, title=f"Notified Vendors of Case", user=self.request.user, - action_type=1, comment=comment) + action_type=CaseAction.lookup('VinceTrack'), comment=comment) ca.save() return JsonResponse({'response': 'success'}, status=200) @@ -7378,7 +7356,7 @@ def post(self, request, *args, **kwargs): ca = CaseAction(case=artifact.get_related_case(), user=self.request.user, title=f"deleted artifact {artifact.title}", - action_type=1) + action_type=CaseAction.lookup('VinceTrack')) ca.save() if attachment: if attachment.public: @@ -7440,7 +7418,7 @@ def get_context_data(self, **kwargs): return context def post(self, request, *args, **kwargs): - logger.debug(self.request.POST) + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") artifact = get_object_or_404(Artifact, id=self.kwargs['pk']) case = artifact.get_related_case() return make_artifact_public(request, artifact, case) @@ -7468,7 +7446,7 @@ def test_func(self): return False def post(self, request, *args, **kwargs): - logger.debug(self.request.POST) + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") artifact = get_object_or_404(CaseArtifact, id=self.kwargs['pk']) case = artifact.case return make_artifact_public(request, artifact, case) @@ -7517,7 +7495,7 @@ def make_artifact_public(request, artifact, case): comment="Removed artifact", user = request.user, artifact=artifact, - action_type=1) + action_type=CaseAction.lookup('VinceTrack')) ca.save() if len(vcattach) == 0: attachment.public = False @@ -7543,7 +7521,6 @@ def make_artifact_public(request, artifact, case): bucket.copy(copy_source, settings.AWS_PRIVATE_MEDIA_LOCATION + "/" + str(attachment.uuid)) except ClientError as e: error_code = e.response['Error']['Code'] - logger.debug(error_code) if error_code == "InvalidRequest": #This file already exists in VinceComm. just ignore pass @@ -7585,7 +7562,7 @@ def make_artifact_public(request, artifact, case): comment=f"{request.user.usersettings.preferred_username} added artifact to VINCEComm Case", user = request.user, artifact=artifact, - action_type=1) + action_type=CaseAction.lookup('VinceTrack')) ca.save() attachment.public = True @@ -7695,10 +7672,9 @@ def form_valid(self, form): comment=title, user = self.request.user, artifact=artifact, - action_type=1) + action_type=CaseAction.lookup('VinceTrack')) ca.save() artifacts = get_all_artifacts(case.case) - logger.debug(artifacts) artifactsjs = [ obj.as_dict() for obj in artifacts ] return JsonResponse({'success': True, 'artifacts': artifactsjs}, status=200) @@ -7752,7 +7728,7 @@ def post(self, request, *args, **kwargs): if casedependency.case != casedependency.depends_on: casedependency.save() ca = CaseAction(case=case, title="Added Dependency", - user=self.request.user, action_type=1) + user=self.request.user, action_type=CaseAction.lookup('VinceTrack')) ca.save() return HttpResponseRedirect(reverse('vince:case', args=[case.id])) @@ -7805,7 +7781,7 @@ def post(self, request, *args, **kwargs): dep.delete() case= VulnerabilityCase.objects.filter(id = self.kwargs['case_id']).first() ca = CaseAction(case=case, title="Removed Dependency", - user=self.request.user, action_type=1) + user=self.request.user, action_type=CaseAction.lookup('VinceTrack')) ca.save() return HttpResponseRedirect(reverse('vince:case', args=[self.kwargs['case_id']])) @@ -7814,7 +7790,6 @@ def get_context_data(self, **kwargs): dep = get_object_or_404(CaseDependency, case_id=self.kwargs['case_id'], id=self.kwargs['dep_id']) context['dependency'] = dep context['case'] = VulnerabilityCase.objects.filter(id = self.kwargs['case_id']).first() - logger.debug("return context") return context @@ -7868,8 +7843,8 @@ def get_context_data(self, **kwargs): return context def post(self, request, *args, **kwargs): + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") req = get_object_or_404(ContactAssociation, id=self.kwargs['pk']) - logger.debug(self.request.POST) ticket=req.ticket fup = FollowUp(title="Contact association failed. Restarting contact association process.", user = self.request.user, @@ -7903,8 +7878,8 @@ def get_context_data(self, **kwargs): return context def post(self, request, *args, **kwargs): + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") req = get_object_or_404(ContactAssociation, id=self.kwargs['pk']) - logger.debug(self.request.POST) ticket = req.ticket if self.request.POST.get('remove'): #this user couldn't be verified @@ -8087,7 +8062,7 @@ def get_context_data(self, **kwargs): return context def post(self,request,*args, **kwargs): - logger.debug(self.request.POST) + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") contact = get_object_or_404(Contact, id=self.kwargs['pk']) sender = User.objects.using('vincecomm').filter(email=self.request.user.email).first() #get emails @@ -8143,7 +8118,7 @@ def get_context_data(self, **kwargs): return context def post(self, request, *args, **kwargs): - logger.debug(f"InitContactForm Post: {self.request.POST}") + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") ticket = get_object_or_404(Ticket, id=self.kwargs['pk']) #search for Contact: if self.request.POST.get('msg'): @@ -8249,7 +8224,6 @@ def get_context_data(self, **kwargs): if tmpl: team_sig = get_team_sig(self.request.user) initial['email_body'] = tmpl.plain_text.replace('[team_signature]', team_sig) - logger.debug(team_sig) initial['subject'] = tmpl.subject if context.get('ca'): context['form'] = InitContactForm(initial=initial, instance=context['ca']) @@ -8258,7 +8232,7 @@ def get_context_data(self, **kwargs): return context def post(self, request, *args, **kwargs): - logger.debug(f"InitContactForm Post: {self.request.POST}") + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") if self.request.POST.get('ca'): ca = get_object_or_404(ContactAssociation, id=self.request.POST.get('ca')) @@ -8293,7 +8267,6 @@ def post(self, request, *args, **kwargs): #make sure all emails are legit emails = self.request.POST.getlist('email') - logger.debug(emails) for email in emails: email = email.strip() if EmailContact.objects.filter(contact=contact, @@ -8312,7 +8285,7 @@ def post(self, request, *args, **kwargs): return self.form_valid(form) else: - logger.debug(form.errors) + logger.debug(f"Error in {self.__class__.__name__} form processing : {form.errors}") return self.form_invalid(form) def form_invalid(self, form): @@ -8449,11 +8422,9 @@ def get(self, request, *args, **kwargs): if facet == "All": vince_results = VinceProfile.objects.using('vincecomm').filter(Q(user__first_name__icontains=search_term) | Q(user__last_name__icontains=search_term) | Q(preferred_username__icontains=search_term) | Q(user__email__icontains=search_term)) user_contacts = list(vince_results.values_list('user__email', flat=True)) - logger.debug(user_contacts) email_contacts = EmailContact.objects.filter(contact__vendor_type="Contact", email__in=user_contacts).values_list('contact__id', flat=True) email_results = EmailContact.objects.filter(Q(email__icontains=search_term) | Q(name__icontains=search_term)).exclude(contact__id__in=email_contacts).values_list('contact', flat=True) emails = Contact.objects.filter(id__in=email_results) - logger.debug(email_contacts) contact_results = Contact.objects.search(search_query).exclude(id__in=email_contacts) ctags = ContactTag.objects.filter(tag__in=search_tags).values_list('contact__id', flat=True) if ctags: @@ -8547,7 +8518,6 @@ def post(self, request, *args, **kwargs): return self.form_invalid(form) def form_valid(self, form): - logger.debug("VALID FORM") if group_name_exists(self.request.POST['name']): return render(self.request, 'vince/newgroup.html', {'form': form, @@ -8585,11 +8555,7 @@ def form_valid(self, form): dupgroup.save() added=0 - logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") - logger.debug(self.request.POST['group_select[]']) - for group in contactlist: - logger.debug(group) contact = Contact.objects.filter(vendor_name=group).first() if contact: newmember=GroupMember(group=newgroup, @@ -8602,9 +8568,9 @@ def form_valid(self, form): text = "Added %s to group %s" % (contact.vendor_name, newgroup.name) _add_activity(self.request.user, 4, contact, text) else: - logger.debug("NOT ADDED") + logger.debug("New member not added already has id {newmember}") else: - logger.debug("this is a group") + logger.debug(f"This request is for a group {group}") contact = ContactGroup.objects.filter(name=group).first() if contact: groupdup = GroupDuplicate.objects.filter(group=contact).first() @@ -8618,7 +8584,7 @@ def form_valid(self, form): _add_group_activity(self.request.user, 2, groupdup.group, text) added=added+1 - logger.debug("added %d members" % added) + logger.debug(f"Added new members {added} members") update_srmail_file() messages.warning( @@ -8639,7 +8605,7 @@ def test_func(self): return is_in_group_vincetrack(self.request.user) and get_contact_write_perms(self.request.user) def post(self, request, *args, **kwargs): - logger.debug(self.request.POST) + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") contact = get_object_or_404(Contact, id=self.kwargs.get('pk')) group = get_object_or_404(ContactGroup, name=self.request.POST.get('group')) if self.request.POST.get('rm'): @@ -8750,9 +8716,7 @@ def test_func(self): return is_in_group_vincetrack(self.request.user) and get_contact_write_perms(self.request.user) def form_invalid(self, form): - logger.debug("INVALID FORM") logger.debug(f"{self.__class__.__name__} errors: {form.errors}") - return render(self.request, 'vince/newcontact.html', {'form': form, }) @@ -8768,7 +8732,6 @@ def post(self, request, *args, **kwargs): return self.form_invalid(form) def form_valid(self, form): - logger.debug("VALID FORM") vendor_name = self.request.POST['vendor_name'].strip() group = False vendor_type = self.request.POST['vtype'] @@ -8919,8 +8882,6 @@ def get(self, request, *args, **kwargs): def form_invalid(self, form): logger.debug(f"{self.__class__.__name__} errors: {form.errors}") - logger.debug("INVALID FORM!!!") - group = ContactGroup.objects.filter(id=self.kwargs['pk']) members = GroupMember.objects.filter(group=group[0]).exclude(contact__isnull=True).values_list('contact', flat=True) # the groups in the group - give the list of groupduplicates @@ -8949,7 +8910,6 @@ def post(self, request, *args, **kwargs): return self.form_invalid(form) def form_valid(self, form): - logger.debug("VALID FORM") current_group = ContactGroup.objects.filter(id=self.kwargs['pk']).first() if current_group.version != int(self.request.POST['version']): error_str = "Someone beat you to editing this group. View the most recent details and retry editing this group." @@ -8995,7 +8955,6 @@ def form_valid(self, form): duplicates = GroupDuplicate.objects.filter(group__in=groupdups).values_list('group', flat=True) groupmembers = ContactGroup.objects.filter(id__in=duplicates).values_list('name', flat=True) allmembers = list(contacts)+list(groupmembers) - logger.debug(allmembers) added=0 contactlist = self.request.POST.getlist('group_select[]') for group in contactlist: @@ -9013,9 +8972,9 @@ def form_valid(self, form): text = "added %s to %s group" % (contact.vendor_name, current_group.name) _add_activity(self.request.user, 4, contact, text) else: - logger.debug("NOT ADDED") + logger.debug(f"Not added {newmember} to {contact}") else: - logger.debug("this is a group") + logger.debug(f"This {newmember} is a group Contact") contact = ContactGroup.objects.filter(name=group).first() if contact: groupdup = GroupDuplicate.objects.filter(group=contact).first() @@ -9031,7 +8990,7 @@ def form_valid(self, form): text = "Added %s to group" % group _add_group_activity(self.request.user, 2, current_group, text) - logger.debug("added %d members" % added) + logger.debug(f"Added new members {new_member} to Group") removed = 0 for group in allmembers: if group not in contactlist: @@ -9069,7 +9028,7 @@ def form_valid(self, form): current_group.status="Active" current_group.save() - logger.debug("Removed %d members" % removed) + logger.debug(f"Removed {removed} members from Group") current_group.version = current_group.version + 1 current_group.save() return redirect("vince:group", current_group.id) @@ -9142,7 +9101,6 @@ def post(self, request, *args, **kwargs): error = False if emaildiff: groupadmin = list(GroupAdmin.objects.filter(contact=contact).values_list('email__email', flat=True)) - logger.debug(groupadmin) email_list = [] vc_emails = VinceCommEmail.objects.filter(contact=vccontact) for email in vc_emails: @@ -9165,12 +9123,10 @@ def post(self, request, *args, **kwargs): error = True if groupadmin: - logger.debug(groupadmin) for ga in groupadmin: logger.debug(f"looking for {ga}") ec = EmailContact.objects.filter(contact=contact, email=ga).first() if ec: - logger.debug("adding new groupadmin") new_group_admin = GroupAdmin.objects.update_or_create(contact=contact, email=ec) else: # remove this email's groupadmin privs @@ -9249,7 +9205,6 @@ def post(self, request, *args, **kwargs): vcphones = list(VinceCommPgP.objects.filter(contact=vccontact).values_list(*fields)) phones = list(ContactPgP.objects.filter(contact=contact).values_list(*fields)) pgpdiff = diff(phones, vcphones) - logger.debug(pgpdiff) if pgpdiff: vcpgp = VinceCommPgP.objects.filter(contact=vccontact) pgp_list = [] @@ -9260,7 +9215,6 @@ def post(self, request, *args, **kwargs): comment = comment[0] else: comment = "" - logger.debug(comment) pgp_list.append(ContactPgP(contact=contact, pgp_key_id=pgp.pgp_key_id, pgp_protocol=pgp.pgp_protocol, @@ -9271,13 +9225,12 @@ def post(self, request, *args, **kwargs): revoked=pgp.revoked, pgp_email=pgp.pgp_email, user=self.request.user)) - logger.debug(pgp_list) try: with transaction.atomic(): ContactPgP.objects.filter(contact=contact.id).delete() ContactPgP.objects.bulk_create(pgp_list) - except: - logger.debug(traceback.format_exc()) + except Exception as e: + logger.debug(f"Exception in PGP information found {e}") messages.error(self.request, _(f"Error saving PGP information")) error = True @@ -9552,7 +9505,7 @@ def get_context_data(self, **kwargs): return context def post(self, request, *args, **kwargs): - logger.debug(self.request.POST) + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") contact = get_object_or_404(Contact, id=self.kwargs['pk']) vinny_contact = VinceCommContact.objects.filter(vendor_id=self.kwargs['pk']).first() email_type = self.request.POST.get('email_type') @@ -9726,7 +9679,6 @@ def test_func(self): def get_context_data(self, **kwargs): context = super(ContactDetailView, self).get_context_data(**kwargs) context['contactpage']=1 - logger.debug("IN CONTACT PAGE") context['groups'] = [group.group.name for group in GroupMember.objects.filter(contact=self.kwargs['pk'])] context['activity_list'] = Activity.objects.filter(contact=self.kwargs['pk']).order_by('-action_ts') @@ -9743,12 +9695,10 @@ def get_context_data(self, **kwargs): context['groupadmins'] = [admin.email.email for admin in GroupAdmin.objects.filter(contact=self.kwargs['pk'])] vc_contact = VinceCommContact.objects.using('vincecomm').filter(vendor_id=self.kwargs['pk']).first() if vc_contact: - logger.debug("VINCE COMM CONTACT") gc = GroupContact.objects.using('vincecomm').filter(contact=vc_contact).first() context['vc_contact'] = vc_contact if vc_contact.vendor_type != 'Contact' and gc: context['participants'] = CaseMember.objects.filter(group=gc.group).order_by('-case__modified')[:20] - logger.debug(context['participants']) context['vince_users'] = list(User.objects.using('vincecomm').filter(groups=gc.group).values_list('username', flat=True)) elif vc_contact.vendor_type in ['Contact', 'User']: #this is prob a contact @@ -9786,7 +9736,7 @@ def post(self, request, *args, **kwargs): _add_groupadmin(cga.email, contact) _add_activity(self.request.user, 3, contact, f"added {user} as VinceComm group administrator") else: - logger.warning("NO EmailContact with this username") + logger.warning(f"No EmailContact with this username {user}") return JsonResponse({'error': "This user is not listed in the Emails above. Please add user's email to this contact."}, status=500) elif request.POST.get('add_tag'): user_groups = self.request.user.groups.exclude(groupsettings__contact__isnull=True) @@ -9798,7 +9748,7 @@ def post(self, request, *args, **kwargs): if created: _add_activity(self.request.user, 6, contact, f" tagged contact with {new_tag}") else: - logger.debug("invalid tag - tag doesn't exist in tag manager") + logger.debug(f"invalid tag - tag {new_tag} doesn't exist in tag manager") return JsonResponse({'tag': new_tag, 'contact': contact.id, 'error': "Invalid Tag."}, status=401) else: return JsonResponse({'tag': new_tag, 'error': "Tag is too long. Max 50 characters."}, status=401) @@ -9925,7 +9875,6 @@ def post(self, request, *args, **kwargs): return self.form_invalid(form) def form_valid(self, form): - logger.debug("VALID FORM") contact = Contact.objects.filter(id=self.kwargs['pk']).first() vinny_contact = VinceCommContact.objects.filter(vendor_id=self.kwargs['pk']).first() @@ -9943,12 +9892,7 @@ def form_valid(self, form): pgpformset = self.PgPFormSet(self.request.POST, prefix='pgp', queryset=pgp, instance=contact) #emailformset = self.EmailFormSet(self.request.POST, prefix='email', queryset=email, instance=contact) - logger.debug(vinny_contact) - - logger.debug(contact.version) - logger.debug(self.request.POST['version']) - - if contact.version != int(self.request.POST['version']): + if self.request.POST.get('version') and self.request.POST.get('version').isdigit() and contact.version != int(self.request.POST['version']): error_str = "Someone beat you to editing this contact. View the most recent details and retry editing this contact." forms = {'form': self.form_class(initial=Contact.objects.filter(id=self.kwargs['pk']).values()[0]), 'collision': error_str, 'postal_formset': self.PostalFormSet(prefix='postal', queryset=postal), @@ -10027,7 +9971,7 @@ def form_valid(self, form): some_changes=True else: - logger.debug(f.errors) + logger.debug(f"Error in form submission for {self.__class__.__name__} error: {f.errors}") try: with transaction.atomic(): @@ -10071,7 +10015,7 @@ def form_valid(self, form): some_changes=True else: - logger.debug(f.errors) + logger.debug(f"Error in form submission for {self.__class__.__name__} error: {f.errors}") try: with transaction.atomic(): @@ -10108,7 +10052,7 @@ def form_valid(self, form): description=cd['description'])) some_changes=True else: - logger.debug(f.errors) + logger.debug(f"Error in form submission for {self.__class__.__name__} error: {f.errors}") try: with transaction.atomic(): @@ -10256,7 +10200,6 @@ def form_valid(self, form): messages.error(self.request, f"There was an error in parsing the PGP Key") continue - logger.debug(cd) _add_activity(self.request.user, 3, contact, f"added pgp key {cd['pgp_key_id']}") instance = f.save() # set values from the extraction @@ -10330,25 +10273,20 @@ def form_valid(self, form): srmail_peer = srmail_peer.translate({ord(i):None for i in '"@+.,;'}) contact.srmail_peer = srmail_peer - if contact.countrycode != self.request.POST['countrycode']: - _add_activity(self.request.user, 3, contact, "modified country code") - contact.countrycode = self.request.POST['countrycode'] - some_changes=True - if contact.location != self.request.POST['location']: - logger.debug(contact.location) - logger.debug(self.request.POST['location']) - _add_activity(self.request.user, 3, contact, f"changed location from {contact.location} to {self.request.POST['location']}") - contact.location = self.request.POST['location'] - some_changes=True - if contact.comment != self.request.POST['comment']: - if not((contact.comment == None) and (self.request.POST['comment'] == '')): - if self.request.POST['comment'] == "": + for pvar in ['countrycode','location']: + if getattr(contact,pvar) != self.request.POST.get(pvar,getattr(contact,pvar)): + _add_activity(self.request.user, 3, contact, f"modified {pvar}") + some_changes=True + newcomment = self.request.POST.get('comment',"") + if contact.comment != new_comment: + if not((contact.comment == None) and (newcomment == '')): + if newcomment == "": _add_activity(self.request.user, 3, contact, f"removed comment: {contact.comment}") elif contact.comment: - _add_activity(self.request.user, 3, contact, f"modified comment from {contact.comment} to {self.request.POST['comment']}") + _add_activity(self.request.user, 3, contact, f"modified comment from {contact.comment} to {newcomment}") else: - _add_activity(self.request.user, 3, contact, f"added comment: {self.request.POST['comment']}") - contact.comment = self.request.POST['comment'] + _add_activity(self.request.user, 3, contact, f"added comment: {newcomment}") + contact.comment = newcomment some_changes=True if pgp_updates: #or email_changes @@ -10470,7 +10408,6 @@ def post(self, request, *args, **kwargs): logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") res = EmailTemplate.objects.filter(locale='en', body_only=True) owner_list = self.request.POST.getlist('owner') - logger.debug(owner_list) if owner_list: res = res.filter(user__usersettings__preferred_username__in=owner_list) @@ -10492,7 +10429,6 @@ def post(self, request, *args, **kwargs): logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") res = CaseTemplate.objects.all() owner_list = self.request.POST.getlist('owner') - logger.debug(owner_list) if owner_list: res = res.filter(user__usersettings__preferred_username__in=owner_list) @@ -10523,7 +10459,6 @@ def form_valid(self, form): task = task.save() tasks = CaseTask.objects.filter(template = task.template) tasksjs = [ obj.as_dict() for obj in tasks] - logger.debug(tasksjs) return JsonResponse({'success': True, 'tasks': tasksjs}, status=200) def get_context_data(self, **kwargs): @@ -10558,7 +10493,6 @@ def form_valid(self, form): task = form.save() tasks = CaseTask.objects.filter(template = self.kwargs['pk']) tasksjs = [ obj.as_dict() for obj in tasks] - logger.debug(tasksjs) return JsonResponse({'success': True, 'tasks': tasksjs}, status=200) def get_context_data(self, **kwargs): @@ -10651,8 +10585,6 @@ def form_invalid(self, form): return super().form_invalid(form) def form_valid(self, form): - logger.debug("IN FORM AVALID") - casetemplate = form.save() casetemplate.user = self.request.user casetemplate.save() @@ -10828,8 +10760,6 @@ def form_invalid(self, form): return super().form_invalid(form) def form_valid(self, form): - logger.debug("IN FORM AVALID") - casetemplate = form.save() casetemplate.user = self.request.user casetemplate.save() @@ -10926,7 +10856,7 @@ def test_func(self): return False def post(self, request, *args, **kwargs): - logger.debug("IN SHARE ARTIFACT") + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") artifact = get_object_or_404(Artifact, id=self.kwargs['pk']) if artifact.get_related_attachment(): vtattach = artifact.get_related_attachment() @@ -10942,7 +10872,7 @@ def post(self, request, *args, **kwargs): bucket.copy(copy_source, settings.AWS_PRIVATE_MEDIA_LOCATION + "/" + str(vtattach.uuid)) except ClientError as e: error_code = e.response['Error']['Code'] - logger.debug(error_code) + logger.debug(f"ClientError in S3 bucket copy {error_code}") if error_code == "InvalidRequest": #This file already exists most likely in VinceComm. just ignore pass @@ -10995,7 +10925,7 @@ def post(self, request, *args, **kwargs): attach.save(using='vincecomm') - logger.debug(f"uuid is {att.uuid}") + logger.debug(f"File storage uuid is {att.uuid}") vf = VinceFile(user=self.request.user, case = case, @@ -11084,7 +11014,6 @@ def get_context_data(self, **kwargs): kwargs['references'] = self.references kwargs['preview'] = self.preview kwargs['case'] = self.vul_note.case - logger.debug(f"test {self.vul_note.case.vulnerablevendor_set.all()}") kwargs['pubnote'] = VUReport.objects.filter(idnumber=self.vul_note.case.vuid).first() return super(VulNoteRevisionPreview, self).get_context_data(**kwargs) @@ -11210,7 +11139,6 @@ def test_func(self): return False def form_valid(self, form): - logger.debug("IN FORM VALID !!!") vul = get_object_or_404(Vulnerability, id=self.kwargs['pk']) cd = form.cleaned_data @@ -11324,7 +11252,7 @@ def get(self, request, *args, **kwargs): date=timezone.now(), comment=f"{request.user.usersettings.preferred_username} changed exploit shared status", user = request.user, - action_type=1) + action_type=CaseAction.lookup('VinceTrack')) ca.save() @@ -11355,14 +11283,12 @@ def test_func(self): return False def form_valid(self, form): - logger.debug("IN FORM VALID !!!") vul = get_object_or_404(Vulnerability, id=self.kwargs['pk']) vul.description = form.cleaned_data['description'] vul.ask_vendor_status = True old_cve = vul.cve vul.cve = form.cleaned_data['cve'] - logger.debug(vul) vul.save() errors = [] #get all tags: @@ -11427,7 +11353,7 @@ def form_valid(self, form): if f.is_valid(): refs.append({'refsource': f.cleaned_data['ref_source'], 'url': f.cleaned_data['reference']}) else: - logger.debug(f.errors) + logger.debug(f"Form errors in {self.__class__.__name__} si {f.errors}") sterrors = str(f.errors) if sterrors.find("valid URL") != -1: errors.append(f"Problem adding reference {len(refs)+1} - not a valid URL") @@ -11442,7 +11368,7 @@ def form_valid(self, form): cve.cwe = json.dumps(cwes) if vul.cve: cve.cve_name = vul.vul - logger.debug(f"saving {cve.cwe}") + logger.debug(f"Saving a new CWE {cve.cwe}") cve.save() else: if vul.cve: @@ -11519,7 +11445,7 @@ def form_valid(self, form): date = timezone.now(), user = self.request.user, comment=vul.vul, - action_type=1) + action_type=CaseAction.lookup('VinceTrack')) action.save() if errors: @@ -11539,7 +11465,6 @@ def form_invalid(self, form): return super().form_invalid(form) def post(self, request, *args, **kwargs): - logger.debug("IN THIS POST") logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") if request.POST.get('add_tag'): vul = get_object_or_404(Vulnerability, id=kwargs['pk']) @@ -11555,7 +11480,7 @@ def post(self, request, *args, **kwargs): user=self.request.user) fup.save() else: - logger.debug("invalid tag - tag doesn't exist in tag manager") + logger.debug(f"invalid tag - tag {tag} doesn't exist in tag manager") return JsonResponse({'tag': tag, 'vul': vul.id, 'error': "Invalid Tag."}, status=401) else: return JsonResponse({'tag': tag, 'vul': vul.id, 'error': "Tag is too long. Max 50 characters."}, status=401) @@ -11748,10 +11673,9 @@ def post(self, request, *args, **kwargs): return self.form_valid(form) else: form.add_error(None, "At least one Product is required") - logger.debug(cveprodformset.errors) return self.form_invalid(form) else: - logger.debug(form.errors) + logger.debug(f"Form errors in {self.__class__.__name__} si {f.errors}") return self.form_invalid(form) def form_valid(self, form): @@ -11846,8 +11770,6 @@ def test_func(self): return is_in_group_vincetrack(self.request.user) def form_valid(self, form): - logger.debug("IN FORM VALID") - cveprodformset = self.CVEProductFormSet(self.request.POST, prefix='product') cverefformset = self.CVEReferenceFormSet(self.request.POST, prefix='ref') #cvewaformset = self.CVEWorkaroundFormSet(self.request.POST, prefix='wa') @@ -11899,7 +11821,7 @@ def form_valid(self, form): #connect any new reservations cve_res = CVEReservation.objects.filter(cve_id=cve.cve_name).first() if cve_res: - logger.debug(f"FOUND RESERVATION for {x}") + logger.debug(f"FOUND CVE RESERVATION for {x}") cve_res.cve_info = cve cve_res.save() @@ -11924,7 +11846,6 @@ def form_valid(self, form): return redirect("vince:vul", cve.vul.id) def form_invalid(self, form): - logger.debug("FORM INVALID") vul = get_object_or_404(Vulnerability, id=self.kwargs['vul']) return render(self.request, 'vince/cveform.html', {'form': form, @@ -11946,10 +11867,8 @@ def post(self, request, *args, **kwargs): return self.form_valid(form) else: form.add_error(None, "At least one Product is required") - logger.debug(cveprodformset.errors) return self.form_invalid(form) else: - logger.debug(form.errors) return self.form_invalid(form) def get_context_data(self, **kwargs): @@ -11957,7 +11876,6 @@ def get_context_data(self, **kwargs): initial = {} oldcwes = [] vulnote = [] - logger.debug(self.kwargs) if "vul" in self.kwargs: initial['vul'] = self.kwargs['vul'] vul = get_object_or_404(Vulnerability, id=self.kwargs['vul']) @@ -12021,7 +11939,6 @@ def form_valid(self, form): case_increment = vulno, ask_vendor_status = True, user=self.request.user) - logger.debug(vul) vul.save() case.vul_incrementer = vulno case.changes_to_publish = True @@ -12112,7 +12029,7 @@ def form_valid(self, form): date = timezone.now(), user = self.request.user, comment=vul.vul, - action_type=1) + action_type=CaseAction.lookup('VinceTrack')) action.save() if errors: @@ -12193,17 +12110,16 @@ def post(self, request, *args, **kwargs): date = timezone.now(), user = self.request.user, comment = comment, - action_type=1) + action_type=CaseAction.lookup('VinceTrack')) action.save() if vendor.approve_ticket: - logger.debug("there is a ticket on this vendor statement") comment = f"{self.request.user.usersettings.preferred_username} has {comment}" f = FollowUp(title="Statements approved and ticket closed.", ticket=vendor.approve_ticket, date=timezone.now(), comment=comment, user=self.request.user) f.save() vendor.approve_ticket.status = Ticket.CLOSED_STATUS vendor.approve_ticket.save() if send_email: - logger.warning("THIS WOULD SEND VENDOR APPROVAL EMAILS.") + logger.debug(f"Currently disabled to send approval email {vendor.contact} for {vendor.case}") #send_approval_email(vendor.contact, vendor.case) return JsonResponse({'success': True, 'link': reverse('vince:case', args=[vendor.case.id]) + '#vendors'}, status=200) @@ -12231,7 +12147,7 @@ def post(self, request, *args, **kwargs): date = timezone.now(), user = self.request.user, comment = comment, - action_type=1) + action_type=CaseAction.lookup('VinceTrack')) if vul.vendor.approve_ticket: comment = f"{self.request.user.usersettings.preferred_username} has {comment}" f = FollowUp(title="Approved vul-specific statement.", ticket=vul.vendor.approve_ticket, date=timezone.now(), comment=comment, user=self.request.user) @@ -12284,7 +12200,7 @@ def post(self, request, *args, **kwargs): date = timezone.now(), user = self.request.user, comment=vul.description, - action_type=1) + action_type=CaseAction.lookup('VinceTrack')) action.save() try: if case.vulnote.date_published or case.publicdate: @@ -12327,8 +12243,7 @@ def test_func(self): return False def post(self, request, *args, **kwargs): - logger.debug(request.POST) - logger.debug(self.kwargs['pk']) + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") if int(self.kwargs['pk']) != int(self.request.POST.get('vul')): #this was messed with raise Http404 @@ -12347,7 +12262,8 @@ def post(self, request, *args, **kwargs): vulcvss.save() else: - logger.debug(form.errors) + logger.debug(f"Form in {self.__class__.__name__} has errors {form.errors}") + return JsonResponse({'success': False}, status=200) return JsonResponse({'success': True}, status=200) @@ -12373,7 +12289,7 @@ def test_func(self): return False def post(self, request, *args, **kwargs): - logger.debug(request.POST) + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") ssvc = get_object_or_404(VulSSVC, vul__id=self.kwargs['pk']) ssvc.delete() @@ -12403,7 +12319,7 @@ def test_func(self): return False def post(self, request, *args, **kwargs): - logger.debug(request.POST) + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") ssvc = get_object_or_404(VulCVSS, vul__id=self.kwargs['pk']) ssvc.delete() @@ -12435,7 +12351,7 @@ def test_func(self): return False def post(self, request, *args, **kwargs): - logger.debug(request.POST) + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") exploit = self.request.POST.get('data[choices][0][Exploitation]') if exploit == 'none': @@ -12497,7 +12413,6 @@ def get_context_data(self, **kwargs): context['vendor_list'] = VendorStatus.objects.filter(vul=context['vul']).order_by('vendor__contact__vendor_name') vlist = context['vendor_list'].values_list('vendor__id', flat=True) - logger.debug(vlist) context['vendor_unknown_list'] = VulnerableVendor.objects.filter(case=context['vul'].case, deleted=False).exclude(id__in=vlist).order_by('contact__vendor_name') context['cveallocation'] = CVEAllocation.objects.filter(vul=context['vul']).first() context['exploits'] = VulExploit.objects.filter(vul=context['vul']) @@ -12538,14 +12453,14 @@ class AddUserToContactView(LoginRequiredMixin, TokenMixin, UserPassesTestMixin, form_class=AddUserToContactForm def post(self, request, *args, **kwargs): - logger.debug(request.POST) + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") vendors = request.POST.getlist('vendors[]') newuser = User.objects.using('vincecomm').filter(id=self.kwargs['pk']).first() for vendor in vendors: contact = Contact.objects.filter(vendor_name=vendor).first() if contact: #add this user to contact - logger.debug("Adding user to contact %s" % contact.vendor_name) + logger.debug(f"Adding user {newuser} to contact {contact.vendor_name}") email, created = EmailContact.objects.update_or_create(contact=contact, email=newuser.username, defaults={'name': newuser.get_full_name(), @@ -12712,14 +12627,14 @@ def post(self, request, *args, **kwargs): self.request, _("Error with resetting this user's TOTP")) error = True - logger.debug(e) + logger.debug(f"Error in TOTP for {user} error is {e}") else: logger.debug(f"{user.username} is using sms mfa") totp = "SMS" try: disable_sms_mfa(user.username) except Exception as e: - logger.debug(e) + logger.debug(f"Error in 2FA for {user} error is {e}") error = True messages.error( self.request, @@ -12925,9 +12840,7 @@ def get_context_data(self, **kwargs): read_queues = get_r_queues(self.request.user) context['tickettags'] = TicketTag.objects.filter(ticket__queue__in=my_queues).values('tag').order_by('tag').annotate(count=Count('tag')).annotate(month_count=Count('tag', filter=Q(ticket__created__year=year, ticket__created__month=month))).order_by('-tag') - logger.debug(context['tickettags']) #context['tickettags'] = TicketTag.objects.filter(ticket__queue__in=my_queues).values('tag').order_by('tag').annotate(count=Count('tag')).order_by('-count') - logger.debug(context['tickettags']) context['casetags'] = CaseTag.objects.filter(case__team_owner=context['my_team']).values('tag').order_by('tag').annotate(count=Count('tag')).annotate(count=Count('tag')).annotate(month_count=Count('tag', filter=Q(created__year=year, created__month=month))).order_by('-count') context['vultags'] = VulnerabilityTag.objects.filter(vulnerability__case__team_owner=context['my_team']).values('tag').order_by('tag').annotate(count=Count('tag')).annotate(count=Count('tag')).annotate(month_count=Count('tag', filter=Q(vulnerability__date_added__year=year, vulnerability__date_added__month=month))).order_by('-count') @@ -12945,10 +12858,9 @@ def test_func(self): def post(self, request, *args, **kwargs): - logger.debug(f"UserGraphReport Post: {self.request.POST}") - month = self.request.POST['month'] + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") + month = self.request.POST.get('month',datetime.datetime.now().strftime('%b-%y')) dt = datetime.strptime(month, '%b-%y') - logger.debug(dt) users = User.objects.using('vincecomm').filter(date_joined__month=dt.month, date_joined__year=dt.year) \ .annotate(day=TruncDay("date_joined")) \ @@ -13003,7 +12915,7 @@ def get_context_data(self, **kwargs): monthly.append(datetime(y, m+1, 1).strftime("%b-%y")) if not(monthly): - logger.debug("ONLY 1 month") + logger.debug(f"Report requested for one month by {self.request.user}") monthly = [users[0]["month"].strftime("%b-%y")] for x in monthly: @@ -13075,7 +12987,7 @@ def get_queryset(self): return Ticket.objects.filter(queue__in=my_queues, status__in=[Ticket.OPEN_STATUS, Ticket.REOPENED_STATUS]).exclude(assigned_to__isnull=False).order_by('-modified') def post(self, request, *args, **kwargs): - logger.debug(f"TicketFilterResult Post: {self.request.POST}") + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") res = self.get_queryset() @@ -13257,7 +13169,7 @@ def post(self, request, *args, **kwargs): for cp in cps: ca = CaseAction(case=cp.case, title="Removed Participant from Case", comment="Participant %s removed from case" % cp.user_name, - user=self.request.user, action_type=1) + user=self.request.user, action_type=CaseAction.lookup('VinceTrack')) ca.save() remove_participant_vinny_case(cp.case, cp) cp.delete() @@ -13299,7 +13211,7 @@ def test_func(self): return is_in_group_vincetrack(self.request.user) def post(self, request, *args, **kwargs): - logger.debug(self.request.POST) + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") if self.request.POST.get('arg'): initial = {} @@ -13351,7 +13263,7 @@ def test_func(self): return is_in_group_vincetrack(self.request.user) def post(self, request, *args, **kwargs): - logger.debug(self.request.POST) + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") if self.request.POST.get('arg'): event = get_object_or_404(CalendarEvent, id=self.request.POST['arg']) @@ -13482,7 +13394,6 @@ def get_context_data(self, **kwargs): context['team'] = user_groups[0].name context['other_teams'] = user_groups.exclude(id=user_groups[0].id) user_groups=[user_groups[0]] - logger.debug(user_groups) context['tags'] = TagManager.objects.filter(Q(team__in=user_groups)|Q(team__isnull=True)) context['group'] = user_groups[0] context['activity'] = context['tags'].order_by('-created') @@ -13501,13 +13412,12 @@ def get_context_data(self, **kwargs): context = super(VinceNewTagView, self).get_context_data(**kwargs) context['tag_type_id'] = self.kwargs['pk'] types = dict(TagManager.TAG_TYPE_CHOICES) - logger.debug(types) context['form'] = AddNewTagForm(initial={'tag_type':int(self.kwargs['pk'])}) context['tag_type'] = types[int(self.kwargs['pk'])] return context def post(self, request, *args, **kwargs): - logger.debug(self.request.POST) + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") #make sure tag doesn't already exist form = AddNewTagForm(self.request.POST) user_groups = self.request.user.groups.exclude(groupsettings__contact__isnull=True) @@ -13516,7 +13426,7 @@ def post(self, request, *args, **kwargs): user_groups = self.request.user.groups.filter(id=self.kwargs.get('group')) else: user_groups=[user_groups[0]] - logger.debug(user_groups) + if self.request.POST.get('id'): tag = get_object_or_404(TagManager, id=self.request.POST.get('id')) @@ -13555,7 +13465,8 @@ def post(self, request, *args, **kwargs): t.team = user_groups[0] t.save() else: - logger.debug(form.errors) + logger.debug(f"Form error in {self.__class__.__name__} post: {form.errors}") + return redirect("vince:tags") @@ -14540,7 +14451,7 @@ def post(self, request, *args, **kwargs): _(f"User {self.request.POST['email']} successfully updated")) return render(request, 'vince/user_search.html', {'form': UserSearchForm()}) else: - logger.debug(form.errors) + logger.debug(f"Form error in {self.__class__.__name__} post: {form.errors}") return super().form_invalid(form) @@ -14582,7 +14493,7 @@ def get_context_data(self, **kwargs): return context def post(self, request, *args, **kwargs): - logger.debug(self.request.POST) + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") if self.request.POST.get('id'): assignment = get_object_or_404(UserAssignmentWeight, id=self.request.POST.get('id')) if self.request.POST.get('remove'): @@ -14645,23 +14556,7 @@ def get_context_data(self, **kwargs): context['accounts'] = CVEServicesAccount.objects.filter(team=context['my_team']) if context['accounts']: acc = context['accounts'][0] - context['cve_service'] = acc - cve_lib = cvelib.CveApi(acc.email, acc.org_name, acc.api_key, env=settings.CVE_SERVICES_API) - if cve_lib.ping(): - try: - context['account'] = cve_lib.show_user(acc.email) - context['org'] = cve_lib.show_org() - context['quota'] = cve_lib.quota() - context['cve_users'] = list(cve_lib.list_users()) - except cvelib.CveApiError as e: - context['account_error'] = str(e) - context['accounts'][0].active = False - context['accounts'][0].save() - else: - context['service_down'] = 1 - - - + get_cve_info(context,acc) return context class CVEServicesDetailAccount(LoginRequiredMixin, TokenMixin, UserPassesTestMixin, generic.DetailView): @@ -14679,21 +14574,7 @@ def test_func(self): def get_context_data(self, **kwargs): context = super(CVEServicesDetailAccount, self).get_context_data(**kwargs) acc = get_object_or_404(CVEServicesAccount, id=self.kwargs['pk']) - context['cve_service'] = acc - cve_lib = cvelib.CveApi(acc.email, acc.org_name, acc.api_key, env=settings.CVE_SERVICES_API) - if cve_lib.ping(): - try: - context['account'] = cve_lib.show_user(acc.email) - context['org'] = cve_lib.show_org() - context['quota'] = cve_lib.quota() - context['cve_users'] = list(cve_lib.list_users()) - except cvelib.CveApiError as e: - context['account_error'] = str(e) - acc.active = False - acc.save() - else: - context['service_down'] = 1 - + get_cve_info(context,acc) return context class CVEServicesDeleteAccount(LoginRequiredMixin, TokenMixin, UserPassesTestMixin, generic.TemplateView): @@ -14714,7 +14595,7 @@ def get_context_data(self, **kwargs): return context def post(self, request, *args, **kwargs): - logger.debug(self.request.POST) + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") account = get_object_or_404(CVEServicesAccount, id=self.kwargs['pk']) if self.request.user.is_superuser: account.delete() @@ -14775,8 +14656,7 @@ def get_context_data(self, **kwargs): return context def post(self, request, *args, **kwargs): - logger.debug(self.request.POST) - + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") form = CVEReserveForm(self.request.POST) groups = self.request.user.groups.exclude(groupsettings__contact__isnull=True) #get available accounts associated with my teams @@ -14835,11 +14715,16 @@ def post(self, request, *args, **kwargs): new_cve_ids = [] + quota = 0 cve_lib = cvelib.CveApi(account.email, account.org_name, account.api_key, env=settings.CVE_SERVICES_API) - if cve_lib.ping(): + if cve_lib.ping() is None: try: - new_reserve, quota = cve_lib.reserve(request_no, random, form.cleaned_data["year"]) - for x in new_reserve["cve_ids"]: + reserved = cve_lib.reserve(request_no, random, form.cleaned_data["year"]) + if not "cve_ids" in reserved: + raise Exception("No cve_ids returned") + if "meta" in reserved and "remaining_quota" in reserved["meta"]: + quota = reserved["meta"]["remaining_quota"] + for x in reserved["cve_ids"]: cveres = CVEReservation(cve_info = cve, cve_id = x["cve_id"], account=account, @@ -14855,7 +14740,7 @@ def post(self, request, *args, **kwargs): new_cve_ids.append(cveres.cve_id) - except cvelib.CveApiError as e: + except Exception as e: messages.error( self.request, _(f"Error Requesting CVE ID {e}")) @@ -14864,7 +14749,7 @@ def post(self, request, *args, **kwargs): else: messages.error( self.request, - _f("Error: CVE Service down. Try again later.")) + _(f"Error: CVE Service down. Try again later.")) return redirect(ret_link) messages.success( @@ -14904,7 +14789,7 @@ def test_func(self): return False def post(self, request, *args, **kwargs): - logger.debug(self.request.POST) + logger.debug(f"{self.__class__.__name__} post: {self.request.POST}") year = self.request.POST.get('year') cve_id = self.request.POST.get('wordSearch') @@ -14922,10 +14807,10 @@ def post(self, request, *args, **kwargs): else: acc = get_object_or_404(CVEServicesAccount, id=self.kwargs['pk']) cve_lib = cvelib.CveApi(acc.email, acc.org_name, acc.api_key, env=settings.CVE_SERVICES_API) - if cve_lib.ping(): + if cve_lib.ping() is None: try: if cve_id: - cves = cve_lib.show_cve(cve_id) + cves = cve_lib.show_cve_id(cve_id) cves = [cves] elif year: cves = list(cve_lib.list_cves(year=year)) @@ -14948,7 +14833,7 @@ def post(self, request, *args, **kwargs): x["vul"] = reverse("vince:vul", args=[v_cve.id]) x["case"] = v_cve.case.vu_vuid - except cvelib.CveApiError as e: + except Exception as e: return render(request, "vince/cve_list_results.html", {'error': e}) else: @@ -14964,10 +14849,10 @@ def get_context_data(self, **kwargs): context["my_team"] = acc.team context['form'] = CVEFilterForm(initial={'year': datetime.now().year}) cve_lib = cvelib.CveApi(acc.email, acc.org_name, acc.api_key, env=settings.CVE_SERVICES_API) - if cve_lib.ping(): + if cve_lib.ping() is None: try: cves = list(cve_lib.list_cves(year=datetime.now().year)) - except cvelib.CveApiError as e: + except Exception as e: context['account_error'] = str(e) acc.active = False acc.save() @@ -14993,7 +14878,6 @@ def get_context_data(self, **kwargs): x["case"] = v_cve.case.vu_vuid context['cves'] = cves - logger.debug(context['cves']) return context @@ -15010,19 +14894,16 @@ def test_func(self): def get_context_data(self, **kwargs): context = super(CVESingleDetailView, self).get_context_data(**kwargs) - logger.debug("SINGLE VIEW") acc = get_object_or_404(CVEServicesAccount, id=self.kwargs['pk']) cve_lib = cvelib.CveApi(acc.email, acc.org_name, acc.api_key, env=settings.CVE_SERVICES_API) - if cve_lib.ping(): + if cve_lib.ping() is None: try: - cves = cve_lib.show_cve(self.kwargs['cveid']) - logger.debug(cves) - except cvelib.CveApiError as e: + context['cve'] = cve_lib.show_cve_id(self.kwargs['cveid']) + except Exception as e: context['account_error'] = str(e) else: context['service_down'] = 1 - context['cve'] = cves context['vince_request'] = CVEReservation.objects.filter(cve_id = self.kwargs['cveid']).first() @@ -15067,7 +14948,7 @@ def get_context_data(self, **kwargs): return context def form_invalid(self, form): - logger.debug(f"CVEservicesManageAccount errors: {form.errors}") + logger.debug(f"Form error in {self.__class__.__name__} post: {form.errors}") return super().form_invalid(form) def post(self, request, *args, **kwargs): diff --git a/vincepub/models.py b/vincepub/models.py index 37ae37f..7700108 100644 --- a/vincepub/models.py +++ b/vincepub/models.py @@ -37,11 +37,18 @@ from django.contrib.postgres.search import SearchVectorField from bigvince.storage_backends import VRFReportsStorage from django.conf import settings +#Django 3 and up +from django.db.models import JSONField + +class OldJSONField(JSONField): + """ This was due to legacy support in Django 2.2. from_db_value + should be explicitily sepcified when extending JSONField """ -class OldJSONField(fields.JSONField): def db_type(self, connection): return 'json' + def from_db_value(self, value, expression, connection): + return value class VUReportManager(models.Manager): diff --git a/vincepub/serializers.py b/vincepub/serializers.py index 9f8a693..c662ea4 100644 --- a/vincepub/serializers.py +++ b/vincepub/serializers.py @@ -163,6 +163,16 @@ class Meta: model = VUReport fields = ["document","vulnerabilities","product_tree"] + def to_representation(self, case): + ret = super().to_representation(case) + if not ret['vulnerabilities']: + del ret['product_tree'] + if hasattr(settings,'CSAF_VUL_EMPTY'): + ret['vulnerabilities'] = settings.CSAF_VUL_EMPTY + else: + ret['vulnerabilities'] = [{"notes": [{"category": "general","text": "No vulnerabilities have been defined at this time for this report"}]}] + return ret + def isvalid_reference(self,ref): if not ref: return False @@ -170,7 +180,7 @@ def isvalid_reference(self,ref): self.validurl(ref) return True except Exception as e: - logger.debug("Encountered invalid URI for reference ignoring %s due to error %s" %(ref,str(e))) + logger.debug("CSAF Parser encountered invalid URI for reference ignoring %s due to error %s" %(ref,str(e))) return False @@ -232,13 +242,15 @@ def get_csafdocument(self,vr): if ven.addendum: addinfo = {"category": "other", "text": ven.addendum, - "title": f"settings.ORG_NAME comment on {ven.vendor} notes"} + "title": f"{settings.ORG_NAME} comment on {ven.vendor} notes"} csafdoc["notes"] += [addinfo] return csafdoc def get_csafvuls(self, vr): self.mproduct_tree = {"branches": []} casevuls = list(NoteVulnerability.objects.filter(note__vuid=vr.idnumber)) + if not len(casevuls): + return None csafvuls = [] tfile = os.path.join(self.template_json_dir,"vulnerability.json") csafvul_template = open(tfile,"r").read() @@ -258,6 +270,7 @@ def get_csafvuls(self, vr): csafvul = csafvul_template % { "vuid": vr.vuid, "cve": cve, + "ORG_NAME": settings.ORG_NAME, "title": json.dumps(casevul.description.split(".")[0]+"."), "description": json.dumps(casevul.description) } csafvulj = json.loads(csafvul,strict=False) diff --git a/vincepub/views.py b/vincepub/views.py index d116cf3..7532b90 100644 --- a/vincepub/views.py +++ b/vincepub/views.py @@ -27,7 +27,10 @@ # DM21-1126 ######################################################################## from __future__ import print_function -from django.shortcuts import render, get_object_or_404, render_to_response, redirect +try: + from django.shortcuts import render, get_object_or_404, render_to_response, redirect +except: + from django.shortcuts import render, get_object_or_404, redirect from django.views import generic from django.http import HttpResponse, HttpResponseRedirect, JsonResponse, Http404 from django.views.generic.edit import FormView, UpdateView, CreateView, FormMixin @@ -455,6 +458,30 @@ def get_vrf_id(): vrf_id = str(today.year)[2:] + '-' + vrf_id_month + '-' + vrf_id_rnd return vrf_id +class PublicAPIView(generics.RetrieveAPIView, generics.GenericAPIView): + """ All public API views will require no authentication + and finalize_response will provide HTTP 200 with JSON error + for friendly working of the JSON API clients. + """ + authentication_classes = [] + permission_classes = (AllowAny,) + is_empty = False + def finalize_response(self, request, response, *args, **kwargs): + response = super().finalize_response(request, response, *args, **kwargs) + generic_error = {"error": "Content requested either does not exist or you do not have permissions to view it!"} + if hasattr(response,'status_code') and not response.status_code in [200,302]: + return JsonResponse(generic_error) + if hasattr(self,'is_empty') and self.is_empty: + return JsonResponse(generic_error) + if hasattr(response,'status_code') and 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 + +class PublicListAPIView(generics.ListAPIView): + """ All public list api views will require no authentication """ + authentication_classes = [] + permission_classes = (AllowAny,) + class VinceView(generic.TemplateView): template_name = 'vincepub/vince.html' @@ -601,15 +628,21 @@ def escape_query(text, re_escape_chars): def process_query(s): + s = s.replace("'","") try: query = "&" .join("$${0}$$:*".format(word) for word in shlex.split(escape_query(s, RE_POSTGRES_ESCAPE_CHARS))) - except ValueError: + except Exception as e: + logger.debug(f"Error {e} from shlex.split when trying to escape string {s} for Postgres") #shlex returns a ValueError when it doesn't have closing quote if s.endswith('\\'): if not s.endswith('\\\\'): s = s + '\\' s = s + '"' - query = "&" .join("$${0}$$:*".format(word) for word in shlex.split(escape_query(s, RE_POSTGRES_ESCAPE_CHARS))) + try: + query = "&" .join("$${0}$$:*".format(word) for word in shlex.split(escape_query(s, RE_POSTGRES_ESCAPE_CHARS))) + except Exception as f: + logger.debug(f"Unable to escape characters in search error {f} for string {s} returning empty search") + return "" # this is for prefix searches query = re.sub(r'\s+', '<->', query) @@ -998,6 +1031,19 @@ class SecurityTxtView(Buildable404View): class VendorView(generic.ListView): template_name = 'vincepub/vendorinfo.html' model = VendorRecord + authentication_classes = [] + permission_classes = (AllowAny,) + + + def retrieve(self, request, *args, **kwargs): + try: + response = super().retrieve(request, *args, **kwargs) + except Exception as e: + logger.debug(f"Error in creating ViewSet {e}") + generic_error = {"error": "Content requested either does not exist or you do not have permissions to view it!"} + return Response(generic_error) + return response + def get_template_names(self): report = VUReport.objects.filter(vuid=self.kwargs['vuid']).first() @@ -1073,16 +1119,18 @@ def get_view_name(self): return "Vulnerability Note Instance Details" -class VulViewAPI(generics.GenericAPIView, mixins.ListModelMixin): +class VulViewAPI(PublicAPIView, mixins.ListModelMixin): queryset = NoteVulnerability.objects.all() serializer_class = serializers.VulSerializer - permission_classes = (AllowAny,) def get_queryset(self): return NoteVulnerability.objects.all() def list(self, request, *args, **kwargs): - vu = get_object_or_404(VUReport, idnumber=self.kwargs['pk']) + vu = VUReport.objects.filter(idnumber=self.kwargs['pk']).first() + if not vu: + self.is_empty = True + return Response({}) if vu.vulnote: queryset = NoteVulnerability.objects.filter(note__vureport__idnumber = self.kwargs['pk']) serializer = serializers.VulSerializer(queryset, many=True) @@ -1107,8 +1155,10 @@ def list(self, request, *args, **kwargs): def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) -class VUNoteViewByMonth(generics.ListAPIView): +class VUNoteViewByMonth(PublicListAPIView): serializer_class = serializers.VUReportSerializer + authentication_classes = [] + permission_classes = (AllowAny,) def get_view_name(self): return "Vulnerability Notes Published by Month" @@ -1118,9 +1168,10 @@ def get_queryset(self): month = re.sub('[^\d]','',self.kwargs['month']) return VUReport.objects.filter(datefirstpublished__year=year, datefirstpublished__month=month) -class VUNoteViewByYear(generics.ListAPIView): +class VUNoteViewByYear(PublicListAPIView): serializer_class = serializers.VUReportSerializer + def get_queryset(self): year = re.sub('[^\d]','',self.kwargs['year']) return VUReport.objects.filter(datefirstpublished__year=year) @@ -1161,8 +1212,10 @@ def summarize(self, request, *args, **kwargs): def get(self, request, *args, **kwargs): return self.summarize(request, *args, **kwargs) -class VendorViewByMonth(generics.ListAPIView): +class VendorViewByMonth(PublicListAPIView): serializer_class = serializers.NewVendorRecordSerializer + authentication_classes = [] + permission_classes = (AllowAny,) def get_view_name(self): return "Vendors By Month" @@ -1183,7 +1236,7 @@ def get(self, request, *args, **kwargs): y = serializers.VRSerializer(vendor, many=True) return Response(x.data+y.data) -class VendorViewByYear(generics.ListAPIView): +class VendorViewByYear(PublicListAPIView): serializer_class = serializers.VendorRecordSerializer def get_queryset(self): @@ -1232,7 +1285,7 @@ def summarize(self, request, *args, **kwargs): def get(self, request, *args, **kwargs): return self.summarize(request, *args, **kwargs) -class VendorViewAPI(generics.GenericAPIView, mixins.ListModelMixin): +class VendorViewAPI(PublicAPIView, mixins.ListModelMixin): queryset = VendorRecord.objects.all() serializer_class = serializers.VendorRecordSerializer permission_classes = (AllowAny,) @@ -1253,7 +1306,7 @@ def list(self, request, *args, **kwargs): def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) -class VendorVulViewAPI(generics.GenericAPIView, mixins.ListModelMixin): +class VendorVulViewAPI(PublicAPIView, mixins.ListModelMixin): queryset = VendorVulStatus.objects.all() serializer_class = serializers.VendorVulSerializer permission_classes = (AllowAny,) @@ -1275,7 +1328,7 @@ def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) -class CVEVulViewAPI(generics.GenericAPIView): +class CVEVulViewAPI(PublicAPIView): def get(self, request, *args, **kwargs): #Be safe and remove all alpha character if this view @@ -1351,7 +1404,7 @@ def error_404(request): data = {} return render(request, 'vincepub/404.html', data, status=404) -class CaseCSAFAPIView(generics.RetrieveAPIView): +class CaseCSAFAPIView(PublicAPIView): serializer_class = serializers.CSAFSerializer def get_view_name(self): @@ -1359,11 +1412,8 @@ def get_view_name(self): def get_object(self): svuid = re.sub('[^\d]','',self.kwargs['vuid']) - vr = get_object_or_404(VUReport, idnumber=svuid) + vr = VUReport.objects.filter(idnumber=svuid).first() + if not vr: + self.is_empty = True return vr - def finalize_response(self, request, response, *args, **kwargs): - response = super().finalize_response(request, response, *args, **kwargs) - if request.headers.get('Origin') and request.headers.get('Origin').find("https://") > -1: - response["Access-Control-Allow-Origin"] = request.headers.get('Origin') - return response diff --git a/vinny/forms.py b/vinny/forms.py index d6b12ea..4fdcd28 100644 --- a/vinny/forms.py +++ b/vinny/forms.py @@ -699,7 +699,9 @@ class InboxFilterForm(forms.Form): keyword = forms.CharField( max_length=200, label='Keyword(s)', - widget=forms.TextInput(attrs={'placeholder': 'Search by keyword'}), + widget=forms.TextInput(attrs={'placeholder': 'Search by keyword', + 'class': 'asyncdelaysearch' + }), required=False) status = forms.MultipleChoiceField( @@ -719,7 +721,8 @@ class LimitedCaseFilterForm(forms.Form): wordSearch = forms.CharField( max_length=100, label='Keyword(s)', - widget=forms.TextInput(attrs={'placeholder': 'Search by keyword'}), + widget=forms.TextInput(attrs={'placeholder': 'Search by keyword', + 'class': 'asyncdelaysearch'}), required=False) status = forms.MultipleChoiceField( @@ -742,7 +745,8 @@ class CaseFilterForm(forms.Form): wordSearch = forms.CharField( max_length=100, label='Keyword(s)', - widget=forms.TextInput(attrs={'placeholder': 'Search by keyword'}), + widget=forms.TextInput(attrs={'placeholder': 'Search by keyword', + 'class': 'asyncdelaysearch'}), required=False) status = forms.MultipleChoiceField( choices=STATUS_FILTER_CHOICES, diff --git a/vinny/mailer.py b/vinny/mailer.py index ceb1744..c16be0a 100644 --- a/vinny/mailer.py +++ b/vinny/mailer.py @@ -30,7 +30,10 @@ from django.conf import settings from django.contrib.auth.models import User from django.core.mail import EmailMultiAlternatives -from django.utils import six +try: + from django.utils import six +except: + import six from django.utils.safestring import mark_safe import mimetypes import os diff --git a/vinny/serializers.py b/vinny/serializers.py index 92cb77a..0f8c5dc 100644 --- a/vinny/serializers.py +++ b/vinny/serializers.py @@ -265,21 +265,37 @@ class Meta: model = Case fields = ["document","vulnerabilities","product_tree"] + + def to_representation(self, case): + ret = super().to_representation(case) + if not ret['vulnerabilities']: + del ret['product_tree'] + if hasattr(settings,'CSAF_VUL_EMPTY'): + ret['vulnerabilities'] = settings.CSAF_VUL_EMPTY + else: + ret['vulnerabilities'] = [{"notes": [{"category": "general","text": "No vulnerabilities have been defined at this time for this report"}]}] + return ret + + def get_csafdocument(self,case): tfile = os.path.join(self.template_json_dir,"document.json") + add_document = {} if not os.path.exists(tfile): return {"error": "Template file for csaf missing"} csafdocument_template = open(tfile,"r").read() vulnote = reverse("vincepub:vudetail", args=[case.vuid]) - if case.publicurl: + # Either one of this is the way to know l.publicdate or l.published + if case.publicdate or case.published: publicurl = f"{settings.KB_SERVER_NAME}{vulnote}" - else: - publicurl = f"{settings.KB_SERVER_NAME}{vulnote}#PendingRelease" - ackurl = f"{settings.KB_SERVER_NAME}{vulnote}#acknowledgments" - if case.published: case_status = "final" else: + publicurl = f"{settings.KB_SERVER_NAME}{vulnote}#PendingRelease" case_status = "interim" + if hasattr(settings,"CSAF_TLP_MAP") and settings.CSAF_TLP_MAP.get("PRIVATE"): + tlp_type = settings.CSAF_TLP_MAP.get("PRIVATE") + if hasattr(settings,"CSAF_DISTRIBUTION_OPTIONS") and settings.CSAF_DISTRIBUTION_OPTIONS.get(tlp_type): + add_document.update(settings.CSAF_DISTRIBUTION_OPTIONS.get(tlp_type)) + ackurl = f"{settings.KB_SERVER_NAME}{vulnote}#acknowledgments" if case.modified: revision_date = case.modified @@ -309,13 +325,15 @@ def get_csafdocument(self,case): "case_status": case_status, "case_version": case_version } - logger.debug(csafdocument) - return json.loads(csafdocument, strict=False) + csafd = json.loads(csafdocument,strict=False) + csafd.update(add_document) + return csafd def get_csafvuls(self, case): self.mproduct_tree = {"branches": []} casevuls = list(CaseVulnerability.objects.filter(case=case, deleted=False)) - logger.debug(casevuls) + if not len(casevuls): + return None csafvuls = [] tfile = os.path.join(self.template_json_dir,"vulnerability.json") csafvul_template = open(tfile,"r").read() diff --git a/vinny/static/vinny/js/case_search.js b/vinny/static/vinny/js/case_search.js index 9b7ea28..a1e0212 100644 --- a/vinny/static/vinny/js/case_search.js +++ b/vinny/static/vinny/js/case_search.js @@ -28,118 +28,7 @@ ######################################################################## */ -function nextPage(page) { - var url = $("#searchform").attr("action"); - $("#searchresults").load(url+"?page=" + page); -} - - -function reloadSearch() { - $.ajax({ - url: $("#searchform").attr("action"), - success: function(data) { - $("#searchresults").html(data); - } - }); -} - -function nextTickets(page) { - var url = $("#searchform").attr("action"); - $("#id_page").val(page); - $.ajax({ - url: url, - type: "POST", - data: $('#searchform').serialize(), - success: function(data) { - $("#searchresults").html(data); - } - }); - -} - -function searchTickets(e,noalert) { - if (e) { - e.preventDefault(); - } - $("#id_page").val("1"); - var url = $("#searchform").attr("action"); - lockunlock(true,'div.mainbody,div.vtmainbody','#searchresults'); - window.txhr = $.ajax({ - url: url, - type: "POST", - data: $('#searchform').serialize(), - success: function(data) { - lockunlock(false,'div.mainbody,div.vtmainbody','#searchresults'); - $("#searchresults").html(data); - }, - error: function() { - lockunlock(false,'div.mainbody,div.vtmainbody','#searchresults'); - console.log(arguments); - if(noalert != true) - alert("Search failed or canceled! See console log for details."); - }, - complete: function() { - /* Just safety net */ - lockunlock(false,'div.mainbody,div.vtmainbody','#searchresults'); - window.txhr = null; - } - }); -} - $(document).ready(function() { - - $(document).on("click", '.search_page', function(event) { - var page = $(this).attr('next'); - nextPage(page); - }); - - $(document).on("click", '.search_notes', function(event) { - var page = $(this).attr('next'); - nextTickets(page); - }); - - var input = document.getElementById("id_wordSearch"); - input.addEventListener("keyup", function(event) { - searchTickets(event,true); - }); - - var form = document.getElementById('searchform'); - if (form.attachEvent) { - form.attachEvent("submit", searchTickets); - } else { - form.addEventListener("submit", searchTickets); - } - - $("input[id^='id_status_']").change(function() { - searchTickets(); - }); - - $("#id_queue").change(function() { - searchTickets(); - }); - - $("#id_case").change(function() { - searchTickets(); - }); - - $("input[id^='id_owner_']").change(function() { - searchTickets(); - }); - - - $("#filter_by_dropdown_select_all_0").click(function(){ - $("input[type=checkbox]").prop('checked', $(this).prop('checked')); - - }); - - if ($("#searchform").attr("name") == "searchform") { - searchTickets(); - } - - /*$.getJSON("/vuls/ajax_calls/search/", function(data) { - vend_auto(data); - });*/ - var dateFormat = "yy-mm-dd", from = $( "#id_datestart" ) .datepicker({ @@ -149,10 +38,6 @@ $(document).ready(function() { dateFormat: dateFormat, numberOfMonths: 1, maxDate: "+0D" - }) - .on( "change", function() { - /*to.datepicker( "option", "minDate", getDate( this ) );*/ - searchTickets(); }), to = $( "#id_dateend" ).datepicker({ defaultDate: "+1w", @@ -162,13 +47,7 @@ $(document).ready(function() { numberOfMonths: 1, maxDate: "+0D" - }) - .on( "change", function() { - from.datepicker( "option", "maxDate", getDate( this ) ); - searchTickets(); }); - - $('input').qtip({ show: { ready: true @@ -184,9 +63,7 @@ $(document).ready(function() { style: { classes: 'qtip-red qtip-bootstrap' } - }); - function getDate( element ) { var date; try { diff --git a/vinny/static/vinny/js/inbox.js b/vinny/static/vinny/js/inbox.js index 1e36e3f..f29c499 100644 --- a/vinny/static/vinny/js/inbox.js +++ b/vinny/static/vinny/js/inbox.js @@ -28,124 +28,23 @@ ######################################################################## */ -function fload(fdiv,furl,fmethod) { - lockunlock(true,'div.mainbody,div.vtmainbody',fdiv); - window.txhr = $.ajax({ - url : furl, - type: fmethod, - data: fmethod == "POST" ? $('#filterform').serialize() : null, - success: function(data) { - lockunlock(false,'div.mainbody,div.vtmainbody','#inbox'); - $(fdiv).html(data); - }, - error: function() { - lockunlock(false,'div.mainbody,div.vtmainbody','#inbox'); - console.log(arguments); - alert("Search failed or canceled! See console log for details."); - }, - complete: function() { - /* Just safety net */ - lockunlock(false,'div.mainbody,div.vtmainbody','#inbox'); - window.txhr = null; - } - }); -} - -function nextPage(page) { - var url = $("#filterform").attr("action") + "?page=" + page; - fload('#inbox',url,"GET"); -} - -function nextThreads(page) { - $("#id_page").val(page); - var url = $("#filterform").attr("action"); - fload('#inbox',url,"POST"); -} - -function searchThreads(e) { - if (e) { - e.preventDefault(); - } - $("#id_page").val("1"); - var url = $("#filterform").attr("action"); - fload('#inbox',url,"POST"); -} - -function nextSent(page) { - var url = $("#filterform").attr("action") + "?page=" + page; - fload('#sent',url,"GET"); -} -function async_load() { - /* Async loading of inbox and sent items */ - nextThreads(1); - /* Load nextSent only on tab click - nextSent(1); - */ -} - $(document).ready(function() { - /* Async loading of inbox and sent items */ - async_load(); - $('#sent-label').on("click", function() { - if($('#sent div').length < 1) - nextSent(1); - }); - $(document).on("click", '.search_page', function(event) { - var page = $(this).attr('next'); - nextPage(page); - }); - - $(document).on("click", '.search_notes', function(event) { - var page = $(this).attr('next'); - event.preventDefault(); - nextThreads(page); - }); - - $(document).on("click", '.searchsent', function(event) { - var page = $(this).attr('next'); - event.preventDefault(); - nextSent(page); - }); - - - var filter_msg = document.getElementById("id_keyword"); - if (filter_msg) { - filter_msg.addEventListener("keyup", delaySearch(function(event) { - searchThreads(event); - },1000)); - } - - var modal = $("#deletemodal"); - + var deletemodal = $('#deletemodal'); + var _ = new Foundation.Reveal(deletemodal); $(document).on("click", ".delete-btn", function(event) { event.preventDefault(); var url = $(this).attr("action"); - + if(!url) { + console.log("No URL found to submit returning"); + return; + } $.ajax({ url: url, type: "GET", success: function(data) { - modal.html(data).foundation('open'); + deletemodal.html(data).foundation('open'); } }); - - }); - - $(document).on("submit", "#filterform", function(event) { - event.preventDefault(); - searchThreads(); - }); - - - $("input[id^='id_status_']").change(function() { - searchThreads(); - }); - - $("#filter_by_dropdown_select_all_0").click(function(){ - $("input[type=checkbox]").prop('checked', $(this).prop('checked')); - searchThreads(); - }); - }); diff --git a/vinny/static/vinny/js/vincecomm.js b/vinny/static/vinny/js/vincecomm.js index 299d128..608a444 100644 --- a/vinny/static/vinny/js/vincecomm.js +++ b/vinny/static/vinny/js/vincecomm.js @@ -1,4 +1,5 @@ -/*######################################################################### +/* + ######################################################################### # VINCE # # Copyright 2022 Carnegie Mellon University. @@ -44,28 +45,6 @@ function getCookie(name) { return cookieValue; } -function lockunlock(f,divmain,divhtml) { - if(f) { - /* Show search is in progress */ - $(divmain).css({opacity:0.5}); - if($(divhtml + ' > .loading').length < 1) - $(divhtml).prepend($('#hiddenloading').html()); - } else { - /* Back to normal */ - $(divmain).css({opacity:1}); - $(divhtml + ' > .loading').remove(); - } -} -function delaySearch(callfun,wait) { - var timeout; - return (...args) => { - clearTimeout(timeout); - timeout = setTimeout( - function () { - callfun.apply(this, args); - }, wait); - }; -} $(function () { $('span[title]').qtip({ @@ -85,7 +64,142 @@ $(function () { nav.toggleClass('less_padding', scrollTop > prev); prev = scrollTop; }); + /* Across the board loader functions */ + function lockunlock(f,divmain,divhtml) { + if(f) { + /* Show search is in progress */ + $(divmain).css({opacity:0.5}); + if($(divhtml).find('.loading').length < 1) + $(divhtml).prepend($('#hiddenloading').html()); + } else { + /* Back to normal */ + $(divmain).css({opacity:1}); + $(divhtml).find('.loading').remove(); + } + } + function delaySearch(callfun,wait) { + var timeout; + return (...args) => { + clearTimeout(timeout); + timeout = setTimeout( + function () { + callfun.apply(this, args); + }, wait); + }; + } + function asyncLoad(fdiv,furl,fmethod,pdiv,formpost,silent) { + /* asyncload from furl(URL) to fdiv(Content div identifier) whose + pdiv(Parent div identified) using fmethod(GET or POST) + in the case of POST use the formpost(Form identified) that + should be serialized. The var silent is now suppressed. + */ + if(!furl) + furl = fdiv.getAttribute("href"); + if(!furl) { + console.log("This div is not valid for async load return"); + return; + } + if(!fmethod) { + if(fdiv.getAttribute("method")) + fmethod = fdiv.getAttribute("method") + else + fmethod = "GET"; + } + if(!pdiv) { + if(fdiv.getAttribute("parentdiv")) + pdiv = fdiv.getAttribute("parentdiv"); + else + pdiv = fdiv; + } + let fdata = null; + if(fmethod == "POST") { + if (!formpost) { + if(fdiv.getAttribute("form")) + formpost = fdiv.getAttribute("form"); + } + if($(formpost).serialize()) + fdata = $(formpost).serialize(); + } + lockunlock(true,pdiv,fdiv); + window.txhr = $.ajax({ + url : furl, + type: fmethod, + data: fdata, + success: function(data) { + lockunlock(false,pdiv,fdiv); + $(fdiv).html(data); + }, + error: function() { + lockunlock(false,pdiv,fdiv); + console.log(arguments); + /* The var silent is no longer being used */ + $(fdiv).html("Content failed to be collected for display! "+ + "See console log for details."); + }, + complete: function() { + /* Just safety net */ + lockunlock(false,pdiv,fdiv); + window.txhr = null; + } + }); + } + + function clickAsyncLoad(e) { + /* Async loader for buttons will use data attributes + data-qparams="all=true" data-divid="ticket_activity" + */ + let el = e.target; + if($(el).data("divid")) { + let fdiv = document.getElementById($(el).data("divid")); + let pdiv = fdiv; + if(fdiv.getAttribute('href')) { + let furl = fdiv.getAttribute('href'); + if($(el).attr("data-completeurl") == furl) { + console.log("Already loaded content as per URL return"); + return true; + } + let fmethod = "GET" + if ($(el).data("qparams")) + furl = furl + "?" + $(el).data("qparams"); + if($(el).data("method")) + fmethod = $(el).data("method"); + if($(el).data("parentdiv")) + pdiv = $(el).data("parentdiv"); + let formpost = null; + if($(el).data("form")) + formpost = $(el).data("form"); + asyncLoad(fdiv,furl,fmethod,pdiv,formpost,true); + /* Mark download as complete */ + $(el).attr("data-completeurl",furl); + } + } + } + function pageAsyncLoad(e) { + /* Async loader for buttons will use closest div.asyncload */ + let el = e.target; + let page = parseInt($(el).attr('next')); + if(isNaN(page)) { + console.log(el); + console.log("Invalid page number returning " + $(el).attr('next')); + return; + } + let jfdiv = $(el).closest('div.asyncload'); + if(jfdiv.length == 1 && page) { + let fdiv = jfdiv[0]; + let furl = fdiv.getAttribute('href'); + let fmethod = fdiv.getAttribute('method'); + if(fmethod == 'POST') { + if(fdiv.getAttribute("form")) { + let formpost = fdiv.getAttribute("form"); + $(formpost).find('input[name="page"]').val(page); + } + } else { + furl = furl + "?page=" + String(page); + } + asyncLoad(fdiv,furl,fmethod); + } + } $(document).keyup(function(e) { if (e.key === "Escape") { if(window.txhr && 'abort' in window.txhr) { @@ -95,4 +209,49 @@ $(function () { } } }); + /* All asyncload class div with autoload should be populated async on + document.ready() */ + $('div.asyncload.autoload').each(function(_,fdiv) { + asyncLoad(fdiv); + }); + /* Create async onclick loaders via buttons if any */ + $('.asyncclick').on("click", clickAsyncLoad); + /* Create async page loaders if any */ + $(document).on("click",".asyncpage",pageAsyncLoad); + function loadiftarget(formpost,e) { + $(formpost).find('input[name="page"]').val(1); + let fdivid = $(formpost).attr("targetdivid") + if(fdivid && document.getElementById(fdivid)) { + e.preventDefault(); + e.stopPropagation(); + asyncLoad(document.getElementById(fdivid)); + } + } + $('.asyncform').on("submit keyup keypress",function(e) { + let keyCode = e.keyCode || e.which; + if (keyCode === 13) { + e.preventDefault(); + e.stopPropagation(); + return false; + } + loadiftarget(e.target,e); + }); + $('.asyncdelaysearch').on("keyup keypress", delaySearch(function(e) { + let keyCode = e.keyCode || e.which; + if (keyCode === 13) { + e.preventDefault(); + e.stopPropagation(); + return false; + } + loadiftarget($(e.target).closest('form'),e); + },1000)); + $('.asyncform input').not('.asyncdelaysearch').on("change",function(e) { + loadiftarget($(e.target).closest('form'),e); + }); + $('.select_all_checkbox').on('click',function(e) { + $(e.target).closest('.select_all_group') + .find('input[type="checkbox"]') + .prop('checked',$(e.target).prop('checked')); + }); + }); diff --git a/vinny/static/vinny/js/vinny_dashboard.js b/vinny/static/vinny/js/vinny_dashboard.js index 82758de..ddc9b8c 100644 --- a/vinny/static/vinny/js/vinny_dashboard.js +++ b/vinny/static/vinny/js/vinny_dashboard.js @@ -28,128 +28,13 @@ ######################################################################## */ -function searchThreads(e, newpage) { - var csrftoken = getCookie('csrftoken'); - - if (e) { - e.preventDefault(); - } - var page = 1; - if (newpage) { - page = newpage; - } - - var url = $("#filter_threads").attr("href"); - var owner = $("input[id^='id_owner_']:checked"); - lockunlock(true,'div.mainbody,div.vtmainbody','#casecontainer'); - window.txhr = $.ajax({ - url : url, - type: "POST", - data: {"keyword": $("#filter_threads").val(), - "owner": owner, - "page": page, - "csrfmiddlewaretoken": csrftoken - }, - success: function(data) { - lockunlock(false,'div.mainbody,div.vtmainbody','#casecontainer'); - $("#casecontainer").html(data); - }, - error: function() { - lockunlock(false,'div.mainbody,div.vtmainbody','#casecontainer'); - console.log(arguments); - /* - alert("Search failed or canceled! See console log for details."); - */ - }, - complete: function() { - /* Just safety net */ - lockunlock(false,'div.mainbody,div.vtmainbody','#casecontainer'); - window.txhr = null; - } - }); -} - -function searchReports(e, newpage) { - var csrftoken = getCookie('csrftoken'); - - if (e) { - e.preventDefault(); - } - var page = 1; - if (newpage) { - page = newpage; - } - - var idSelector = function() { return this.value; }; - var url = $("#filter_reports").attr("href"); - var status = $("#id_status input[type=checkbox]:checked").map(idSelector).get(); - window.txhr = $.ajax({ - url : url, - type: "POST", - data: {"keyword": $("#filter_reports").val(), - "page": page, - "status": status, - "csrfmiddlewaretoken": csrftoken - }, - success: function(data) { - lockunlock(false,'div.mainbody,div.vtmainbody','#casecontainer'); - $("#casecontainer").html(data); - }, - error: function() { - lockunlock(false,'div.mainbody,div.vtmainbody','#casecontainer'); - console.log(arguments); - /* - alert("Search failed or canceled! See console log for details."); - */ - }, - complete: function() { - /* Just safety net */ - lockunlock(false,'div.mainbody,div.vtmainbody','#casecontainer'); - window.txhr = null; - } - }); -} - - -$(document).ready(function() { - - var filter_msg = document.getElementById("filter_threads"); - if (filter_msg) { - filter_msg.addEventListener("keyup", delaySearch(function(event) { - searchThreads(event); - },1000)); - } - - var filter_report = document.getElementById("filter_reports"); - if (filter_report) { - filter_report.addEventListener("keyup", delaySearch(function(event) { - searchReports(event); - },1000)); - } - - $("input[id^='id_owner_']").change(function() { - searchThreads(); - }); - - $("#filter_by_dropdown_select_all_0").click(function(){ - $("#id_owner input[type=checkbox]").prop('checked', $(this).prop('checked')); - searchThreads(); - - }); +/* + See also static/vince/js/vinny_dashboard.js for Case view related + javascript libraries +*/ - $("input[id^='id_status_']").change(function() { - searchReports(); - }); +/* - $("#filter_by_dropdown_select_all_1").click(function(){ - $("#id_status input[type=checkbox]").prop('checked', $(this).prop('checked')); - searchReports(); - }); - - $(document).on("click", '.search_notes', function(event) { - var page = $(this).attr('next'); - $("#id_page").val(page); - searchThreads(0, page); - }); + All form submits moved to vincecomm.js -}); + */ diff --git a/vinny/templates/vinny/404.html b/vinny/templates/vinny/404.html index b7685fb..28d9f58 100644 --- a/vinny/templates/vinny/404.html +++ b/vinny/templates/vinny/404.html @@ -3,16 +3,16 @@
-

Page Not Found

+

Content Unavailable

-

We're sorry. The page you requested could not be found.

+

We're sorry. The page you requested could not be found or is inaccessible.


- + It looks like this content either does not exist or you do not have permissions to view it. If you signed up recently for a VINCE account, you may need to contact your Group administrator to provide necessary access for your account. If you have a VINCE account but do not know or do not have a Group that you are part of, you may also contact VINCE coordinators to request access to this content.

diff --git a/vinny/templates/vinny/base.html b/vinny/templates/vinny/base.html new file mode 100644 index 0000000..d1e0429 --- /dev/null +++ b/vinny/templates/vinny/base.html @@ -0,0 +1,166 @@ + + + + + + + + VINCE + +{% load static %} +{% load user_tags %} + + + + + + + + + +{% block js %} + + + + + +{% if devmode %} + +{% endif %} + +{% endblock %} + + {% block notice %}{% endblock %} + + +
+ +
+ +
+ {% include 'vinny/dropmenu.html' %} +
+
+ +
+ {% include 'vinny/offcanvas.html' %} +
+
+ {% block content %} + {% endblock %} +
+ +
+
+ +
+ +{% if devmode %} +
+{% endif %} + + + + diff --git a/vinny/templates/vinny/confirm_email_change.html b/vinny/templates/vinny/confirm_email_change.html index 956e26f..b75237f 100644 --- a/vinny/templates/vinny/confirm_email_change.html +++ b/vinny/templates/vinny/confirm_email_change.html @@ -6,7 +6,9 @@
- -
+ {% csrf_token %}
- +
@@ -33,9 +32,9 @@

Dashboard

{% if user.vinceprofile.is_track %}