CEF logging; bug 690286
This commit is contained in:
Родитель
4736c0bf22
Коммит
37d8135ae5
|
@ -27,6 +27,7 @@ from django.utils.translation import trans_real
|
|||
from django.utils.functional import Promise
|
||||
from django.utils.encoding import smart_str, smart_unicode
|
||||
|
||||
from cef import log_cef as _log_cef
|
||||
from easy_thumbnails import processors
|
||||
import html5lib
|
||||
from html5lib.serializer.htmlserializer import HTMLSerializer
|
||||
|
@ -180,7 +181,7 @@ def send_mail(subject, message, from_email=None, recipient_list=None,
|
|||
'unsubscribe_url': unsubscribe_url,
|
||||
'perm_setting': perm_setting.label,
|
||||
'SITE_URL': settings.SITE_URL,
|
||||
'mandatory': perm_setting.mandatory }
|
||||
'mandatory': perm_setting.mandatory}
|
||||
send_message = template.render(Context(context,
|
||||
autoescape=False))
|
||||
|
||||
|
@ -660,3 +661,29 @@ def smart_path(string):
|
|||
if os.path.supports_unicode_filenames:
|
||||
return smart_unicode(string)
|
||||
return smart_str(string)
|
||||
|
||||
|
||||
def log_cef(name, severity, request, *args, **kwargs):
|
||||
"""Simply wraps the cef_log function so we don't need to pass in the config
|
||||
dictionary every time. See bug 707060."""
|
||||
|
||||
c = {'cef.product': getattr(settings, 'CEF_PRODUCT', 'AMO'),
|
||||
'cef.vendor': getattr(settings, 'CEF_VENDOR', 'Mozilla'),
|
||||
'cef.version': getattr(settings, 'CEF_VERSION', '0'),
|
||||
'cef.device_version': getattr(settings, 'CEF_DEVICE_VERSION', '0'),
|
||||
'cef.file': getattr(settings, 'CEF_FILE', 'syslog'), }
|
||||
|
||||
# The CEF library looks for some things in the request object like
|
||||
# REQUEST_METHOD and any REMOTE_ADDR stuff. Django not only doesn't send
|
||||
# half the stuff you'd expect, but it specifically doesn't implement
|
||||
# readline on its FakePayload object so these things fail. I have no idea
|
||||
# if that's outdated code in Django or not, but andym made this
|
||||
# <strike>awesome</strike> less crappy so the tests will actually pass.
|
||||
# In theory, the second half of this if() will never be hit except in the
|
||||
# test runner. Good luck with that.
|
||||
if request:
|
||||
r = request.META.copy()
|
||||
else:
|
||||
r = {}
|
||||
|
||||
return _log_cef(name, severity, r, *args, config=c, **kwargs)
|
||||
|
|
|
@ -16,10 +16,10 @@ from statsd import statsd
|
|||
import amo
|
||||
import files.tasks
|
||||
from amo.decorators import no_login_required, post_required
|
||||
from amo.utils import log_cef
|
||||
from . import monitors
|
||||
|
||||
monitor_log = commonware.log.getLogger('z.monitor')
|
||||
csp_log = commonware.log.getLogger('z.csp')
|
||||
jp_log = commonware.log.getLogger('z.jp.repack')
|
||||
|
||||
|
||||
|
@ -111,11 +111,10 @@ def cspreport(request):
|
|||
method, url = v['request'].split(' ', 1)
|
||||
meta.update({'REQUEST_METHOD': method, 'PATH_INFO': url})
|
||||
v = [(k, v[k]) for k in report if k in v]
|
||||
# This requires you to use the cef.formatter to get something nice out.
|
||||
csp_log.warning('Violation', dict(environ=meta,
|
||||
product='addons',
|
||||
username=request.user,
|
||||
data=v))
|
||||
log_cef('CSP Violation', 5, meta, username=request.user,
|
||||
signature='CSPREPORT',
|
||||
msg='A client reported a CSP violation',
|
||||
cs7=v, cs7Label='ContentPolicy')
|
||||
except Exception:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import happyforms
|
|||
from tower import ugettext as _, ugettext_lazy as _lazy
|
||||
|
||||
import amo
|
||||
from amo.utils import slug_validator
|
||||
from amo.utils import log_cef, slug_validator
|
||||
from .models import (UserProfile, UserNotification, BlacklistedUsername,
|
||||
BlacklistedEmailDomain, BlacklistedPassword, DjangoUser)
|
||||
from .widgets import NotificationsSelectMultiple
|
||||
|
@ -65,6 +65,10 @@ class AuthenticationForm(auth_forms.AuthenticationForm):
|
|||
|
||||
class PasswordResetForm(auth_forms.PasswordResetForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.request = kwargs.pop('request', None)
|
||||
super(PasswordResetForm, self).__init__(*args, **kwargs)
|
||||
|
||||
def clean_email(self):
|
||||
email = self.cleaned_data['email']
|
||||
self.users_cache = UserProfile.objects.filter(email__iexact=email)
|
||||
|
@ -79,6 +83,16 @@ class PasswordResetForm(auth_forms.PasswordResetForm):
|
|||
def save(self, **kw):
|
||||
for user in self.users_cache:
|
||||
log.info(u'Password reset email sent for user (%s)' % user)
|
||||
if user.needs_tougher_password:
|
||||
log_cef('Password Reset', 5, self.request,
|
||||
username=user,
|
||||
signature='PASSWORDRESET',
|
||||
msg='Privileged user requested password reset')
|
||||
else:
|
||||
log_cef('Password Reset', 5, self.request,
|
||||
username=user,
|
||||
signature='PASSWORDRESET',
|
||||
msg='User requested password reset')
|
||||
try:
|
||||
# Django calls send_mail() directly and has no option to pass
|
||||
# in fail_silently, so we have to catch the SMTP error ourselves
|
||||
|
@ -94,6 +108,7 @@ class SetPasswordForm(auth_forms.SetPasswordForm, PasswordMixin):
|
|||
widget=PasswordMixin.widget())
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.request = kwargs.pop('request', None)
|
||||
super(SetPasswordForm, self).__init__(*args, **kwargs)
|
||||
# We store our password in the users table, not auth_user like
|
||||
# Django expects.
|
||||
|
@ -104,8 +119,12 @@ class SetPasswordForm(auth_forms.SetPasswordForm, PasswordMixin):
|
|||
return self.clean_password(field='new_password1', instance='user')
|
||||
|
||||
def save(self, **kw):
|
||||
# Three different loggers? :(
|
||||
amo.log(amo.LOG.CHANGE_PASSWORD, user=self.user)
|
||||
log.info(u'User (%s) changed password with reset form' % self.user)
|
||||
log_cef('Password Changed', 5, self.request,
|
||||
username=self.user.username, signature='PASSWORDCHANGED',
|
||||
msg='User changed password')
|
||||
super(SetPasswordForm, self).save(**kw)
|
||||
|
||||
|
||||
|
@ -318,6 +337,8 @@ class UserEditForm(UserRegisterForm, PasswordMixin):
|
|||
|
||||
if data['password']:
|
||||
u.set_password(data['password'])
|
||||
log_cef('Password Changed', 5, self.request, username=u.username,
|
||||
signature='PASSWORDCHANGED', msg='User changed password')
|
||||
if log_for_developer:
|
||||
amo.log(amo.LOG.CHANGE_PASSWORD)
|
||||
log.info(u'User (%s) changed their password' % u)
|
||||
|
@ -374,6 +395,13 @@ class AdminUserEditForm(UserEditForm):
|
|||
self.cleaned_data['admin_log'], details=self.changes())
|
||||
log.info('Admin edit user: %s changed fields: %s' %
|
||||
(self.instance, self.changed_fields()))
|
||||
if 'password' in self.changes():
|
||||
log_cef('Password Changed', 5, self.request,
|
||||
username=self.instance.username,
|
||||
signature='PASSWORDRESET',
|
||||
msg='Admin requested password reset',
|
||||
cs1=self.request.amo_user.username,
|
||||
cs1Label='AdminName')
|
||||
return profile
|
||||
|
||||
|
||||
|
|
|
@ -273,6 +273,7 @@ class UserProfile(amo.models.OnChangeMixin, amo.models.ModelBase):
|
|||
|
||||
def set_password(self, raw_password, algorithm='sha512'):
|
||||
self.password = create_password(algorithm, raw_password)
|
||||
# Can't do CEF logging here because we don't have a request object.
|
||||
|
||||
def email_confirmation_code(self):
|
||||
from amo.utils import send_mail
|
||||
|
@ -288,7 +289,7 @@ class UserProfile(amo.models.OnChangeMixin, amo.models.ModelBase):
|
|||
t.render(Context(c)), None, [self.email],
|
||||
use_blacklist=False)
|
||||
|
||||
def log_login_attempt(self, request, successful):
|
||||
def log_login_attempt(self, successful):
|
||||
"""Log a user's login attempt"""
|
||||
self.last_login_attempt = datetime.now()
|
||||
self.last_login_attempt_ip = commonware.log.get_remote_addr()
|
||||
|
|
|
@ -696,7 +696,7 @@ class TestFailedCount(UserViewBase):
|
|||
self.data = {'username': 'jbalogh@mozilla.com', 'password': 'foo'}
|
||||
|
||||
def log_calls(self, obj):
|
||||
return [call[0][1] for call in obj.call_args_list]
|
||||
return [call[0][0] for call in obj.call_args_list]
|
||||
|
||||
def test_login_passes(self, log_login_attempt):
|
||||
self.client.post(self.url, data=self.data)
|
||||
|
|
|
@ -33,7 +33,7 @@ from amo.decorators import (json_view, login_required, no_login_required,
|
|||
from amo.forms import AbuseForm
|
||||
from amo.urlresolvers import reverse
|
||||
from amo.helpers import absolutify, loc
|
||||
from amo.utils import send_mail, urlparams
|
||||
from amo.utils import log_cef, send_mail, urlparams
|
||||
from abuse.models import send_abuse_report
|
||||
from addons.models import Addon
|
||||
from addons.views import BaseFilter
|
||||
|
@ -285,6 +285,8 @@ def browserid_authenticate(request, assertion):
|
|||
"""
|
||||
Verify a BrowserID login attempt. If the BrowserID assertion is
|
||||
good, but no account exists on AMO, create one.
|
||||
|
||||
Request is only needed for logging. :(
|
||||
"""
|
||||
backend = BrowserIDBackend()
|
||||
result = backend.verify(assertion, settings.SITE_URL)
|
||||
|
@ -308,6 +310,9 @@ def browserid_authenticate(request, assertion):
|
|||
profile.user.backend = 'django_browserid.auth.BrowserIDBackend'
|
||||
profile.user.save()
|
||||
profile.save()
|
||||
log_cef('New Account', 5, request, username=username,
|
||||
signature='AUTHNOTICE',
|
||||
msg='User created a new account (from BrowserID)')
|
||||
return (profile, None)
|
||||
|
||||
|
||||
|
@ -374,6 +379,10 @@ def _login(request, template=None, data=None, dont_redirect=False):
|
|||
settings.LOGIN_RATELIMIT_USER) or limited)
|
||||
login_status = False
|
||||
except UserProfile.DoesNotExist:
|
||||
log_cef('Authentication Failure', 5, request,
|
||||
username=request.POST['username'],
|
||||
signature='AUTHFAIL',
|
||||
msg='The username was invalid')
|
||||
pass
|
||||
|
||||
partial_form = partial(forms.AuthenticationForm, use_recaptcha=limited)
|
||||
|
@ -403,7 +412,11 @@ def _login(request, template=None, data=None, dont_redirect=False):
|
|||
log.warning(u'Attempt to log in with deleted account (%s)' % user)
|
||||
messages.error(request, _('Wrong email address or password!'))
|
||||
data.update({'form': partial_form()})
|
||||
user.log_login_attempt(request, False)
|
||||
user.log_login_attempt(False)
|
||||
log_cef('Authentication Failure', 5, request,
|
||||
username=request.user,
|
||||
signature='AUTHFAIL',
|
||||
msg='Account is deactivated')
|
||||
return jingo.render(request, template, data)
|
||||
|
||||
if user.confirmationcode:
|
||||
|
@ -423,7 +436,7 @@ def _login(request, template=None, data=None, dont_redirect=False):
|
|||
messages.info(request, _('Having Trouble?'), msg2,
|
||||
title_safe=True, message_safe=True)
|
||||
data.update({'form': partial_form()})
|
||||
user.log_login_attempt(request, False)
|
||||
user.log_login_attempt(False)
|
||||
return jingo.render(request, template, data)
|
||||
|
||||
rememberme = request.POST.get('rememberme', None)
|
||||
|
@ -440,7 +453,11 @@ def _login(request, template=None, data=None, dont_redirect=False):
|
|||
r = jingo.render(request, template, data)
|
||||
|
||||
if login_status is not None:
|
||||
user.log_login_attempt(request, login_status)
|
||||
user.log_login_attempt(login_status)
|
||||
log_cef('Authentication Failure', 5, request,
|
||||
username=request.POST['username'],
|
||||
signature='AUTHFAIL',
|
||||
msg='The password was incorrect')
|
||||
|
||||
if (settings.REGISTER_OVERRIDE_TOKEN
|
||||
and request.GET.get('ro') == settings.REGISTER_OVERRIDE_TOKEN):
|
||||
|
@ -555,6 +572,9 @@ def register(request):
|
|||
u.save()
|
||||
u.create_django_user()
|
||||
log.info(u'Registered new account for user (%s)', u)
|
||||
log_cef('New Account', 5, request, username=u.username,
|
||||
signature='AUTHNOTICE',
|
||||
msg='User created a new account')
|
||||
|
||||
u.email_confirmation_code()
|
||||
|
||||
|
@ -625,6 +645,10 @@ def password_reset_confirm(request, uidb36=None, token=None):
|
|||
form = forms.SetPasswordForm(user, request.POST)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
log_cef('Password Changed', 5, request,
|
||||
username=user.username,
|
||||
signature='PASSWORDCHANGED',
|
||||
msg='User changed password')
|
||||
return redirect(reverse('django.contrib.auth.'
|
||||
'views.password_reset_complete'))
|
||||
else:
|
||||
|
|
|
@ -7,8 +7,6 @@ from django.conf import settings
|
|||
import commonware.log
|
||||
import dictconfig
|
||||
|
||||
from cef import cef
|
||||
|
||||
|
||||
class NullHandler(logging.Handler):
|
||||
|
||||
|
@ -64,11 +62,6 @@ cfg = {
|
|||
'format': ('%s: [%%(USERNAME)s][%%(REMOTE_ADDR)s] %s'
|
||||
% (settings.SYSLOG_TAG2, base_fmt)),
|
||||
},
|
||||
'csp': {
|
||||
'()': cef.SysLogFormatter,
|
||||
'datefmt': '%H:%M:%S',
|
||||
'format': '%s: %s' % (settings.SYSLOG_CSP, base_fmt),
|
||||
},
|
||||
},
|
||||
'handlers': {
|
||||
'console': {
|
||||
|
@ -85,11 +78,6 @@ cfg = {
|
|||
'facility': logging.handlers.SysLogHandler.LOG_LOCAL7,
|
||||
'formatter': 'prod2',
|
||||
},
|
||||
'syslog_csp': {
|
||||
'()': UnicodeLogger,
|
||||
'facility': logging.handlers.SysLogHandler.LOG_LOCAL5,
|
||||
'formatter': 'csp',
|
||||
},
|
||||
'null': {
|
||||
'()': NullHandler,
|
||||
},
|
||||
|
@ -118,7 +106,6 @@ USE_SYSLOG = settings.HAS_SYSLOG and not settings.DEBUG
|
|||
|
||||
if USE_SYSLOG:
|
||||
cfg['loggers']['z.timer'] = {'handlers': ['syslog2']}
|
||||
cfg['loggers']['z.csp'] = {'handlers': ['syslog_csp'], 'level':'WARNING'}
|
||||
|
||||
# Set the level and handlers for all loggers.
|
||||
for logger in cfg['loggers'].values() + [cfg['root']]:
|
||||
|
|
|
@ -1030,7 +1030,6 @@ LOG_LEVEL = logging.DEBUG
|
|||
HAS_SYSLOG = True # syslog is used if HAS_SYSLOG and NOT DEBUG.
|
||||
SYSLOG_TAG = "http_app_addons"
|
||||
SYSLOG_TAG2 = "http_app_addons2"
|
||||
SYSLOG_CSP = "http_app_addons_csp"
|
||||
# See PEP 391 and log_settings.py for formatting help. Each section of
|
||||
# LOGGING will get merged into the corresponding section of
|
||||
# log_settings.py. Handlers and log levels are set up automatically based
|
||||
|
@ -1052,6 +1051,8 @@ LOGGING = {
|
|||
},
|
||||
}
|
||||
|
||||
CEF_PRODUCT = "amo"
|
||||
|
||||
# CSP Settings
|
||||
CSP_REPORT_URI = '/services/csp/report'
|
||||
CSP_POLICY_URI = '/services/csp/policy?build=%s' % build_id
|
||||
|
|
Загрузка…
Ссылка в новой задаче