Beispiel #1
0
 def test_multiple_record_deletion(self):
     client = Client(server_url=self.server_url, auth=self.auth,
                     bucket='mozilla', collection='payments')
     client.create_bucket()
     client.create_collection()
     client.create_record({'foo': 'bar'})
     client.delete_records()
     assert len(client.get_records()) == 0
Beispiel #2
0
 def test_multiple_record_deletion(self):
     client = Client(server_url=self.server_url, auth=self.auth,
                     bucket='mozilla', collection='payments')
     client.create_bucket()
     client.create_collection()
     client.create_record(data={'foo': 'bar'})
     client.delete_records()
     assert len(client.get_records()) == 0
Beispiel #3
0
 def test_multiple_record_deletion(self):
     client = Client(server_url=self.server_url,
                     auth=self.auth,
                     bucket="mozilla",
                     collection="payments")
     client.create_bucket()
     client.create_collection()
     client.create_record(data={"foo": "bar"})
     client.delete_records()
     assert len(client.get_records()) == 0
Beispiel #4
0
 def test_records_deletion_when_no_records_exist(self):
     client = Client(server_url=self.server_url, auth=self.auth,
                     bucket='mozilla', collection='payments')
     client.create_bucket()
     client.create_collection()
     deleted_records = client.delete_records()
     assert len(deleted_records) == 0
Beispiel #5
0
 def test_records_deletion_when_no_records_exist(self):
     client = Client(server_url=self.server_url, auth=self.auth,
                     bucket='mozilla', collection='payments')
     client.create_bucket()
     client.create_collection()
     deleted_records = client.delete_records()
     assert len(deleted_records) == 0
Beispiel #6
0
class RecordLoggingTest(unittest.TestCase):
    def setUp(self):
        self.session = mock.MagicMock()
        self.client = Client(session=self.session)
        mock_response(self.session)

    def test_create_record_logs_info_message(self):
        with mock.patch('kinto_http.logger') as mocked_logger:
            self.client.create_bucket(id='buck')
            self.client.create_collection(id='mozilla',
                                          bucket='buck')
            self.client.create_record(
                id='fake-record',
                data={'foo': 'bar'},
                permissions={'write': ['blah', ]},
                bucket='buck',
                collection='mozilla')
            mocked_logger.info.assert_called_with(
                "Create record with id 'fake-record' in collection 'mozilla' in bucket 'buck'")

    def test_update_record_logs_info_message(self):
        with mock.patch('kinto_http.logger') as mocked_logger:
            self.client.create_bucket(id='buck')
            self.client.create_collection(bucket='buck',
                                          id='mozilla')
            self.client.update_record(
                id='fake-record',
                data={'ss': 'aa'},
                bucket='buck',
                collection='mozilla')
            mocked_logger.info.assert_called_with(
                "Update record with id 'fake-record' in collection 'mozilla' in bucket 'buck'")

    def test_get_record_logs_info_message(self):
        with mock.patch('kinto_http.logger') as mocked_logger:
            self.client.create_bucket(id='buck')
            self.client.create_collection(id='mozilla',
                                          bucket='buck')
            self.client.get_record(
                id='fake-record',
                bucket='buck',
                collection='mozilla')
            mocked_logger.info.assert_called_with(
                "Get record with id 'fake-record' from collection 'mozilla' in bucket 'buck'")

    def test_delete_record_logs_info_message(self):
        with mock.patch('kinto_http.logger') as mocked_logger:
            self.client.create_bucket(id='buck')
            self.client.create_collection(id='mozilla',
                                          bucket='buck')
            self.client.delete_record(
                id='fake-record',
                bucket='buck',
                collection='mozilla')
            mocked_logger.info.assert_called_with(
                "Delete record with id 'fake-record' from collection 'mozilla' in bucket 'buck'")

    def test_delete_records_logs_info_message(self):
        with mock.patch('kinto_http.logger') as mocked_logger:
            self.client.create_bucket(id='buck')
            self.client.create_collection(id='mozilla',
                                          bucket='buck')
            self.client.delete_records(
                bucket='buck',
                collection='mozilla')
            mocked_logger.info.assert_called_with(
                "Delete records from collection 'mozilla' in bucket 'buck'")
