Convert IP addresses to strings in MySQL before calling `GROUP_CONCAT()` (#19786)
* Convert IP addresses to strings in MySQL before calling GROUP_CONCAT() This avoids issues with commas inside the bytestring representation of the binary value, which can happen for some IPs.
This commit is contained in:
Родитель
94d8e57e78
Коммит
92979934a4
|
@ -7,6 +7,8 @@ from urllib.parse import urljoin
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.files.storage import default_storage as storage
|
from django.core.files.storage import default_storage as storage
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.models.expressions import Func
|
||||||
|
from django.db.models.fields import CharField
|
||||||
from django.db.models.fields.related_descriptors import ManyToManyDescriptor
|
from django.db.models.fields.related_descriptors import ManyToManyDescriptor
|
||||||
from django.db.models.query import ModelIterable
|
from django.db.models.query import ModelIterable
|
||||||
from django.urls import resolve, reverse
|
from django.urls import resolve, reverse
|
||||||
|
@ -633,3 +635,8 @@ class FilterableManyToManyField(models.fields.related.ManyToManyField):
|
||||||
class GroupConcat(models.Aggregate):
|
class GroupConcat(models.Aggregate):
|
||||||
function = 'GROUP_CONCAT'
|
function = 'GROUP_CONCAT'
|
||||||
allow_distinct = True
|
allow_distinct = True
|
||||||
|
|
||||||
|
|
||||||
|
class Inet6Ntoa(Func):
|
||||||
|
function = 'INET6_NTOA'
|
||||||
|
output_field = CharField()
|
||||||
|
|
|
@ -6,7 +6,6 @@ from django import http
|
||||||
from django.contrib import admin, messages
|
from django.contrib import admin, messages
|
||||||
from django.contrib.admin.utils import unquote
|
from django.contrib.admin.utils import unquote
|
||||||
from django.db.models import (
|
from django.db.models import (
|
||||||
CharField,
|
|
||||||
Count,
|
Count,
|
||||||
F,
|
F,
|
||||||
FilteredRelation,
|
FilteredRelation,
|
||||||
|
@ -37,7 +36,7 @@ from olympia.amo.admin import (
|
||||||
SEARCH_VAR,
|
SEARCH_VAR,
|
||||||
)
|
)
|
||||||
from olympia.amo.fields import IPAddressBinaryField
|
from olympia.amo.fields import IPAddressBinaryField
|
||||||
from olympia.amo.models import GroupConcat
|
from olympia.amo.models import GroupConcat, Inet6Ntoa
|
||||||
from olympia.api.models import APIKey, APIKeyConfirmation
|
from olympia.api.models import APIKey, APIKeyConfirmation
|
||||||
from olympia.bandwagon.models import Collection
|
from olympia.bandwagon.models import Collection
|
||||||
from olympia.ratings.models import Rating
|
from olympia.ratings.models import Rating
|
||||||
|
@ -242,11 +241,8 @@ class UserAdmin(CommaSearchInAdminMixin, admin.ModelAdmin):
|
||||||
# FilteredRelation we can force it...
|
# FilteredRelation we can force it...
|
||||||
annotations = {
|
annotations = {
|
||||||
'activity_ips': GroupConcat(
|
'activity_ips': GroupConcat(
|
||||||
'activitylog__iplog__ip_address_binary',
|
Inet6Ntoa('activitylog__iplog__ip_address_binary'),
|
||||||
distinct=True,
|
distinct=True,
|
||||||
# You can't have multiple ip addresses in an IPv[4|6]Address object
|
|
||||||
# so we store the binary values in a CharField instead.
|
|
||||||
output_field=CharField(),
|
|
||||||
),
|
),
|
||||||
'activitylog_filtered': FilteredRelation(
|
'activitylog_filtered': FilteredRelation(
|
||||||
'activitylog__iplog', condition=condition
|
'activitylog__iplog', condition=condition
|
||||||
|
@ -482,11 +478,12 @@ class UserAdmin(CommaSearchInAdminMixin, admin.ModelAdmin):
|
||||||
# extra queries for each row of results), otherwise, look everywhere
|
# extra queries for each row of results), otherwise, look everywhere
|
||||||
# we can.
|
# we can.
|
||||||
activity_ips = getattr(obj, 'activity_ips', None)
|
activity_ips = getattr(obj, 'activity_ips', None)
|
||||||
to_ipaddress = IPAddressBinaryField().to_python
|
|
||||||
if activity_ips is not None:
|
if activity_ips is not None:
|
||||||
# The GroupConcat value is a comma seperated string of the binary values.
|
# The GroupConcat value is a comma seperated string of the ip
|
||||||
ip_addresses = {to_ipaddress(ip) for ip in activity_ips.split(b',')}
|
# addresses (already converted to string thanks to INET6_NTOA).
|
||||||
|
ip_addresses = set(activity_ips.split(','))
|
||||||
else:
|
else:
|
||||||
|
to_ipaddress = IPAddressBinaryField().to_python
|
||||||
ip_addresses = set(
|
ip_addresses = set(
|
||||||
Rating.objects.filter(user=obj)
|
Rating.objects.filter(user=obj)
|
||||||
.values_list('ip_address', flat=True)
|
.values_list('ip_address', flat=True)
|
||||||
|
|
|
@ -127,7 +127,11 @@ class TestUserAdmin(TestCase):
|
||||||
self.user.update(email='foo@bar.com')
|
self.user.update(email='foo@bar.com')
|
||||||
# That will make self.user match our query.
|
# That will make self.user match our query.
|
||||||
ActivityLog.create(amo.LOG.LOG_IN, user=self.user)
|
ActivityLog.create(amo.LOG.LOG_IN, user=self.user)
|
||||||
with core.override_remote_addr('127.0.0.3'):
|
# 127.0.0.44 packed is b'\x7f\x00\x00,' - it's useful to test since it
|
||||||
|
# contains a comma character, which would trip the split(',') we do on
|
||||||
|
# the result of the GROUP_CONCAT if the binary value wasn't translated
|
||||||
|
# to an IP address string representation before being grouped.
|
||||||
|
with core.override_remote_addr('127.0.0.44'):
|
||||||
# Add another login from a different IP to make sure we show it.
|
# Add another login from a different IP to make sure we show it.
|
||||||
ActivityLog.create(amo.LOG.LOG_IN, user=self.user)
|
ActivityLog.create(amo.LOG.LOG_IN, user=self.user)
|
||||||
with core.override_remote_addr('127.0.0.1'):
|
with core.override_remote_addr('127.0.0.1'):
|
||||||
|
@ -145,7 +149,7 @@ class TestUserAdmin(TestCase):
|
||||||
# Make sure it's the right user.
|
# Make sure it's the right user.
|
||||||
assert doc('.field-email').text() == self.user.email
|
assert doc('.field-email').text() == self.user.email
|
||||||
# Make sure login ip is now displayed, and has the right value.
|
# Make sure login ip is now displayed, and has the right value.
|
||||||
assert doc('.field-known_ip_adresses').text() == '127.0.0.2\n127.0.0.3'
|
assert doc('.field-known_ip_adresses').text() == '127.0.0.2\n127.0.0.44'
|
||||||
|
|
||||||
def test_search_for_single_ip_multiple_results_for_different_reasons(self):
|
def test_search_for_single_ip_multiple_results_for_different_reasons(self):
|
||||||
user = user_factory(email='someone@mozilla.com')
|
user = user_factory(email='someone@mozilla.com')
|
||||||
|
|
Загрузка…
Ссылка в новой задаче