submit blocks to Kinto blocklist bucket (v2) (#13602)
* submit blocks to Kinto blocklist bucket (v2) * add is_imported_from_kinto_regex test * change KINTO_API_IS_TEST_SERVER default to False
This commit is contained in:
Родитель
a81ca7277e
Коммит
068d33509f
|
@ -19,6 +19,7 @@ INSTALLED_APPS += (
|
||||||
'debug_toolbar',
|
'debug_toolbar',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# django-debug-doolbar middleware needs to be inserted as high as possible
|
# django-debug-doolbar middleware needs to be inserted as high as possible
|
||||||
# but after GZip middleware
|
# but after GZip middleware
|
||||||
def insert_debug_toolbar_middleware(middlewares):
|
def insert_debug_toolbar_middleware(middlewares):
|
||||||
|
@ -32,6 +33,7 @@ def insert_debug_toolbar_middleware(middlewares):
|
||||||
|
|
||||||
return tuple(ret_middleware)
|
return tuple(ret_middleware)
|
||||||
|
|
||||||
|
|
||||||
MIDDLEWARE = insert_debug_toolbar_middleware(MIDDLEWARE)
|
MIDDLEWARE = insert_debug_toolbar_middleware(MIDDLEWARE)
|
||||||
|
|
||||||
DEBUG_TOOLBAR_CONFIG = {
|
DEBUG_TOOLBAR_CONFIG = {
|
||||||
|
@ -65,7 +67,8 @@ DOMAIN = SERVICES_DOMAIN = urlparse(SITE_URL).netloc
|
||||||
SERVICES_URL = SITE_URL
|
SERVICES_URL = SITE_URL
|
||||||
EXTERNAL_SITE_URL = SITE_URL
|
EXTERNAL_SITE_URL = SITE_URL
|
||||||
|
|
||||||
CODE_MANAGER_URL = os.environ.get('CODE_MANAGER_URL') or 'http://localhost:3000'
|
CODE_MANAGER_URL = (
|
||||||
|
os.environ.get('CODE_MANAGER_URL') or 'http://localhost:3000')
|
||||||
|
|
||||||
ALLOWED_HOSTS = ALLOWED_HOSTS + [SERVICES_DOMAIN]
|
ALLOWED_HOSTS = ALLOWED_HOSTS + [SERVICES_DOMAIN]
|
||||||
|
|
||||||
|
@ -104,7 +107,7 @@ FXA_CONFIG = {
|
||||||
'client_secret': env(
|
'client_secret': env(
|
||||||
'FXA_CLIENT_SECRET',
|
'FXA_CLIENT_SECRET',
|
||||||
default='d7d5f1148a35b12c067fb9eafafc29d35165a90f5d8b0032f1fcd37468ae49fe'), # noqa
|
default='d7d5f1148a35b12c067fb9eafafc29d35165a90f5d8b0032f1fcd37468ae49fe'), # noqa
|
||||||
# fxa redirects to http://localhost:3000/api/auth/authenticate-callback/?config=local #noqa
|
# noqa fxa redirects to http://localhost:3000/api/auth/authenticate-callback/?config=local #noqa
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
FXA_CONTENT_HOST = 'https://stable.dev.lcip.org'
|
FXA_CONTENT_HOST = 'https://stable.dev.lcip.org'
|
||||||
|
@ -132,6 +135,8 @@ CUSTOMS_API_KEY = 'customssecret'
|
||||||
WAT_API_URL = 'http://wat:10102/'
|
WAT_API_URL = 'http://wat:10102/'
|
||||||
WAT_API_KEY = 'watsecret'
|
WAT_API_KEY = 'watsecret'
|
||||||
|
|
||||||
|
KINTO_API_IS_TEST_SERVER = True
|
||||||
|
|
||||||
# If you have settings you want to overload, put them in a local_settings.py.
|
# If you have settings you want to overload, put them in a local_settings.py.
|
||||||
try:
|
try:
|
||||||
from local_settings import * # noqa
|
from local_settings import * # noqa
|
||||||
|
|
|
@ -7,6 +7,7 @@ from django.db import models
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
|
|
||||||
|
import waffle
|
||||||
from django_extensions.db.fields.json import JSONField
|
from django_extensions.db.fields.json import JSONField
|
||||||
|
|
||||||
from olympia import amo
|
from olympia import amo
|
||||||
|
@ -21,7 +22,8 @@ from olympia.versions.compare import addon_version_int
|
||||||
from olympia.versions.models import Version
|
from olympia.versions.models import Version
|
||||||
|
|
||||||
from .utils import (
|
from .utils import (
|
||||||
block_activity_log_save, block_activity_log_delete, splitlines)
|
block_activity_log_save, block_activity_log_delete, legacy_delete_blocks,
|
||||||
|
legacy_publish_blocks, splitlines)
|
||||||
|
|
||||||
|
|
||||||
class Block(ModelBase):
|
class Block(ModelBase):
|
||||||
|
@ -61,6 +63,10 @@ class Block(ModelBase):
|
||||||
assert self.updated_by
|
assert self.updated_by
|
||||||
return super().save(**kwargs)
|
return super().save(**kwargs)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_imported_from_kinto_regex(self):
|
||||||
|
return self.kinto_id.startswith('*')
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def addon(self):
|
def addon(self):
|
||||||
return Addon.unfiltered.filter(
|
return Addon.unfiltered.filter(
|
||||||
|
@ -399,6 +405,8 @@ class BlockSubmission(ModelBase):
|
||||||
|
|
||||||
modified_datetime = datetime.datetime.now()
|
modified_datetime = datetime.datetime.now()
|
||||||
all_guids_to_block = [block['guid'] for block in self.to_block]
|
all_guids_to_block = [block['guid'] for block in self.to_block]
|
||||||
|
kinto_submit_legacy_switch = waffle.switch_is_active(
|
||||||
|
'blocklist_legacy_submit')
|
||||||
for guids_chunk in chunked(all_guids_to_block, 100):
|
for guids_chunk in chunked(all_guids_to_block, 100):
|
||||||
blocks = self._get_block_instances_to_save(guids_chunk)
|
blocks = self._get_block_instances_to_save(guids_chunk)
|
||||||
Block.preload_addon_versions(blocks)
|
Block.preload_addon_versions(blocks)
|
||||||
|
@ -412,6 +420,8 @@ class BlockSubmission(ModelBase):
|
||||||
block.submission.add(self)
|
block.submission.add(self)
|
||||||
block_activity_log_save(
|
block_activity_log_save(
|
||||||
block, change=change, submission_obj=self)
|
block, change=change, submission_obj=self)
|
||||||
|
if kinto_submit_legacy_switch:
|
||||||
|
legacy_publish_blocks(blocks)
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
self.update(signoff_state=self.SIGNOFF_PUBLISHED)
|
self.update(signoff_state=self.SIGNOFF_PUBLISHED)
|
||||||
|
@ -420,13 +430,17 @@ class BlockSubmission(ModelBase):
|
||||||
assert self.is_submission_ready
|
assert self.is_submission_ready
|
||||||
assert self.action == self.ACTION_DELETE
|
assert self.action == self.ACTION_DELETE
|
||||||
block_ids_to_delete = [block['id'] for block in self.to_block]
|
block_ids_to_delete = [block['id'] for block in self.to_block]
|
||||||
|
kinto_submit_legacy_switch = waffle.switch_is_active(
|
||||||
|
'blocklist_legacy_submit')
|
||||||
for ids_chunk in chunked(block_ids_to_delete, 100):
|
for ids_chunk in chunked(block_ids_to_delete, 100):
|
||||||
blocks = Block.objects.filter(id__in=ids_chunk)
|
blocks = list(Block.objects.filter(id__in=ids_chunk))
|
||||||
Block.preload_addon_versions(blocks)
|
Block.preload_addon_versions(blocks)
|
||||||
for block in blocks:
|
for block in blocks:
|
||||||
block_activity_log_delete(block, submission_obj=self)
|
block_activity_log_delete(block, submission_obj=self)
|
||||||
|
if kinto_submit_legacy_switch:
|
||||||
|
legacy_delete_blocks(blocks)
|
||||||
self.save()
|
self.save()
|
||||||
blocks.delete()
|
Block.objects.filter(id__in=ids_chunk).delete()
|
||||||
|
|
||||||
self.update(signoff_state=self.SIGNOFF_PUBLISHED)
|
self.update(signoff_state=self.SIGNOFF_PUBLISHED)
|
||||||
|
|
||||||
|
|
|
@ -885,7 +885,9 @@ class TestBlockSubmissionAdmin(TestCase):
|
||||||
assert Block.objects.count() == 0
|
assert Block.objects.count() == 0
|
||||||
assert LogEntry.objects.count() == 0
|
assert LogEntry.objects.count() == 0
|
||||||
|
|
||||||
def test_signoff_approve(self):
|
@override_switch('blocklist_legacy_submit', active=True)
|
||||||
|
@mock.patch('olympia.blocklist.models.legacy_publish_blocks')
|
||||||
|
def test_signoff_approve(self, legacy_publish_blocks_mock):
|
||||||
addon = addon_factory(guid='guid@', name='Danger Danger')
|
addon = addon_factory(guid='guid@', name='Danger Danger')
|
||||||
mbs = BlockSubmission.objects.create(
|
mbs = BlockSubmission.objects.create(
|
||||||
input_guids='guid@\ninvalid@',
|
input_guids='guid@\ninvalid@',
|
||||||
|
@ -947,6 +949,10 @@ class TestBlockSubmissionAdmin(TestCase):
|
||||||
assert signoff_log.arguments == [addon, addon.guid, 'add', new_block]
|
assert signoff_log.arguments == [addon, addon.guid, 'add', new_block]
|
||||||
assert signoff_log.user == user
|
assert signoff_log.user == user
|
||||||
|
|
||||||
|
# blocks would have been submitted to kinto legacy collection
|
||||||
|
legacy_publish_blocks_mock.assert_called()
|
||||||
|
legacy_publish_blocks_mock.assert_called_with([new_block])
|
||||||
|
|
||||||
assert mbs.to_block == [
|
assert mbs.to_block == [
|
||||||
{'guid': 'guid@',
|
{'guid': 'guid@',
|
||||||
'id': None,
|
'id': None,
|
||||||
|
@ -968,7 +974,9 @@ class TestBlockSubmissionAdmin(TestCase):
|
||||||
response.content)
|
response.content)
|
||||||
assert b'not a Block!' not in response.content
|
assert b'not a Block!' not in response.content
|
||||||
|
|
||||||
def test_signoff_reject(self):
|
@override_switch('blocklist_legacy_submit', active=True)
|
||||||
|
@mock.patch('olympia.blocklist.models.legacy_publish_blocks')
|
||||||
|
def test_signoff_reject(self, legacy_publish_blocks_mock):
|
||||||
addon = addon_factory(guid='guid@', name='Danger Danger')
|
addon = addon_factory(guid='guid@', name='Danger Danger')
|
||||||
mbs = BlockSubmission.objects.create(
|
mbs = BlockSubmission.objects.create(
|
||||||
input_guids='guid@\ninvalid@',
|
input_guids='guid@\ninvalid@',
|
||||||
|
@ -1004,6 +1012,9 @@ class TestBlockSubmissionAdmin(TestCase):
|
||||||
assert mbs.url != 'new.url'
|
assert mbs.url != 'new.url'
|
||||||
assert mbs.reason != 'a reason'
|
assert mbs.reason != 'a reason'
|
||||||
|
|
||||||
|
# blocks would not have been submitted to kinto legacy collection
|
||||||
|
legacy_publish_blocks_mock.assert_not_called()
|
||||||
|
|
||||||
# And the blocksubmission was rejected, so no Blocks created
|
# And the blocksubmission was rejected, so no Blocks created
|
||||||
assert mbs.signoff_state == BlockSubmission.SIGNOFF_REJECTED
|
assert mbs.signoff_state == BlockSubmission.SIGNOFF_REJECTED
|
||||||
assert Block.objects.count() == 0
|
assert Block.objects.count() == 0
|
||||||
|
@ -1548,7 +1559,9 @@ class TestBlockAdminBulkDelete(TestCase):
|
||||||
]
|
]
|
||||||
assert not submission.block_set.all().exists()
|
assert not submission.block_set.all().exists()
|
||||||
|
|
||||||
def test_submit_no_dual_signoff(self):
|
@override_switch('blocklist_legacy_submit', active=True)
|
||||||
|
@mock.patch('olympia.blocklist.models.legacy_delete_blocks')
|
||||||
|
def test_submit_no_dual_signoff(self, legacy_delete_blocks_mock):
|
||||||
addon_adu = settings.DUAL_SIGNOFF_AVERAGE_DAILY_USERS_THRESHOLD
|
addon_adu = settings.DUAL_SIGNOFF_AVERAGE_DAILY_USERS_THRESHOLD
|
||||||
block_with_addon, block_no_addon = self._test_delete_multiple_submit(
|
block_with_addon, block_no_addon = self._test_delete_multiple_submit(
|
||||||
addon_adu=addon_adu)
|
addon_adu=addon_adu)
|
||||||
|
@ -1556,8 +1569,12 @@ class TestBlockAdminBulkDelete(TestCase):
|
||||||
block_with_addon,
|
block_with_addon,
|
||||||
block_no_addon,
|
block_no_addon,
|
||||||
has_signoff=False)
|
has_signoff=False)
|
||||||
|
legacy_delete_blocks_mock.assert_called_with(
|
||||||
|
[block_with_addon, block_no_addon])
|
||||||
|
|
||||||
def test_submit_dual_signoff(self):
|
@override_switch('blocklist_legacy_submit', active=True)
|
||||||
|
@mock.patch('olympia.blocklist.models.legacy_delete_blocks')
|
||||||
|
def test_submit_dual_signoff(self, legacy_delete_blocks_mock):
|
||||||
addon_adu = settings.DUAL_SIGNOFF_AVERAGE_DAILY_USERS_THRESHOLD + 1
|
addon_adu = settings.DUAL_SIGNOFF_AVERAGE_DAILY_USERS_THRESHOLD + 1
|
||||||
block_with_addon, block_no_addon = self._test_delete_multiple_submit(
|
block_with_addon, block_no_addon = self._test_delete_multiple_submit(
|
||||||
addon_adu=addon_adu)
|
addon_adu=addon_adu)
|
||||||
|
@ -1574,6 +1591,8 @@ class TestBlockAdminBulkDelete(TestCase):
|
||||||
block_with_addon,
|
block_with_addon,
|
||||||
block_no_addon,
|
block_no_addon,
|
||||||
has_signoff=True)
|
has_signoff=True)
|
||||||
|
legacy_delete_blocks_mock.assert_called_with(
|
||||||
|
[block_with_addon, block_no_addon])
|
||||||
|
|
||||||
def test_edit_with_delete_submission(self):
|
def test_edit_with_delete_submission(self):
|
||||||
threshold = settings.DUAL_SIGNOFF_AVERAGE_DAILY_USERS_THRESHOLD
|
threshold = settings.DUAL_SIGNOFF_AVERAGE_DAILY_USERS_THRESHOLD
|
||||||
|
|
|
@ -75,6 +75,7 @@ class TestImportBlocklist(TestCase):
|
||||||
assert block.kinto_id == '*' + this_block['id']
|
assert block.kinto_id == '*' + this_block['id']
|
||||||
assert block.include_in_legacy
|
assert block.include_in_legacy
|
||||||
assert block.modified == datetime(2019, 11, 29, 22, 22, 46, 785000)
|
assert block.modified == datetime(2019, 11, 29, 22, 22, 46, 785000)
|
||||||
|
assert block.is_imported_from_kinto_regex
|
||||||
assert KintoImport.objects.count() == 6
|
assert KintoImport.objects.count() == 6
|
||||||
assert KintoImport.objects.filter(
|
assert KintoImport.objects.filter(
|
||||||
outcome=KintoImport.OUTCOME_NOMATCH).count() == 4
|
outcome=KintoImport.OUTCOME_NOMATCH).count() == 4
|
||||||
|
@ -101,6 +102,7 @@ class TestImportBlocklist(TestCase):
|
||||||
assert blocks[0].kinto_id == blocklist_json['data'][1]['id']
|
assert blocks[0].kinto_id == blocklist_json['data'][1]['id']
|
||||||
assert blocks[0].include_in_legacy
|
assert blocks[0].include_in_legacy
|
||||||
assert blocks[0].modified == datetime(2019, 11, 29, 15, 32, 56, 477000)
|
assert blocks[0].modified == datetime(2019, 11, 29, 15, 32, 56, 477000)
|
||||||
|
assert not blocks[0].is_imported_from_kinto_regex
|
||||||
|
|
||||||
assert blocks[1].guid == 'Ytarkovpn.5.14@firefox.com'
|
assert blocks[1].guid == 'Ytarkovpn.5.14@firefox.com'
|
||||||
assert blocks[1].url == blocklist_json['data'][2]['details']['bug']
|
assert blocks[1].url == blocklist_json['data'][2]['details']['bug']
|
||||||
|
@ -112,6 +114,8 @@ class TestImportBlocklist(TestCase):
|
||||||
assert blocks[1].kinto_id == blocklist_json['data'][2]['id']
|
assert blocks[1].kinto_id == blocklist_json['data'][2]['id']
|
||||||
assert blocks[1].include_in_legacy
|
assert blocks[1].include_in_legacy
|
||||||
assert blocks[1].modified == datetime(2019, 11, 22, 16, 49, 58, 416000)
|
assert blocks[1].modified == datetime(2019, 11, 22, 16, 49, 58, 416000)
|
||||||
|
assert not blocks[1].is_imported_from_kinto_regex
|
||||||
|
|
||||||
assert KintoImport.objects.count() == 6
|
assert KintoImport.objects.count() == 6
|
||||||
assert KintoImport.objects.filter(
|
assert KintoImport.objects.filter(
|
||||||
outcome=KintoImport.OUTCOME_NOMATCH).count() == 3
|
outcome=KintoImport.OUTCOME_NOMATCH).count() == 3
|
||||||
|
|
|
@ -31,6 +31,17 @@ class TestBlock(TestCase):
|
||||||
assert block.is_version_blocked('10.1')
|
assert block.is_version_blocked('10.1')
|
||||||
assert block.is_version_blocked('10.%s' % (MAX_VERSION_PART + 1))
|
assert block.is_version_blocked('10.%s' % (MAX_VERSION_PART + 1))
|
||||||
|
|
||||||
|
def test_is_imported_from_kinto_regex(self):
|
||||||
|
block = Block.objects.create(guid='foo@baa', updated_by=user_factory())
|
||||||
|
# no kinto_id
|
||||||
|
assert not block.is_imported_from_kinto_regex
|
||||||
|
# from a regex kinto_id
|
||||||
|
block.update(kinto_id='*123456789')
|
||||||
|
assert block.is_imported_from_kinto_regex
|
||||||
|
# and a normal one
|
||||||
|
block.update(kinto_id='1234567890')
|
||||||
|
assert not block.is_imported_from_kinto_regex
|
||||||
|
|
||||||
|
|
||||||
class TestMultiBlockSubmission(TestCase):
|
class TestMultiBlockSubmission(TestCase):
|
||||||
def test_is_submission_ready(self):
|
def test_is_submission_ready(self):
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from olympia.amo.tests import user_factory
|
||||||
|
from olympia.blocklist.models import Block
|
||||||
|
from olympia.blocklist.utils import legacy_delete_blocks, legacy_publish_blocks
|
||||||
|
from olympia.lib.kinto import KintoServer
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
@mock.patch.object(KintoServer, 'publish_record')
|
||||||
|
@mock.patch.object(KintoServer, 'delete_record')
|
||||||
|
def test_legacy_publish_blocks(delete_mock, publish_mock):
|
||||||
|
publish_mock.return_value = {'id': 'a-kinto-id'}
|
||||||
|
block_new = Block.objects.create(
|
||||||
|
guid='new@guid', include_in_legacy=True, updated_by=user_factory())
|
||||||
|
block_regex = Block.objects.create(
|
||||||
|
guid='regex@guid', include_in_legacy=True, updated_by=user_factory(),
|
||||||
|
kinto_id='*regex')
|
||||||
|
block_legacy_dropped = Block.objects.create(
|
||||||
|
guid='drop@guid', include_in_legacy=False, updated_by=user_factory(),
|
||||||
|
kinto_id='dropped_legacy')
|
||||||
|
block_never_legacy = Block.objects.create(
|
||||||
|
guid='never@guid', include_in_legacy=False, updated_by=user_factory())
|
||||||
|
block_update = Block.objects.create(
|
||||||
|
guid='update@guid', include_in_legacy=True, updated_by=user_factory(),
|
||||||
|
kinto_id='update')
|
||||||
|
# Currently we don't ever mix include_in_legacy=True and False together,
|
||||||
|
# but the function should handle it.
|
||||||
|
data = {
|
||||||
|
'details': {
|
||||||
|
'bug': '',
|
||||||
|
'why': '',
|
||||||
|
'name': ''
|
||||||
|
},
|
||||||
|
'enabled': True,
|
||||||
|
'versionRange': [{
|
||||||
|
'severity': 3,
|
||||||
|
'minVersion': '0',
|
||||||
|
'maxVersion': '*',
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
|
||||||
|
legacy_publish_blocks(
|
||||||
|
[block_new, block_regex, block_legacy_dropped, block_never_legacy,
|
||||||
|
block_update])
|
||||||
|
assert publish_mock.call_args_list == [
|
||||||
|
mock.call(dict(guid='new@guid', **data)),
|
||||||
|
mock.call(dict(guid='regex@guid', **data)),
|
||||||
|
mock.call(dict(guid='update@guid', **data), 'update')]
|
||||||
|
assert delete_mock.call_args_list == [
|
||||||
|
mock.call('dropped_legacy')]
|
||||||
|
assert block_new.kinto_id == 'a-kinto-id'
|
||||||
|
assert block_regex.kinto_id == 'a-kinto-id' # it'd be unique if not mocked
|
||||||
|
assert block_legacy_dropped.kinto_id == ''
|
||||||
|
assert block_update.kinto_id == 'update' # it's not changed
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
@mock.patch.object(KintoServer, 'delete_record')
|
||||||
|
def test_legacy_delete_blocks(delete_record_mock):
|
||||||
|
block = Block.objects.create(
|
||||||
|
guid='legacy@guid', include_in_legacy=True, updated_by=user_factory(),
|
||||||
|
kinto_id='legacy')
|
||||||
|
block_regex = Block.objects.create(
|
||||||
|
guid='regex@guid', include_in_legacy=True, updated_by=user_factory(),
|
||||||
|
kinto_id='*regex')
|
||||||
|
block_not_legacy = Block.objects.create(
|
||||||
|
guid='not@guid', include_in_legacy=False, updated_by=user_factory(),
|
||||||
|
kinto_id='not_legacy')
|
||||||
|
block_not_imported = Block.objects.create(
|
||||||
|
guid='new@guid', include_in_legacy=True, updated_by=user_factory())
|
||||||
|
|
||||||
|
legacy_delete_blocks(
|
||||||
|
[block, block_regex, block_not_legacy, block_not_imported])
|
||||||
|
assert delete_record_mock.call_args_list == [mock.call('legacy')]
|
||||||
|
assert block.kinto_id == ''
|
|
@ -5,10 +5,14 @@ from filtercascade import FilterCascade
|
||||||
import olympia.core.logger
|
import olympia.core.logger
|
||||||
from olympia import amo
|
from olympia import amo
|
||||||
from olympia.activity import log_create
|
from olympia.activity import log_create
|
||||||
|
from olympia.lib.kinto import KintoServer
|
||||||
|
|
||||||
|
|
||||||
log = olympia.core.logger.getLogger('z.amo.blocklist')
|
log = olympia.core.logger.getLogger('z.amo.blocklist')
|
||||||
|
|
||||||
|
KINTO_BUCKET = 'staging'
|
||||||
|
KINTO_COLLECTION_LEGACY = 'addons'
|
||||||
|
|
||||||
|
|
||||||
def add_version_log_for_blocked_versions(obj, al):
|
def add_version_log_for_blocked_versions(obj, al):
|
||||||
from olympia.activity.models import VersionLog
|
from olympia.activity.models import VersionLog
|
||||||
|
@ -116,3 +120,57 @@ def generateMLBF(stats, *, blocked, not_blocked, capacity, diffMetaFile=None):
|
||||||
log.debug("Filter cascade layers: {layers}, bit: {bits}".format(
|
log.debug("Filter cascade layers: {layers}, bit: {bits}".format(
|
||||||
layers=cascade.layerCount(), bits=cascade.bitCount()))
|
layers=cascade.layerCount(), bits=cascade.bitCount()))
|
||||||
return cascade
|
return cascade
|
||||||
|
|
||||||
|
|
||||||
|
def legacy_publish_blocks(blocks):
|
||||||
|
server = KintoServer(KINTO_BUCKET, KINTO_COLLECTION_LEGACY)
|
||||||
|
for block in blocks:
|
||||||
|
needs_updating = block.include_in_legacy and block.kinto_id
|
||||||
|
needs_creating = block.include_in_legacy and not needs_updating
|
||||||
|
needs_deleting = block.kinto_id and not block.include_in_legacy
|
||||||
|
|
||||||
|
if needs_updating or needs_creating:
|
||||||
|
if block.is_imported_from_kinto_regex:
|
||||||
|
log.debug(
|
||||||
|
f'Block [{block.guid}] was imported from a regex guid so '
|
||||||
|
'can\'t be safely updated. Creating as a new Block '
|
||||||
|
'instead.')
|
||||||
|
needs_creating = True
|
||||||
|
data = {
|
||||||
|
'guid': block.guid,
|
||||||
|
'details': {
|
||||||
|
'bug': block.url,
|
||||||
|
'why': block.reason,
|
||||||
|
'name': str(block.reason).partition('.')[0], # required
|
||||||
|
},
|
||||||
|
'enabled': True,
|
||||||
|
'versionRange': [{
|
||||||
|
'severity': 3, # Always high severity now.
|
||||||
|
'minVersion': block.min_version,
|
||||||
|
'maxVersion': block.max_version,
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
if needs_creating:
|
||||||
|
record = server.publish_record(data)
|
||||||
|
block.update(kinto_id=record.get('id', ''))
|
||||||
|
else:
|
||||||
|
server.publish_record(data, block.kinto_id)
|
||||||
|
elif needs_deleting:
|
||||||
|
server.delete_record(block.kinto_id)
|
||||||
|
block.update(kinto_id='')
|
||||||
|
# else no existing kinto record and it shouldn't be in legacy so skip
|
||||||
|
server.signoff_request()
|
||||||
|
|
||||||
|
|
||||||
|
def legacy_delete_blocks(blocks):
|
||||||
|
server = KintoServer(KINTO_BUCKET, KINTO_COLLECTION_LEGACY)
|
||||||
|
for block in blocks:
|
||||||
|
if block.kinto_id and block.include_in_legacy:
|
||||||
|
if block.is_imported_from_kinto_regex:
|
||||||
|
log.debug(
|
||||||
|
f'Block [{block.guid}] was imported from a regex guid so '
|
||||||
|
'can\'t be safely deleted. Skipping.')
|
||||||
|
else:
|
||||||
|
server.delete_record(block.kinto_id)
|
||||||
|
block.update(kinto_id='')
|
||||||
|
server.signoff_request()
|
||||||
|
|
|
@ -124,3 +124,5 @@ FXA_SQS_AWS_QUEUE_URL = (
|
||||||
'amo-account-change-dev')
|
'amo-account-change-dev')
|
||||||
|
|
||||||
VAMO_URL = 'https://versioncheck-dev.allizom.org'
|
VAMO_URL = 'https://versioncheck-dev.allizom.org'
|
||||||
|
|
||||||
|
KINTO_API_IS_TEST_SERVER = True
|
||||||
|
|
|
@ -108,3 +108,5 @@ FXA_SQS_AWS_QUEUE_URL = (
|
||||||
|
|
||||||
EXTENSION_WORKSHOP_URL = env(
|
EXTENSION_WORKSHOP_URL = env(
|
||||||
'EXTENSION_WORKSHOP_URL', default='https://extensionworkshop.com')
|
'EXTENSION_WORKSHOP_URL', default='https://extensionworkshop.com')
|
||||||
|
|
||||||
|
KINTO_API_URL = 'http://settings-writer.prod.mozaws.net/v1/'
|
||||||
|
|
|
@ -123,3 +123,5 @@ VAMO_URL = 'https://versioncheck.allizom.org'
|
||||||
|
|
||||||
EXTENSION_WORKSHOP_URL = env(
|
EXTENSION_WORKSHOP_URL = env(
|
||||||
'EXTENSION_WORKSHOP_URL', default='https://extensionworkshop.allizom.org')
|
'EXTENSION_WORKSHOP_URL', default='https://extensionworkshop.allizom.org')
|
||||||
|
|
||||||
|
KINTO_API_URL = 'https://settings-writer.stage.mozaws.net/v1/'
|
||||||
|
|
|
@ -0,0 +1,138 @@
|
||||||
|
from base64 import b64encode
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
import olympia.core.logger
|
||||||
|
|
||||||
|
|
||||||
|
log = olympia.core.logger.getLogger('lib.kinto')
|
||||||
|
|
||||||
|
|
||||||
|
class KintoServer(object):
|
||||||
|
username = None
|
||||||
|
password = None
|
||||||
|
bucket = None
|
||||||
|
collection = None
|
||||||
|
_setup_done = False
|
||||||
|
_needs_signoff = False
|
||||||
|
|
||||||
|
def __init__(self, bucket, collection):
|
||||||
|
self.username = settings.BLOCKLIST_KINTO_USERNAME
|
||||||
|
self.password = settings.BLOCKLIST_KINTO_PASSWORD
|
||||||
|
self.bucket = bucket
|
||||||
|
self.collection = collection
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
if self._setup_done:
|
||||||
|
return
|
||||||
|
if settings.KINTO_API_IS_TEST_SERVER:
|
||||||
|
self.setup_test_server_auth()
|
||||||
|
self.bucket = f'{self.bucket}_{self.username}'
|
||||||
|
self.setup_test_server_collection()
|
||||||
|
self._setup_done = True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def headers(self):
|
||||||
|
b64 = b64encode(f'{self.username}:{self.password}'.encode()).decode()
|
||||||
|
return {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': f'Basic {b64}'}
|
||||||
|
|
||||||
|
def setup_test_server_auth(self):
|
||||||
|
# check if the user already exists in kinto's accounts
|
||||||
|
host = settings.KINTO_API_URL
|
||||||
|
response = requests.get(host, headers=self.headers)
|
||||||
|
user_id = response.json().get('user', {}).get('id')
|
||||||
|
if user_id != f'account:{self.username}':
|
||||||
|
# lets create it
|
||||||
|
log.info('Creating kinto test account for %s' % self.username)
|
||||||
|
response = requests.put(
|
||||||
|
f'{host}accounts/{self.username}',
|
||||||
|
json={'data': {'password': self.password}},
|
||||||
|
headers={'Content-Type': 'application/json'})
|
||||||
|
if response.status_code != 201:
|
||||||
|
log.error(
|
||||||
|
'Creating kinto test account for %s failed. [%s]' %
|
||||||
|
(self.username, response.content),
|
||||||
|
stack_info=True)
|
||||||
|
raise ConnectionError('Kinto account not created')
|
||||||
|
|
||||||
|
def setup_test_server_collection(self):
|
||||||
|
# check if the bucket and collection exist
|
||||||
|
host = settings.KINTO_API_URL
|
||||||
|
url = (
|
||||||
|
f'{host}buckets/{self.bucket}/'
|
||||||
|
f'collections/{self.collection}/records')
|
||||||
|
headers = self.headers
|
||||||
|
response = requests.get(url, headers=headers)
|
||||||
|
if response.status_code == 403:
|
||||||
|
# lets create them
|
||||||
|
data = {'permissions': {'read': ["system.Everyone"]}}
|
||||||
|
log.info(
|
||||||
|
'Creating kinto bucket %s and collection %s' %
|
||||||
|
(self.bucket, self.collection))
|
||||||
|
response = requests.put(
|
||||||
|
f'{host}buckets/{self.bucket}',
|
||||||
|
json=data,
|
||||||
|
headers=headers)
|
||||||
|
response = requests.put(
|
||||||
|
f'{host}buckets/{self.bucket}/collections/{self.collection}',
|
||||||
|
json=data,
|
||||||
|
headers=headers)
|
||||||
|
|
||||||
|
if response.status_code != 201:
|
||||||
|
log.error(
|
||||||
|
'Creating collection %s/%s failed: %s' %
|
||||||
|
(self.bucket, self.collection, response.content),
|
||||||
|
stack_info=True)
|
||||||
|
raise ConnectionError('Kinto collection not created')
|
||||||
|
|
||||||
|
def publish_record(self, data, kinto_id=None):
|
||||||
|
"""Publish a record to kinto. If `kinto_id` is not None the existing
|
||||||
|
record will be updated (PUT); otherwise a new record will be created
|
||||||
|
(POST)."""
|
||||||
|
self.setup()
|
||||||
|
|
||||||
|
add_url = (
|
||||||
|
f'{settings.KINTO_API_URL}buckets/{self.bucket}/'
|
||||||
|
f'collections/{self.collection}/records')
|
||||||
|
json_data = {'data': data}
|
||||||
|
if not kinto_id:
|
||||||
|
log.info('Creating record for [%s]' % data.get('guid'))
|
||||||
|
response = requests.post(
|
||||||
|
add_url, json=json_data, headers=self.headers)
|
||||||
|
else:
|
||||||
|
log.info(
|
||||||
|
'Updating record [%s] for [%s]' % (kinto_id, data.get('guid')))
|
||||||
|
update_url = f'{add_url}/{kinto_id}'
|
||||||
|
response = requests.put(
|
||||||
|
update_url, json=json_data, headers=self.headers)
|
||||||
|
if response.status_code not in (200, 201):
|
||||||
|
log.error(
|
||||||
|
'Creating record for [%s] failed: %s' %
|
||||||
|
(data.get('guid'), response.content),
|
||||||
|
stack_info=True)
|
||||||
|
raise ConnectionError('Kinto record not created/updated')
|
||||||
|
self._needs_signoff = True
|
||||||
|
return response.json().get('data', {})
|
||||||
|
|
||||||
|
def delete_record(self, kinto_id):
|
||||||
|
self.setup()
|
||||||
|
url = (
|
||||||
|
f'{settings.KINTO_API_URL}buckets/{self.bucket}/'
|
||||||
|
f'collections/{self.collection}/records/{kinto_id}')
|
||||||
|
requests.delete(
|
||||||
|
url, headers=self.headers)
|
||||||
|
self._needs_signoff = True
|
||||||
|
|
||||||
|
def signoff_request(self):
|
||||||
|
if not self._needs_signoff:
|
||||||
|
return
|
||||||
|
self.setup()
|
||||||
|
url = (
|
||||||
|
f'{settings.KINTO_API_URL}buckets/{self.bucket}/'
|
||||||
|
f'collections/{self.collection}')
|
||||||
|
requests.patch(
|
||||||
|
url, json={'data': {'status': 'to-review'}}, headers=self.headers)
|
||||||
|
self._needs_signoff = False
|
|
@ -1924,6 +1924,13 @@ YARA_GIT_REPOSITORY = env('YARA_GIT_REPOSITORY', default=None)
|
||||||
|
|
||||||
# Addon.average_daily_user count that forces dual sign-off for Blocklist Blocks
|
# Addon.average_daily_user count that forces dual sign-off for Blocklist Blocks
|
||||||
DUAL_SIGNOFF_AVERAGE_DAILY_USERS_THRESHOLD = 100_000
|
DUAL_SIGNOFF_AVERAGE_DAILY_USERS_THRESHOLD = 100_000
|
||||||
|
KINTO_API_URL = 'https://kinto.dev.mozaws.net/v1/'
|
||||||
|
# The kinto test server needs accounts and setting up before using.
|
||||||
|
KINTO_API_IS_TEST_SERVER = False
|
||||||
|
BLOCKLIST_KINTO_USERNAME = env(
|
||||||
|
'BLOCKLIST_KINTO_USERNAME', default='amo_dev')
|
||||||
|
BLOCKLIST_KINTO_PASSWORD = env(
|
||||||
|
'BLOCKLIST_KINTO_PASSWORD', default='amo_dev_password')
|
||||||
|
|
||||||
# The path to the current google service account configuration. This is
|
# The path to the current google service account configuration. This is
|
||||||
# being used to query Google BigQuery as part of our stats processing.
|
# being used to query Google BigQuery as part of our stats processing.
|
||||||
|
|
|
@ -0,0 +1,149 @@
|
||||||
|
from django.conf import settings
|
||||||
|
from django.test.utils import override_settings
|
||||||
|
|
||||||
|
import responses
|
||||||
|
|
||||||
|
from olympia.amo.tests import TestCase
|
||||||
|
from olympia.lib.kinto import KintoServer
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
BLOCKLIST_KINTO_USERNAME='test_username',
|
||||||
|
BLOCKLIST_KINTO_PASSWORD='test_password')
|
||||||
|
class TestKintoServer(TestCase):
|
||||||
|
|
||||||
|
def test_setup_test_server_auth(self):
|
||||||
|
server = KintoServer('foo', 'baa')
|
||||||
|
responses.add(
|
||||||
|
responses.GET,
|
||||||
|
settings.KINTO_API_URL,
|
||||||
|
content_type='application/json',
|
||||||
|
json={'user': {'id': ''}})
|
||||||
|
responses.add(
|
||||||
|
responses.PUT,
|
||||||
|
settings.KINTO_API_URL + 'accounts/test_username',
|
||||||
|
content_type='application/json',
|
||||||
|
json={'data': {'password': 'test_password'}},
|
||||||
|
status=201)
|
||||||
|
server.setup_test_server_auth()
|
||||||
|
|
||||||
|
# If repeated then the account should exist the 2nd time
|
||||||
|
responses.add(
|
||||||
|
responses.GET,
|
||||||
|
settings.KINTO_API_URL,
|
||||||
|
content_type='application/json',
|
||||||
|
json={'user': {'id': 'account:test_username'}})
|
||||||
|
server.setup_test_server_auth()
|
||||||
|
|
||||||
|
def test_setup_test_server_collection(self):
|
||||||
|
server = KintoServer('foo', 'baa')
|
||||||
|
responses.add(
|
||||||
|
responses.GET,
|
||||||
|
settings.KINTO_API_URL + 'buckets/foo/collections/baa/records',
|
||||||
|
content_type='application/json',
|
||||||
|
status=403)
|
||||||
|
responses.add(
|
||||||
|
responses.PUT,
|
||||||
|
settings.KINTO_API_URL + 'buckets/foo',
|
||||||
|
content_type='application/json')
|
||||||
|
responses.add(
|
||||||
|
responses.PUT,
|
||||||
|
settings.KINTO_API_URL + 'buckets/foo/collections/baa',
|
||||||
|
content_type='application/json',
|
||||||
|
status=201)
|
||||||
|
server.setup_test_server_collection()
|
||||||
|
|
||||||
|
# If repeated then the collection shouldn't 403 a second time
|
||||||
|
responses.add(
|
||||||
|
responses.GET,
|
||||||
|
settings.KINTO_API_URL + 'buckets/foo/collections/baa/records',
|
||||||
|
content_type='application/json')
|
||||||
|
server.setup_test_server_collection()
|
||||||
|
|
||||||
|
@override_settings(KINTO_API_IS_TEST_SERVER=False)
|
||||||
|
def test_setup_not_test_server(self):
|
||||||
|
server = KintoServer('foo', 'baa')
|
||||||
|
|
||||||
|
server.setup() # will just return
|
||||||
|
assert server._setup_done
|
||||||
|
assert server.bucket == 'foo'
|
||||||
|
|
||||||
|
@override_settings(KINTO_API_IS_TEST_SERVER=True)
|
||||||
|
def test_setup(self):
|
||||||
|
server = KintoServer('foo', 'baa')
|
||||||
|
responses.add(
|
||||||
|
responses.GET,
|
||||||
|
settings.KINTO_API_URL,
|
||||||
|
content_type='application/json',
|
||||||
|
json={'user': {'id': 'account:test_username'}})
|
||||||
|
records_url = (
|
||||||
|
settings.KINTO_API_URL +
|
||||||
|
'buckets/foo_test_username/collections/baa/records')
|
||||||
|
responses.add(
|
||||||
|
responses.GET,
|
||||||
|
records_url,
|
||||||
|
content_type='application/json')
|
||||||
|
|
||||||
|
server.setup()
|
||||||
|
assert server._setup_done
|
||||||
|
assert server.bucket == 'foo_test_username'
|
||||||
|
|
||||||
|
server.setup() # a second time shouldn't make any requests
|
||||||
|
|
||||||
|
def test_publish_record(self):
|
||||||
|
server = KintoServer('foo', 'baa')
|
||||||
|
server._setup_done = True
|
||||||
|
assert not server._needs_signoff
|
||||||
|
responses.add(
|
||||||
|
responses.POST,
|
||||||
|
settings.KINTO_API_URL + 'buckets/foo/collections/baa/records',
|
||||||
|
content_type='application/json',
|
||||||
|
json={'data': {'id': 'new!'}})
|
||||||
|
|
||||||
|
record = server.publish_record({'something': 'somevalue'})
|
||||||
|
assert server._needs_signoff
|
||||||
|
assert record == {'id': 'new!'}
|
||||||
|
|
||||||
|
url = (
|
||||||
|
settings.KINTO_API_URL +
|
||||||
|
'buckets/foo/collections/baa/records/an-id')
|
||||||
|
responses.add(
|
||||||
|
responses.PUT,
|
||||||
|
url,
|
||||||
|
content_type='application/json',
|
||||||
|
json={'data': {'id': 'updated'}})
|
||||||
|
|
||||||
|
record = server.publish_record({'something': 'somevalue'}, 'an-id')
|
||||||
|
assert record == {'id': 'updated'}
|
||||||
|
|
||||||
|
def test_delete_record(self):
|
||||||
|
server = KintoServer('foo', 'baa')
|
||||||
|
server._setup_done = True
|
||||||
|
assert not server._needs_signoff
|
||||||
|
url = (
|
||||||
|
settings.KINTO_API_URL +
|
||||||
|
'buckets/foo/collections/baa/records/an-id')
|
||||||
|
responses.add(
|
||||||
|
responses.DELETE,
|
||||||
|
url,
|
||||||
|
content_type='application/json')
|
||||||
|
|
||||||
|
server.delete_record('an-id')
|
||||||
|
assert server._needs_signoff
|
||||||
|
|
||||||
|
def test_signoff(self):
|
||||||
|
server = KintoServer('foo', 'baa')
|
||||||
|
server._setup_done = True
|
||||||
|
# should return because nothing to signoff
|
||||||
|
server.signoff_request()
|
||||||
|
|
||||||
|
server._needs_signoff = True
|
||||||
|
url = (
|
||||||
|
settings.KINTO_API_URL +
|
||||||
|
'buckets/foo/collections/baa')
|
||||||
|
responses.add(
|
||||||
|
responses.PATCH,
|
||||||
|
url,
|
||||||
|
content_type='application/json')
|
||||||
|
server.signoff_request()
|
||||||
|
assert not server._needs_signoff
|
Загрузка…
Ссылка в новой задаче