class RecordLoggingTest(unittest.TestCase):
    def setUp(self):
        self.session = mock.MagicMock()
        self.client = Client(session=self.session)
        mock_response(self.session)

    def test_create_record_logs_info_message(self):
        with mock.patch('kinto_http.logger') as mocked_logger:
            self.client.create_bucket('buck')
            self.client.create_collection('mozilla', bucket='buck')
            self.client.create_record(id='fake-record',
                                      data={'foo': 'bar'},
                                      permissions={'write': [
                                          'blah',
                                      ]},
                                      bucket='buck',
                                      collection='mozilla')
            mocked_logger.info.assert_called_with(
                "Create record with id 'fake-record' in collection 'mozilla' in bucket 'buck'"
            )

    def test_update_record_logs_info_message(self):
        with mock.patch('kinto_http.logger') as mocked_logger:
            self.client.create_bucket('buck')
            self.client.create_collection('mozilla', bucket='buck')
            self.client.update_record(id='fake-record',
                                      data={'ss': 'aa'},
                                      bucket='buck',
                                      collection='mozilla')
            mocked_logger.info.assert_called_with(
                "Update record with id 'fake-record' in collection 'mozilla' in bucket 'buck'"
            )

    def test_get_record_logs_info_message(self):
        with mock.patch('kinto_http.logger') as mocked_logger:
            self.client.create_bucket('buck')
            self.client.create_collection('mozilla', bucket='buck')
            self.client.get_record(id='fake-record',
                                   bucket='buck',
                                   collection='mozilla')
            mocked_logger.info.assert_called_with(
                "Get record with id 'fake-record' from collection 'mozilla' in bucket 'buck'"
            )

    def test_delete_record_logs_info_message(self):
        with mock.patch('kinto_http.logger') as mocked_logger:
            self.client.create_bucket('buck')
            self.client.create_collection('mozilla', bucket='buck')
            self.client.delete_record(id='fake-record',
                                      bucket='buck',
                                      collection='mozilla')
            mocked_logger.info.assert_called_with(
                "Delete record with id 'fake-record' from collection 'mozilla' in bucket 'buck'"
            )

    def test_delete_records_logs_info_message(self):
        with mock.patch('kinto_http.logger') as mocked_logger:
            self.client.create_bucket('buck')
            self.client.create_collection('mozilla', bucket='buck')
            self.client.delete_records(bucket='buck', collection='mozilla')
            mocked_logger.info.assert_called_with(
                "Delete records from collection 'mozilla' in bucket 'buck'")
