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',
|
||||
)
|
||||
|
||||
|
||||
# django-debug-doolbar middleware needs to be inserted as high as possible
|
||||
# but after GZip middleware
|
||||
def insert_debug_toolbar_middleware(middlewares):
|
||||
|
@ -32,6 +33,7 @@ def insert_debug_toolbar_middleware(middlewares):
|
|||
|
||||
return tuple(ret_middleware)
|
||||
|
||||
|
||||
MIDDLEWARE = insert_debug_toolbar_middleware(MIDDLEWARE)
|
||||
|
||||
DEBUG_TOOLBAR_CONFIG = {
|
||||
|
@ -65,7 +67,8 @@ DOMAIN = SERVICES_DOMAIN = urlparse(SITE_URL).netloc
|
|||
SERVICES_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]
|
||||
|
||||
|
@ -104,7 +107,7 @@ FXA_CONFIG = {
|
|||
'client_secret': env(
|
||||
'FXA_CLIENT_SECRET',
|
||||
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'
|
||||
|
@ -132,6 +135,8 @@ CUSTOMS_API_KEY = 'customssecret'
|
|||
WAT_API_URL = 'http://wat:10102/'
|
||||
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.
|
||||
try:
|
||||
from local_settings import * # noqa
|
||||
|
|
|
@ -7,6 +7,7 @@ from django.db import models
|
|||
from django.utils.html import format_html
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
import waffle
|
||||
from django_extensions.db.fields.json import JSONField
|
||||
|
||||
from olympia import amo
|
||||
|
@ -21,7 +22,8 @@ from olympia.versions.compare import addon_version_int
|
|||
from olympia.versions.models import Version
|
||||
|
||||
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):
|
||||
|
@ -61,6 +63,10 @@ class Block(ModelBase):
|
|||
assert self.updated_by
|
||||
return super().save(**kwargs)
|
||||
|
||||
@property
|
||||
def is_imported_from_kinto_regex(self):
|
||||
return self.kinto_id.startswith('*')
|
||||
|
||||
@cached_property
|
||||
def addon(self):
|
||||
return Addon.unfiltered.filter(
|
||||
|
@ -399,6 +405,8 @@ class BlockSubmission(ModelBase):
|
|||
|
||||
modified_datetime = datetime.datetime.now()
|
||||
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):
|
||||
blocks = self._get_block_instances_to_save(guids_chunk)
|
||||
Block.preload_addon_versions(blocks)
|
||||
|
@ -412,6 +420,8 @@ class BlockSubmission(ModelBase):
|
|||
block.submission.add(self)
|
||||
block_activity_log_save(
|
||||
block, change=change, submission_obj=self)
|
||||
if kinto_submit_legacy_switch:
|
||||
legacy_publish_blocks(blocks)
|
||||
self.save()
|
||||
|
||||
self.update(signoff_state=self.SIGNOFF_PUBLISHED)
|
||||
|
@ -420,13 +430,17 @@ class BlockSubmission(ModelBase):
|
|||
assert self.is_submission_ready
|
||||
assert self.action == self.ACTION_DELETE
|
||||
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):
|
||||
blocks = Block.objects.filter(id__in=ids_chunk)
|
||||
blocks = list(Block.objects.filter(id__in=ids_chunk))
|
||||
Block.preload_addon_versions(blocks)
|
||||
for block in blocks:
|
||||
block_activity_log_delete(block, submission_obj=self)
|
||||
if kinto_submit_legacy_switch:
|
||||
legacy_delete_blocks(blocks)
|
||||
self.save()
|
||||
blocks.delete()
|
||||
Block.objects.filter(id__in=ids_chunk).delete()
|
||||
|
||||
self.update(signoff_state=self.SIGNOFF_PUBLISHED)
|
||||
|
||||
|
|
|
@ -885,7 +885,9 @@ class TestBlockSubmissionAdmin(TestCase):
|
|||
assert Block.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')
|
||||
mbs = BlockSubmission.objects.create(
|
||||
input_guids='guid@\ninvalid@',
|
||||
|
@ -947,6 +949,10 @@ class TestBlockSubmissionAdmin(TestCase):
|
|||
assert signoff_log.arguments == [addon, addon.guid, 'add', new_block]
|
||||
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 == [
|
||||
{'guid': 'guid@',
|
||||
'id': None,
|
||||
|
@ -968,7 +974,9 @@ class TestBlockSubmissionAdmin(TestCase):
|
|||
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')
|
||||
mbs = BlockSubmission.objects.create(
|
||||
input_guids='guid@\ninvalid@',
|
||||
|
@ -1004,6 +1012,9 @@ class TestBlockSubmissionAdmin(TestCase):
|
|||
assert mbs.url != 'new.url'
|
||||
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
|
||||
assert mbs.signoff_state == BlockSubmission.SIGNOFF_REJECTED
|
||||
assert Block.objects.count() == 0
|
||||
|
@ -1548,7 +1559,9 @@ class TestBlockAdminBulkDelete(TestCase):
|
|||
]
|
||||
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
|
||||
block_with_addon, block_no_addon = self._test_delete_multiple_submit(
|
||||
addon_adu=addon_adu)
|
||||
|
@ -1556,8 +1569,12 @@ class TestBlockAdminBulkDelete(TestCase):
|
|||
block_with_addon,
|
||||
block_no_addon,
|
||||
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
|
||||
block_with_addon, block_no_addon = self._test_delete_multiple_submit(
|
||||
addon_adu=addon_adu)
|
||||
|
@ -1574,6 +1591,8 @@ class TestBlockAdminBulkDelete(TestCase):
|
|||
block_with_addon,
|
||||
block_no_addon,
|
||||
has_signoff=True)
|
||||
legacy_delete_blocks_mock.assert_called_with(
|
||||
[block_with_addon, block_no_addon])
|
||||
|
||||
def test_edit_with_delete_submission(self):
|
||||
threshold = settings.DUAL_SIGNOFF_AVERAGE_DAILY_USERS_THRESHOLD
|
||||
|
|
|
@ -75,6 +75,7 @@ class TestImportBlocklist(TestCase):
|
|||
assert block.kinto_id == '*' + this_block['id']
|
||||
assert block.include_in_legacy
|
||||
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.filter(
|
||||
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].include_in_legacy
|
||||
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].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].include_in_legacy
|
||||
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.filter(
|
||||
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.%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):
|
||||
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
|
||||
from olympia import amo
|
||||
from olympia.activity import log_create
|
||||
from olympia.lib.kinto import KintoServer
|
||||
|
||||
|
||||
log = olympia.core.logger.getLogger('z.amo.blocklist')
|
||||
|
||||
KINTO_BUCKET = 'staging'
|
||||
KINTO_COLLECTION_LEGACY = 'addons'
|
||||
|
||||
|
||||
def add_version_log_for_blocked_versions(obj, al):
|
||||
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(
|
||||
layers=cascade.layerCount(), bits=cascade.bitCount()))
|
||||
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')
|
||||
|
||||
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', 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', 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
|
||||
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
|
||||
# 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
|
Загрузка…
Ссылка в новой задаче