This commit is contained in:
Wil Clouser 2011-12-01 13:53:51 -08:00
Родитель 4736c0bf22
Коммит 37d8135ae5
8 изменённых файлов: 95 добавлений и 28 удалений

Просмотреть файл

@ -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