Beispiel #8
0
class CollectionTest(unittest.TestCase):

    def setUp(self):
        self.session = mock.MagicMock()
        mock_response(self.session)
        self.client = Client(session=self.session, bucket='mybucket')

    def test_collection_names_are_slugified(self):
        self.client.get_collection('my collection')
        url = '/buckets/mybucket/collections/my-collection'
        self.session.request.assert_called_with('get', url)

    def test_collection_creation_issues_an_http_put(self):
        self.client.create_collection(
            'mycollection',
            permissions=mock.sentinel.permissions)

        url = '/buckets/mybucket/collections/mycollection'
        self.session.request.assert_called_with(
            'put', url, data=None, permissions=mock.sentinel.permissions,
            headers=DO_NOT_OVERWRITE)

    def test_data_can_be_sent_on_creation(self):
        self.client.create_collection(
            'mycollection',
            'testbucket',
            data={'foo': 'bar'})

        self.session.request.assert_called_with(
            'put',
            '/buckets/testbucket/collections/mycollection',
            data={'foo': 'bar'},
            permissions=None,
            headers=DO_NOT_OVERWRITE)

    def test_collection_update_issues_an_http_put(self):
        self.client.update_collection(
            {'foo': 'bar'},
            collection='mycollection',
            permissions=mock.sentinel.permissions)

        url = '/buckets/mybucket/collections/mycollection'
        self.session.request.assert_called_with(
            'put', url, data={'foo': 'bar'},
            permissions=mock.sentinel.permissions, headers=None)

    def test_update_handles_if_match(self):
        self.client.update_collection(
            {'foo': 'bar'},
            collection='mycollection',
            if_match=1234)

        url = '/buckets/mybucket/collections/mycollection'
        headers = {'If-Match': '"1234"'}
        self.session.request.assert_called_with(
            'put', url, data={'foo': 'bar'},
            headers=headers, permissions=None)

    def test_collection_update_use_an_if_match_header(self):
        data = {'foo': 'bar', 'last_modified': '1234'}
        self.client.update_collection(
            data,
            collection='mycollection',
            permissions=mock.sentinel.permissions)

        url = '/buckets/mybucket/collections/mycollection'
        self.session.request.assert_called_with(
            'put', url, data={'foo': 'bar', 'last_modified': '1234'},
            permissions=mock.sentinel.permissions,
            headers={'If-Match': '"1234"'})

    def test_patch_collection_issues_an_http_patch(self):
        self.client.patch_collection(
            collection='mycollection',
            data={'key': 'secret'})

        url = '/buckets/mybucket/collections/mycollection'
        self.session.request.assert_called_with(
            'patch', url, data={'key': 'secret'}, headers=None,
            permissions=None)

    def test_patch_collection_handles_if_match(self):
        self.client.patch_collection(
            collection='mycollection',
            data={'key': 'secret'},
            if_match=1234)

        url = '/buckets/mybucket/collections/mycollection'
        headers = {'If-Match': '"1234"'}
        self.session.request.assert_called_with(
            'patch', url, data={'key': 'secret'}, headers=headers,
            permissions=None)

    def test_get_collections_returns_the_list_of_collections(self):
        mock_response(
            self.session,
            data=[
                {'id': 'foo', 'last_modified': '12345'},
                {'id': 'bar', 'last_modified': '59874'},
            ])

        collections = self.client.get_collections(bucket='default')
        assert list(collections) == [
            {'id': 'foo', 'last_modified': '12345'},
            {'id': 'bar', 'last_modified': '59874'},
        ]

    def test_collection_can_delete_all_its_records(self):
        self.client.delete_records(bucket='abucket', collection='acollection')
        url = '/buckets/abucket/collections/acollection/records'
        self.session.request.assert_called_with('delete', url, headers=None)

    def test_delete_is_issued_on_list_deletion(self):
        self.client.delete_collections(bucket='mybucket')
        url = '/buckets/mybucket/collections'
        self.session.request.assert_called_with('delete', url, headers=None)

    def test_collection_can_be_deleted(self):
        data = {}
        mock_response(self.session, data=data)
        deleted = self.client.delete_collection('mycollection')
        assert deleted == data
        url = '/buckets/mybucket/collections/mycollection'
        self.session.request.assert_called_with('delete', url, headers=None)

    def test_collection_delete_if_match(self):
        data = {}
        mock_response(self.session, data=data)
        deleted = self.client.delete_collection(
            'mycollection',
            if_match=1234)
        assert deleted == data
        url = '/buckets/mybucket/collections/mycollection'
        self.session.request.assert_called_with(
            'delete', url, headers={'If-Match': '"1234"'})

    def test_collection_delete_if_match_not_included_if_not_safe(self):
        data = {}
        mock_response(self.session, data=data)
        deleted = self.client.delete_collection(
            'mycollection',
            if_match=1324,
            safe=False)
        assert deleted == data
        url = '/buckets/mybucket/collections/mycollection'
        self.session.request.assert_called_with('delete', url, headers=None)

    def test_get_or_create_doesnt_raise_in_case_of_conflict(self):
        data = {
            'permissions': mock.sentinel.permissions,
            'data': {'foo': 'bar'}
        }
        self.session.request.side_effect = [
            get_http_error(status=412),
            (data, None)
        ]
        returned_data = self.client.create_collection(
            bucket="buck",
            collection="coll",
            if_not_exists=True)  # Should not raise.
        assert returned_data == data

    def test_get_or_create_raise_in_other_cases(self):
        self.session.request.side_effect = get_http_error(status=500)
        with self.assertRaises(KintoException):
            self.client.create_collection(
                bucket="buck",
                collection="coll",
                if_not_exists=True)

    def test_create_collection_raises_a_special_error_on_403(self):
        self.session.request.side_effect = get_http_error(status=403)
        with self.assertRaises(KintoException) as e:
            self.client.create_collection(
                bucket="buck",
                collection="coll")
        expected_msg = ("Unauthorized. Please check that the bucket exists "
                        "and that you have the permission to create or write "
                        "on this collection.")
        assert e.exception.message == expected_msg
Beispiel #9
0
class CollectionTest(unittest.TestCase):

    def setUp(self):
        self.session = mock.MagicMock()
        mock_response(self.session)
        self.client = Client(session=self.session, bucket='mybucket')

    def test_collection_names_are_slugified(self):
        self.client.get_collection(id='my collection')
        url = '/buckets/mybucket/collections/my-collection'
        self.session.request.assert_called_with('get', url)

    def test_collection_creation_issues_an_http_put(self):
        self.client.create_collection(id='mycollection',
                                      permissions=mock.sentinel.permissions)

        url = '/buckets/mybucket/collections/mycollection'
        self.session.request.assert_called_with(
            'put', url, data=None, permissions=mock.sentinel.permissions,
            headers=DO_NOT_OVERWRITE)

    def test_data_can_be_sent_on_creation(self):
        self.client.create_collection(id='mycollection',
                                      bucket='testbucket',
                                      data={'foo': 'bar'})

        self.session.request.assert_called_with(
            'put',
            '/buckets/testbucket/collections/mycollection',
            data={'foo': 'bar'},
            permissions=None,
            headers=DO_NOT_OVERWRITE)

    def test_collection_update_issues_an_http_put(self):
        self.client.update_collection(id='mycollection',
                                      data={'foo': 'bar'},
                                      permissions=mock.sentinel.permissions)

        url = '/buckets/mybucket/collections/mycollection'
        self.session.request.assert_called_with(
            'put', url, data={'foo': 'bar'},
            permissions=mock.sentinel.permissions, headers=None)

    def test_update_handles_if_match(self):
        self.client.update_collection(id='mycollection',
                                      data={'foo': 'bar'},
                                      if_match=1234)

        url = '/buckets/mybucket/collections/mycollection'
        headers = {'If-Match': '"1234"'}
        self.session.request.assert_called_with(
            'put', url, data={'foo': 'bar'},
            headers=headers, permissions=None)

    def test_collection_update_use_an_if_match_header(self):
        data = {'foo': 'bar', 'last_modified': '1234'}
        self.client.update_collection(id='mycollection', data=data,
                                      permissions=mock.sentinel.permissions)

        url = '/buckets/mybucket/collections/mycollection'
        self.session.request.assert_called_with(
            'put', url, data={'foo': 'bar', 'last_modified': '1234'},
            permissions=mock.sentinel.permissions,
            headers={'If-Match': '"1234"'})

    def test_patch_collection_issues_an_http_patch(self):
        self.client.patch_collection(id='mycollection',
                                     data={'key': 'secret'})

        url = '/buckets/mybucket/collections/mycollection'
        self.session.request.assert_called_with(
            'patch', url, payload={'data': {'key': 'secret'}},
            headers={'Content-Type': 'application/json'},
        )

    def test_patch_collection_handles_if_match(self):
        self.client.patch_collection(id='mycollection',
                                     data={'key': 'secret'},
                                     if_match=1234)

        url = '/buckets/mybucket/collections/mycollection'
        headers = {'If-Match': '"1234"', 'Content-Type': 'application/json'}
        self.session.request.assert_called_with(
            'patch', url, payload={'data': {'key': 'secret'}}, headers=headers,
        )

    def test_patch_requires_patch_to_be_patch_type(self):
        with pytest.raises(TypeError):
            self.client.patch_collection(id='testcoll', bucket='testbucket', changes=5)

    def test_get_collections_returns_the_list_of_collections(self):
        mock_response(
            self.session,
            data=[
                {'id': 'foo', 'last_modified': '12345'},
                {'id': 'bar', 'last_modified': '59874'},
            ])

        collections = self.client.get_collections(bucket='default')
        assert list(collections) == [
            {'id': 'foo', 'last_modified': '12345'},
            {'id': 'bar', 'last_modified': '59874'},
        ]

    def test_collection_can_delete_all_its_records(self):
        self.client.delete_records(bucket='abucket', collection='acollection')
        url = '/buckets/abucket/collections/acollection/records'
        self.session.request.assert_called_with('delete', url, headers=None)

    def test_delete_is_issued_on_list_deletion(self):
        self.client.delete_collections(bucket='mybucket')
        url = '/buckets/mybucket/collections'
        self.session.request.assert_called_with('delete', url, headers=None)

    def test_collection_can_be_deleted(self):
        data = {}
        mock_response(self.session, data=data)
        deleted = self.client.delete_collection(id='mycollection')
        assert deleted == data
        url = '/buckets/mybucket/collections/mycollection'
        self.session.request.assert_called_with('delete', url, headers=None)

    def test_collection_delete_if_match(self):
        data = {}
        mock_response(self.session, data=data)
        deleted = self.client.delete_collection(id='mycollection', if_match=1234)
        assert deleted == data
        url = '/buckets/mybucket/collections/mycollection'
        self.session.request.assert_called_with(
            'delete', url, headers={'If-Match': '"1234"'})

    def test_collection_delete_if_match_not_included_if_not_safe(self):
        data = {}
        mock_response(self.session, data=data)
        deleted = self.client.delete_collection(id='mycollection',
                                                if_match=1324,
                                                safe=False)
        assert deleted == data
        url = '/buckets/mybucket/collections/mycollection'
        self.session.request.assert_called_with('delete', url, headers=None)

    def test_get_or_create_doesnt_raise_in_case_of_conflict(self):
        data = {
            'permissions': mock.sentinel.permissions,
            'data': {'foo': 'bar'}
        }
        self.session.request.side_effect = [
            get_http_error(status=412),
            (data, None)
        ]
        returned_data = self.client.create_collection(bucket="buck",
                                                      id="coll",
                                                      if_not_exists=True)  # Should not raise.
        assert returned_data == data

    def test_get_or_create_raise_in_other_cases(self):
        self.session.request.side_effect = get_http_error(status=500)
        with self.assertRaises(KintoException):
            self.client.create_collection(bucket="buck",
                                          id="coll",
                                          if_not_exists=True)

    def test_create_collection_raises_a_special_error_on_403(self):
        self.session.request.side_effect = get_http_error(status=403)
        with self.assertRaises(KintoException) as e:
            self.client.create_collection(bucket="buck",
                                          id="coll")
        expected_msg = ("Unauthorized. Please check that the bucket exists "
                        "and that you have the permission to create or write "
                        "on this collection.")
        assert e.exception.message == expected_msg

    def test_create_collection_can_deduce_id_from_data(self):
        self.client.create_collection(data={'id': 'coll'}, bucket='buck')
        self.session.request.assert_called_with(
            'put', '/buckets/buck/collections/coll', data={'id': 'coll'}, permissions=None,
            headers=DO_NOT_OVERWRITE)

    def test_update_collection_can_deduce_id_from_data(self):
        self.client.update_collection(data={'id': 'coll'}, bucket='buck')
        self.session.request.assert_called_with(
            'put', '/buckets/buck/collections/coll', data={'id': 'coll'}, permissions=None,
            headers=None)
Beispiel #10
0
class RecordLoggingTest(unittest.TestCase):
    def setUp(self):
        self.session = mock.MagicMock()
        self.client = Client(session=self.session)
        mock_response(self.session)

    def test_create_record_logs_info_message(self):
        with mock.patch("kinto_http.logger") as mocked_logger:
            self.client.create_bucket(id="buck")
            self.client.create_collection(id="mozilla", bucket="buck")
            self.client.create_record(
                id="fake-record",
                data={"foo": "bar"},
                permissions={"write": ["blah"]},
                bucket="buck",
                collection="mozilla",
            )
            mocked_logger.info.assert_called_with(
                "Create record with id 'fake-record' in collection 'mozilla' in bucket 'buck'"
            )

    def test_update_record_logs_info_message(self):
        with mock.patch("kinto_http.logger") as mocked_logger:
            self.client.create_bucket(id="buck")
            self.client.create_collection(bucket="buck", id="mozilla")
            self.client.update_record(id="fake-record",
                                      data={"ss": "aa"},
                                      bucket="buck",
                                      collection="mozilla")
            mocked_logger.info.assert_called_with(
                "Update record with id 'fake-record' in collection 'mozilla' in bucket 'buck'"
            )

    def test_patch_record_logs_info_message(self):
        with mock.patch("kinto_http.logger") as mocked_logger:
            self.client.create_bucket(id="buck")
            self.client.create_collection(bucket="buck", id="mozilla")
            self.client.patch_record(id="fake-record",
                                     data={"ss": "aa"},
                                     bucket="buck",
                                     collection="mozilla")
            mocked_logger.info.assert_called_with(
                "Patch record with id 'fake-record' in collection 'mozilla' in bucket 'buck'"
            )

    def test_get_record_logs_info_message(self):
        with mock.patch("kinto_http.logger") as mocked_logger:
            self.client.create_bucket(id="buck")
            self.client.create_collection(id="mozilla", bucket="buck")
            self.client.get_record(id="fake-record",
                                   bucket="buck",
                                   collection="mozilla")
            mocked_logger.info.assert_called_with(
                "Get record with id 'fake-record' from collection 'mozilla' in bucket 'buck'"
            )

    def test_delete_record_logs_info_message(self):
        with mock.patch("kinto_http.logger") as mocked_logger:
            self.client.create_bucket(id="buck")
            self.client.create_collection(id="mozilla", bucket="buck")
            self.client.delete_record(id="fake-record",
                                      bucket="buck",
                                      collection="mozilla")
            mocked_logger.info.assert_called_with(
                "Delete record with id 'fake-record' from collection 'mozilla' in bucket 'buck'"
            )

    def test_delete_records_logs_info_message(self):
        with mock.patch("kinto_http.logger") as mocked_logger:
            self.client.create_bucket(id="buck")
            self.client.create_collection(id="mozilla", bucket="buck")
            self.client.delete_records(bucket="buck", collection="mozilla")
            mocked_logger.info.assert_called_with(
                "Delete records from collection 'mozilla' in bucket 'buck'")
Beispiel #11
0
def main():
    args = _get_args()

    client = Client(server_url=args.server,
                    auth=tuple(args.auth.split(':')),
                    bucket=args.source_bucket,
                    collection=args.source_col)

    if args.editor_auth is None:
        args.editor_auth = args.auth

    if args.reviewer_auth is None:
        args.reviewer_auth = args.auth

    editor_client = Client(server_url=args.server,
                           auth=tuple(args.editor_auth.split(':')),
                           bucket=args.source_bucket,
                           collection=args.source_col)
    reviewer_client = Client(server_url=args.server,
                             auth=tuple(args.reviewer_auth.split(':')),
                             bucket=args.source_bucket,
                             collection=args.source_col)

    # 0. initialize source bucket/collection (if necessary)
    server_info = client.server_info()
    editor_id = editor_client.server_info()['user']['id']
    reviewer_id = reviewer_client.server_info()['user']['id']
    print('Server: {0}'.format(args.server))
    print('Author: {user[id]}'.format(**server_info))
    print('Editor: {0}'.format(editor_id))
    print('Reviewer: {0}'.format(reviewer_id))

    # 0. check that this collection is well configured.
    signer_capabilities = server_info['capabilities']['signer']
    to_review_enabled = signer_capabilities.get('to_review_enabled', False)
    group_check_enabled = signer_capabilities.get('group_check_enabled', False)

    resources = [
        r for r in signer_capabilities['resources']
        if (args.source_bucket, args.source_col) == (r['source']['bucket'],
                                                     r['source']['collection'])
    ]
    assert len(resources) > 0, 'Specified source not configured to be signed'
    resource = resources[0]
    if to_review_enabled and 'preview' in resource:
        print(
            'Signoff: {source[bucket]}/{source[collection]} => {preview[bucket]}/{preview[collection]} => {destination[bucket]}/{destination[collection]}'
            .format(**resource))
    else:
        print(
            'Signoff: {source[bucket]}/{source[collection]} => {destination[bucket]}/{destination[collection]}'
            .format(**resource))
    print('Group check: {0}'.format(group_check_enabled))
    print('Review workflow: {0}'.format(to_review_enabled))

    print('_' * 80)

    bucket = client.create_bucket(if_not_exists=True)
    client.patch_bucket(permissions={
        'write': [editor_id, reviewer_id] + bucket['permissions']['write']
    },
                        if_match=bucket['data']['last_modified'],
                        safe=True)

    client.create_collection(if_not_exists=True)

    if args.reset:
        client.delete_records()
        existing = 0
    else:
        existing_records = client.get_records()
        existing = len(existing_records)

    if group_check_enabled:
        editors_group = signer_capabilities['editors_group']
        client.create_group(editors_group,
                            data={'members': [editor_id]},
                            if_not_exists=True)
        reviewers_group = signer_capabilities['reviewers_group']
        client.create_group(reviewers_group,
                            data={'members': [reviewer_id]},
                            if_not_exists=True)

    dest_client = Client(server_url=args.server,
                         bucket=resource['destination']['bucket'],
                         collection=resource['destination']['collection'])

    preview_client = None
    if to_review_enabled and 'preview' in resource:
        preview_bucket = resource['preview']['bucket']
        preview_collection = resource['preview']['collection']
        preview_client = Client(server_url=args.server,
                                bucket=preview_bucket,
                                collection=preview_collection)

    # 1. upload data
    print('Author uploads 20 random records')
    records = upload_records(client, 20)

    # 2. ask for a signature
    # 2.1 ask for review (noop on old versions)
    print('Editor asks for review')
    data = {"status": "to-review"}
    editor_client.patch_collection(data=data)
    # 2.2 check the preview collection (if enabled)
    if preview_client:
        print('Check preview collection')
        preview_records = preview_client.get_records()
        expected = existing + 20
        assert len(preview_records) == expected, '%s != %s records' % (
            len(preview_records), expected)
        metadata = preview_client.get_collection()['data']
        preview_signature = metadata.get('signature')
        assert preview_signature, 'Preview collection not signed'
        preview_timestamp = collection_timestamp(preview_client)
    # 2.3 approve the review
    print('Reviewer approves and triggers signature')
    data = {"status": "to-sign"}
    reviewer_client.patch_collection(data=data)

    # 3. upload more data
    print('Author creates 20 others records')
    upload_records(client, 20)

    print('Editor updates 5 random records')
    for toupdate in random.sample(records, 5):
        editor_client.patch_record(dict(newkey=_rand(10), **toupdate))

    print('Author deletes 5 random records')
    for todelete in random.sample(records, 5):
        client.delete_record(todelete['id'])

    expected = existing + 20 + 20 - 5

    # 4. ask again for a signature
    # 2.1 ask for review (noop on old versions)
    print('Editor asks for review')
    data = {"status": "to-review"}
    editor_client.patch_collection(data=data)
    # 2.2 check the preview collection (if enabled)
    if preview_client:
        print('Check preview collection')
        preview_records = preview_client.get_records()
        assert len(preview_records) == expected, '%s != %s records' % (
            len(preview_records), expected)
        # Diff size is 20 + 5 if updated records are also all deleted,
        # or 30 if deletions and updates apply to different records.
        diff_since_last = preview_client.get_records(_since=preview_timestamp)
        assert 25 <= len(
            diff_since_last
        ) <= 30, 'Changes since last signature are not consistent'

        metadata = preview_client.get_collection()['data']
        assert preview_signature != metadata[
            'signature'], 'Preview collection not updated'

    # 2.3 approve the review
    print('Reviewer approves and triggers signature')
    data = {"status": "to-sign"}
    reviewer_client.patch_collection(data=data)

    # 5. wait for the result

    # 6. obtain the destination records and serialize canonically.

    records = list(dest_client.get_records())
    assert len(records) == expected, '%s != %s records' % (len(records),
                                                           expected)
    timestamp = collection_timestamp(dest_client)
    serialized = canonical_json(records, timestamp)
    print('Hash is %r' % compute_hash(serialized))

    # 7. get back the signed hash

    dest_col = dest_client.get_collection()
    signature = dest_col['data']['signature']

    with open('pub', 'w') as f:
        f.write(signature['public_key'])

    # 8. verify the signature matches the hash
    signer = ECDSASigner(public_key='pub')
    try:
        signer.verify(serialized, signature)
        print('Signature OK')
    except Exception:
        print('Signature KO')
        raise
Beispiel #12
0
def main():
    args = _get_args()

    client = Client(server_url=args.server, auth=tuple(args.auth.split(':')),
                    bucket=args.source_bucket,
                    collection=args.source_col)

    if args.editor_auth is None:
        args.editor_auth = args.auth

    if args.reviewer_auth is None:
        args.reviewer_auth = args.auth

    editor_client = Client(server_url=args.server,
                           auth=tuple(args.editor_auth.split(':')),
                           bucket=args.source_bucket,
                           collection=args.source_col)
    reviewer_client = Client(server_url=args.server,
                             auth=tuple(args.reviewer_auth.split(':')),
                             bucket=args.source_bucket,
                             collection=args.source_col)

    # 0. initialize source bucket/collection (if necessary)
    server_info = client.server_info()
    editor_id = editor_client.server_info()['user']['id']
    reviewer_id = reviewer_client.server_info()['user']['id']
    print('Server: {0}'.format(args.server))
    print('Author: {user[id]}'.format(**server_info))
    print('Editor: {0}'.format(editor_id))
    print('Reviewer: {0}'.format(reviewer_id))

    # 0. check that this collection is well configured.
    signer_capabilities = server_info['capabilities']['signer']

    resources = [r for r in signer_capabilities['resources']
                 if (args.source_bucket, args.source_col) == (r['source']['bucket'], r['source']['collection']) or
                    (args.source_bucket, None) == (r['source']['bucket'], r['source']['collection'])]
    assert len(resources) > 0, 'Specified source not configured to be signed'
    resource = resources[0]
    if 'preview' in resource:
        print('Signoff: {source[bucket]}/{source[collection]} => {preview[bucket]}/{preview[collection]} => {destination[bucket]}/{destination[collection]}'.format(**resource))
    else:
        print('Signoff: {source[bucket]}/{source[collection]} => {destination[bucket]}/{destination[collection]}'.format(**resource))

    print('_' * 80)

    bucket = client.create_bucket(if_not_exists=True)
    client.create_collection(permissions={'write': [editor_id, reviewer_id] + bucket['permissions']['write']}, if_not_exists=True)

    editors_group = resource.get('editors_group') or signer_capabilities['editors_group']
    editors_group = editors_group.format(collection_id=args.source_col)
    client.patch_group(id=editors_group, data={'members': [editor_id]})

    reviewers_group = resource.get('reviewers_group') or signer_capabilities['reviewers_group']
    reviewers_group = reviewers_group.format(collection_id=args.source_col)
    client.patch_group(id=reviewers_group, data={'members': [reviewer_id]})

    if args.reset:
        client.delete_records()
        existing = 0
    else:
        existing_records = client.get_records()
        existing = len(existing_records)

    dest_col = resource['destination'].get('collection') or args.source_col
    dest_client = Client(server_url=args.server,
                         bucket=resource['destination']['bucket'],
                         collection=dest_col)

    preview_client = None
    if 'preview' in resource:
        preview_bucket = resource['preview']['bucket']
        preview_collection = resource['preview'].get('collection') or args.source_col
        preview_client = Client(server_url=args.server,
                                bucket=preview_bucket,
                                collection=preview_collection)

    # 1. upload data
    print('Author uploads 20 random records')
    records = upload_records(client, 20)

    # 2. ask for a signature
    # 2.1 ask for review (noop on old versions)
    print('Editor asks for review')
    data = {"status": "to-review"}
    editor_client.patch_collection(data=data)
    # 2.2 check the preview collection (if enabled)
    if preview_client:
        print('Check preview collection')
        preview_records = preview_client.get_records()
        expected = existing + 20
        assert len(preview_records) == expected, '%s != %s records' % (len(preview_records), expected)
        metadata = preview_client.get_collection()['data']
        preview_signature = metadata.get('signature')
        assert preview_signature, 'Preview collection not signed'
        preview_timestamp = preview_client.get_records_timestamp()
    # 2.3 approve the review
    print('Reviewer approves and triggers signature')
    data = {"status": "to-sign"}
    reviewer_client.patch_collection(data=data)

    # 3. upload more data
    print('Author creates 20 others records')
    upload_records(client, 20)

    print('Editor updates 5 random records')
    for toupdate in random.sample(records, 5):
        editor_client.patch_record(data=dict(newkey=_rand(10), **toupdate))

    print('Author deletes 5 random records')
    for todelete in random.sample(records, 5):
        client.delete_record(id=todelete['id'])

    expected = existing + 20 + 20 - 5

    # 4. ask again for a signature
    # 2.1 ask for review (noop on old versions)
    print('Editor asks for review')
    data = {"status": "to-review"}
    editor_client.patch_collection(data=data)
    # 2.2 check the preview collection (if enabled)
    if preview_client:
        print('Check preview collection')
        preview_records = preview_client.get_records()
        assert len(preview_records) == expected, '%s != %s records' % (len(preview_records), expected)
        # Diff size is 20 + 5 if updated records are also all deleted,
        # or 30 if deletions and updates apply to different records.
        diff_since_last = preview_client.get_records(_since=preview_timestamp)
        assert 25 <= len(diff_since_last) <= 30, 'Changes since last signature are not consistent'

        metadata = preview_client.get_collection()['data']
        assert preview_signature != metadata['signature'], 'Preview collection not updated'

    # 2.3 approve the review
    print('Reviewer approves and triggers signature')
    data = {"status": "to-sign"}
    reviewer_client.patch_collection(data=data)

    # 5. wait for the result

    # 6. obtain the destination records and serialize canonically.

    records = list(dest_client.get_records())
    assert len(records) == expected, '%s != %s records' % (len(records), expected)
    timestamp = dest_client.get_records_timestamp()
    serialized = canonical_json(records, timestamp)
    print('Hash is %r' % compute_hash(serialized))

    # 7. get back the signed hash

    signature = dest_client.get_collection()['data']['signature']

    with open('pub', 'w') as f:
        f.write(signature['public_key'])

    # 8. verify the signature matches the hash
    signer = ECDSASigner(public_key='pub')
    try:
        signer.verify(serialized, signature)
        print('Signature OK')
    except Exception:
        print('Signature KO')
        raise