diff --git a/lemur/authorities/schemas.py b/lemur/authorities/schemas.py index d70d1cac..13cdc48f 100644 --- a/lemur/authorities/schemas.py +++ b/lemur/authorities/schemas.py @@ -80,6 +80,21 @@ class AuthorityOutputSchema(LemurOutputSchema): options = fields.Dict() roles = fields.List(fields.Nested(AssociatedRoleSchema)) + +class AuthorityNestedOutputSchema(LemurOutputSchema): + id = fields.Integer() + description = fields.String() + name = fields.String() + owner = fields.Email() + not_before = fields.DateTime() + not_after = fields.DateTime() + plugin = fields.Nested(PluginOutputSchema) + body = fields.String() + chain = fields.String() + active = fields.Boolean() + options = fields.Dict() + + authority_update_schema = AuthorityUpdateSchema() authority_input_schema = AuthorityInputSchema() authority_output_schema = AuthorityOutputSchema() diff --git a/lemur/certificates/schemas.py b/lemur/certificates/schemas.py index 711fc9d9..ed275f0a 100644 --- a/lemur/certificates/schemas.py +++ b/lemur/certificates/schemas.py @@ -12,6 +12,13 @@ from marshmallow.exceptions import ValidationError from lemur.schemas import AssociatedAuthoritySchema, AssociatedDestinationSchema, AssociatedCertificateSchema, \ AssociatedNotificationSchema, PluginInputSchema, ExtensionSchema + +from lemur.authorities.schemas import AuthorityNestedOutputSchema +from lemur.destinations.schemas import DestinationNestedOutputSchema +from lemur.notifications.schemas import NotificationNestedOutputSchema +# from lemur.domains.schemas import DomainNestedOutputSchema +from lemur.users.schemas import UserNestedOutputSchema + from lemur.common.schema import LemurInputSchema, LemurOutputSchema from lemur.common import validators @@ -47,6 +54,33 @@ class CertificateInputSchema(LemurInputSchema): validators.dates(data) +class CertificateEditInputSchema(LemurInputSchema): + owner = fields.Email(required=True) + description = fields.String() + active = fields.Boolean() + destinations = fields.Nested(AssociatedDestinationSchema, missing=[], many=True) + notifications = fields.Nested(AssociatedNotificationSchema, missing=[], many=True) + replacements = fields.Nested(AssociatedCertificateSchema, missing=[], many=True) + + +class CertificateNestedOutputSchema(LemurOutputSchema): + __envelope__ = False + id = fields.Integer() + active = fields.Boolean() + bits = fields.Integer() + body = fields.String() + chain = fields.String() + description = fields.String() + name = fields.String() + cn = fields.String() + not_after = fields.DateTime() + not_before = fields.DateTime() + owner = fields.Email() + status = fields.Boolean() + creator = fields.Nested(UserNestedOutputSchema) + issuer = fields.Nested(AuthorityNestedOutputSchema) + + class CertificateOutputSchema(LemurOutputSchema): id = fields.Integer() active = fields.Boolean() @@ -57,7 +91,7 @@ class CertificateOutputSchema(LemurOutputSchema): description = fields.String() issuer = fields.String() name = fields.String() - common_name = fields.String() + cn = fields.String() not_after = fields.DateTime() not_before = fields.DateTime() owner = fields.Email() @@ -65,6 +99,12 @@ class CertificateOutputSchema(LemurOutputSchema): serial = fields.String() signing_algorithm = fields.String() status = fields.Boolean() + user = fields.Nested(UserNestedOutputSchema) + # domains = fields.Nested(DomainNestedOutputSchema) + destinations = fields.Nested(DestinationNestedOutputSchema, many=True) + notifications = fields.Nested(NotificationNestedOutputSchema, many=True) + replaces = fields.Nested(CertificateNestedOutputSchema, many=True) + authority = fields.Nested(AuthorityNestedOutputSchema) class CertificateUploadInputSchema(LemurInputSchema): @@ -97,3 +137,4 @@ certificate_output_schema = CertificateOutputSchema() certificates_output_schema = CertificateOutputSchema(many=True) certificate_upload_input_schema = CertificateUploadInputSchema() certificate_export_input_schema = CertificateExportInputSchema() +certificate_edit_input_schema = CertificateEditInputSchema() diff --git a/lemur/certificates/views.py b/lemur/certificates/views.py index cd2439b5..9726fdb9 100644 --- a/lemur/certificates/views.py +++ b/lemur/certificates/views.py @@ -19,7 +19,7 @@ from lemur.auth.permissions import ViewKeyPermission, AuthorityPermission, Updat from lemur.certificates import service from lemur.certificates.schemas import certificate_input_schema, certificate_output_schema, \ - certificate_upload_input_schema, certificates_output_schema, certificate_export_input_schema + certificate_upload_input_schema, certificates_output_schema, certificate_export_input_schema, certificate_edit_input_schema from lemur.roles import service as role_service @@ -464,7 +464,7 @@ class Certificates(AuthenticatedResource): """ return service.get(certificate_id) - @validate_schema(certificate_upload_input_schema, certificate_output_schema) + @validate_schema(certificate_edit_input_schema, certificate_output_schema) def put(self, certificate_id, data=None): """ .. http:put:: /certificates/1 diff --git a/lemur/destinations/schemas.py b/lemur/destinations/schemas.py index 2545bf71..3bcb0d5a 100644 --- a/lemur/destinations/schemas.py +++ b/lemur/destinations/schemas.py @@ -33,6 +33,10 @@ class DestinationOutputSchema(LemurOutputSchema): return data +class DestinationNestedOutputSchema(DestinationOutputSchema): + __envelope__ = False + + destination_input_schema = DestinationInputSchema() destinations_output_schema = DestinationOutputSchema(many=True) destination_output_schema = DestinationOutputSchema() diff --git a/lemur/domains/schemas.py b/lemur/domains/schemas.py new file mode 100644 index 00000000..5b1b10dd --- /dev/null +++ b/lemur/domains/schemas.py @@ -0,0 +1,35 @@ +""" +.. module: lemur.domains.schemas + :platform: unix + :copyright: (c) 2015 by Netflix Inc., see AUTHORS for more + :license: Apache, see LICENSE for more details. +.. moduleauthor:: Kevin Glisson +""" +from marshmallow import fields +from lemur.common.schema import LemurInputSchema, LemurOutputSchema +from lemur.schemas import AssociatedCertificateSchema + +from lemur.certificates.schemas import CertificateNestedOutputSchema + + +class DomainInputSchema(LemurInputSchema): + id = fields.Integer() + name = fields.String(required=True) + sensitive = fields.Boolean() + certificates = fields.Nested(AssociatedCertificateSchema, many=True, missing=[]) + + +class DomainOutputSchema(LemurOutputSchema): + id = fields.Integer() + name = fields.String() + sensitive = fields.Boolean() + certificates = fields.Nested(CertificateNestedOutputSchema, many=True, missing=[]) + + +class DomainNestedOutputSchema(DomainOutputSchema): + __envelope__ = False + + +domain_input_schema = DomainInputSchema() +domain_output_schema = DomainOutputSchema() +domains_output_schema = DomainOutputSchema(many=True) diff --git a/lemur/domains/service.py b/lemur/domains/service.py index 5b3e02f5..c9b0b83a 100644 --- a/lemur/domains/service.py +++ b/lemur/domains/service.py @@ -77,11 +77,6 @@ def render(args): :return: """ query = database.session_query(Domain).join(Certificate, Domain.certificate) - - sort_by = args.pop('sort_by') - sort_dir = args.pop('sort_dir') - page = args.pop('page') - count = args.pop('count') filt = args.pop('filter') certificate_id = args.pop('certificate_id', None) @@ -92,9 +87,4 @@ def render(args): if certificate_id: query = query.filter(Certificate.id == certificate_id) - query = database.find_all(query, Domain, args) - - if sort_by and sort_dir: - query = database.sort(query, Domain, sort_by, sort_dir) - - return database.paginate(query, page, count) + return database.sort_and_page(query, Domain, args) diff --git a/lemur/domains/views.py b/lemur/domains/views.py index 1fa6a6d7..0ed07261 100644 --- a/lemur/domains/views.py +++ b/lemur/domains/views.py @@ -8,19 +8,16 @@ """ from flask import Blueprint -from flask.ext.restful import reqparse, Api, fields +from flask.ext.restful import reqparse, Api from lemur.domains import service from lemur.auth.service import AuthenticatedResource from lemur.auth.permissions import SensitiveDomainPermission -from lemur.common.utils import paginated_parser, marshal_items +from lemur.common.schema import validate_schema +from lemur.common.utils import paginated_parser -FIELDS = { - 'id': fields.Integer, - 'name': fields.String, - 'sensitive': fields.Boolean -} +from lemur.domains.schemas import domain_input_schema, domain_output_schema, domains_output_schema mod = Blueprint('domains', __name__) api = Api(mod) @@ -31,7 +28,7 @@ class DomainsList(AuthenticatedResource): def __init__(self): super(DomainsList, self).__init__() - @marshal_items(FIELDS) + @validate_schema(None, domains_output_schema) def get(self): """ .. http:get:: /domains @@ -83,8 +80,8 @@ class DomainsList(AuthenticatedResource): args = parser.parse_args() return service.render(args) - @marshal_items(FIELDS) - def post(self): + @validate_schema(domain_input_schema, domain_output_schema) + def post(self, data=None): """ .. http:post:: /domains @@ -126,10 +123,7 @@ class DomainsList(AuthenticatedResource): :statuscode 200: no error :statuscode 403: unauthenticated """ - self.reqparse.add_argument('name', type=str, location='json') - self.reqparse.add_argument('sensitive', type=bool, default=False, location='json') - args = self.reqparse.parse_args() - return service.create(args['name'], args['sensitive']) + return service.create(data['name'], data['sensitive']) class Domains(AuthenticatedResource): @@ -137,7 +131,7 @@ class Domains(AuthenticatedResource): self.reqparse = reqparse.RequestParser() super(Domains, self).__init__() - @marshal_items(FIELDS) + @validate_schema(None, domain_output_schema) def get(self, domain_id): """ .. http:get:: /domains/1 @@ -172,8 +166,8 @@ class Domains(AuthenticatedResource): """ return service.get(domain_id) - @marshal_items(FIELDS) - def put(self, domain_id): + @validate_schema(domain_input_schema, domain_output_schema) + def put(self, domain_id, data=None): """ .. http:get:: /domains/1 @@ -210,12 +204,8 @@ class Domains(AuthenticatedResource): :statuscode 200: no error :statuscode 403: unauthenticated """ - self.reqparse.add_argument('name', type=str, location='json') - self.reqparse.add_argument('sensitive', type=bool, default=False, location='json') - args = self.reqparse.parse_args() - if SensitiveDomainPermission().can(): - return service.update(domain_id, args['name'], args['sensitive']) + return service.update(domain_id, data['name'], data['sensitive']) return dict(message='You are not authorized to modify this domain'), 403 @@ -225,7 +215,7 @@ class CertificateDomains(AuthenticatedResource): def __init__(self): super(CertificateDomains, self).__init__() - @marshal_items(FIELDS) + @validate_schema(None, domains_output_schema) def get(self, certificate_id): """ .. http:get:: /certificates/1/domains diff --git a/lemur/notifications/schemas.py b/lemur/notifications/schemas.py index b1510cd6..3f164a33 100644 --- a/lemur/notifications/schemas.py +++ b/lemur/notifications/schemas.py @@ -34,6 +34,10 @@ class NotificationOutputSchema(LemurOutputSchema): return data +class NotificationNestedOutputSchema(NotificationOutputSchema): + __envelope__ = False + + notification_input_schema = NotificationInputSchema() notification_output_schema = NotificationOutputSchema() notifications_output_schema = NotificationOutputSchema(many=True) diff --git a/lemur/static/app/angular/authorities/authority/edit.tpl.html b/lemur/static/app/angular/authorities/authority/edit.tpl.html index 5397bbfd..8859e2e4 100644 --- a/lemur/static/app/angular/authorities/authority/edit.tpl.html +++ b/lemur/static/app/angular/authorities/authority/edit.tpl.html @@ -38,7 +38,7 @@ uib-tooltip="Roles control which authorities a user can issue certificates from" tooltip-trigger="focus" tooltip-placement="top"> - diff --git a/lemur/static/app/angular/authorities/authority/permissions.tpl.html b/lemur/static/app/angular/authorities/authority/permissions.tpl.html index 8cccbc2f..938735de 100644 --- a/lemur/static/app/angular/authorities/authority/permissions.tpl.html +++ b/lemur/static/app/angular/authorities/authority/permissions.tpl.html @@ -10,7 +10,7 @@ uib-tooltip="These are the User roles you wish to associated with your authority" tooltip-trigger="focus" tooltip-placement="top"> - diff --git a/lemur/static/app/angular/certificates/certificate/destinations.tpl.html b/lemur/static/app/angular/certificates/certificate/destinations.tpl.html index fc57ba0b..b4a93fdc 100644 --- a/lemur/static/app/angular/certificates/certificate/destinations.tpl.html +++ b/lemur/static/app/angular/certificates/certificate/destinations.tpl.html @@ -11,7 +11,7 @@ uib-tooltip-trigger="focus" uib-tooltip-placement="top" typeahead-wait-ms="500"> - diff --git a/lemur/static/app/angular/certificates/certificate/notifications.tpl.html b/lemur/static/app/angular/certificates/certificate/notifications.tpl.html index dd704e0a..280460f9 100644 --- a/lemur/static/app/angular/certificates/certificate/notifications.tpl.html +++ b/lemur/static/app/angular/certificates/certificate/notifications.tpl.html @@ -10,7 +10,7 @@ uib-tooltip="By default Lemur will always notify you about this certificate through Email notifications." uib-tooltip-trigger="focus" tooltip-placement="top" typeahead-wait-ms="500"> - diff --git a/lemur/static/app/angular/certificates/certificate/replaces.tpl.html b/lemur/static/app/angular/certificates/certificate/replaces.tpl.html index d2d89b15..0df938d4 100644 --- a/lemur/static/app/angular/certificates/certificate/replaces.tpl.html +++ b/lemur/static/app/angular/certificates/certificate/replaces.tpl.html @@ -10,7 +10,7 @@ uib-tooltip="Lemur will mark any certificates being replaced as 'inactive'" uib-tooltip-trigger="focus" uib-tooltip-placement="top" typeahead-wait-ms="500"> - diff --git a/lemur/static/app/angular/certificates/view/view.js b/lemur/static/app/angular/certificates/view/view.js index d1662196..63d20a20 100644 --- a/lemur/static/app/angular/certificates/view/view.js +++ b/lemur/static/app/angular/certificates/view/view.js @@ -31,15 +31,6 @@ angular.module('lemur') getData: function ($defer, params) { CertificateApi.getList(params.url()) .then(function (data) { - // TODO we should attempt to resolve all of these in parallel - _.each(data, function (certificate) { - CertificateService.getDomains(certificate); - CertificateService.getDestinations(certificate); - CertificateService.getNotifications(certificate); - CertificateService.getReplacements(certificate); - CertificateService.getAuthority(certificate); - CertificateService.getCreator(certificate); - }); params.total(data.total); $defer.resolve(data); }); diff --git a/lemur/static/app/angular/certificates/view/view.tpl.html b/lemur/static/app/angular/certificates/view/view.tpl.html index f51d4022..3426b023 100644 --- a/lemur/static/app/angular/certificates/view/view.tpl.html +++ b/lemur/static/app/angular/certificates/view/view.tpl.html @@ -37,14 +37,14 @@ {{ certificate.authority.name || certificate.issuer }} - + {{ certificate.cn }}
Permalink - - +
{{ certificate.chain }}
- - - + + + Public Certificate - +
{{ certificate.body }}
-
- - + + + Private Key - +
{{ certificate.privateKey }}
-
- + + diff --git a/lemur/static/app/angular/notifications/notification/notification.tpl.html b/lemur/static/app/angular/notifications/notification/notification.tpl.html index c8e9da70..4a9dfcf4 100644 --- a/lemur/static/app/angular/notifications/notification/notification.tpl.html +++ b/lemur/static/app/angular/notifications/notification/notification.tpl.html @@ -56,7 +56,7 @@ uib-typeahead="certificate.name for certificate in certificateService.findCertificatesByName($viewValue)" typeahead-loading="loadingCertificates" class="form-control input-md" typeahead-on-select="notification.attachCertificate($item)" typeahead-wait-ms="500"> - diff --git a/lemur/static/app/angular/users/user/user.tpl.html b/lemur/static/app/angular/users/user/user.tpl.html index 9875b57b..be98795d 100644 --- a/lemur/static/app/angular/users/user/user.tpl.html +++ b/lemur/static/app/angular/users/user/user.tpl.html @@ -62,7 +62,7 @@ uib-tooltip="Roles control which authorities a user can issue certificates from" tooltip-trigger="focus" tooltip-placement="top"> - diff --git a/lemur/tests/test_domains.py b/lemur/tests/test_domains.py index 5757c91d..b585c870 100644 --- a/lemur/tests/test_domains.py +++ b/lemur/tests/test_domains.py @@ -1,127 +1,87 @@ +import pytest + from lemur.domains.views import * # noqa -def test_domain_get(client): - assert client.get(api.url_for(Domains, domain_id=1)).status_code == 401 +from .vectors import VALID_ADMIN_HEADER_TOKEN, VALID_USER_HEADER_TOKEN -def test_domain_post(client): - assert client.post(api.url_for(Domains, domain_id=1), data={}).status_code == 405 +@pytest.mark.parametrize("token,status", [ + (VALID_USER_HEADER_TOKEN, 404), + (VALID_ADMIN_HEADER_TOKEN, 404), + ('', 401) +]) +def test_domain_get(client, token, status): + assert client.get(api.url_for(Domains, domain_id=1), headers=token).status_code == status -def test_domain_put(client): - assert client.put(api.url_for(Domains, domain_id=1), data={}).status_code == 401 +@pytest.mark.parametrize("token,status", [ + (VALID_USER_HEADER_TOKEN, 405), + (VALID_ADMIN_HEADER_TOKEN, 405), + ('', 405) +]) +def test_domain_post_(client, token, status): + assert client.post(api.url_for(Domains, domain_id=1), data={}, headers=token).status_code == status -def test_domain_delete(client): - assert client.delete(api.url_for(Domains, domain_id=1)).status_code == 405 +@pytest.mark.parametrize("token,status", [ + (VALID_USER_HEADER_TOKEN, 400), + (VALID_ADMIN_HEADER_TOKEN, 400), + ('', 401) +]) +def test_domain_put(client, token, status): + assert client.put(api.url_for(Domains, domain_id=1), data={}, headers=token).status_code == status -def test_domain_patch(client): - assert client.patch(api.url_for(Domains, domain_id=1), data={}).status_code == 405 +@pytest.mark.parametrize("token,status", [ + (VALID_USER_HEADER_TOKEN, 405), + (VALID_ADMIN_HEADER_TOKEN, 405), + ('', 405) +]) +def test_domain_delete(client, token, status): + assert client.delete(api.url_for(Domains, domain_id=1), headers=token).status_code == status -VALID_USER_HEADER_TOKEN = { - 'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyMzMzNjksInN1YiI6MSwiZXhwIjoxNTIxNTQ2OTY5fQ.1qCi0Ip7mzKbjNh0tVd3_eJOrae3rNa_9MCVdA4WtQI'} +@pytest.mark.parametrize("token,status", [ + (VALID_USER_HEADER_TOKEN, 405), + (VALID_ADMIN_HEADER_TOKEN, 405), + ('', 405) +]) +def test_domain_patch(client, token, status): + assert client.patch(api.url_for(Domains, domain_id=1), data={}, headers=token).status_code == status -def test_auth_domain_get(client): - assert client.get(api.url_for(Domains, domain_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200 +@pytest.mark.parametrize("token,status", [ + (VALID_USER_HEADER_TOKEN, 400), + (VALID_ADMIN_HEADER_TOKEN, 400), + ('', 401) +]) +def test_domain_list_post_(client, token, status): + assert client.post(api.url_for(DomainsList), data={}, headers=token).status_code == status -def test_auth_domain_post_(client): - assert client.post(api.url_for(Domains, domain_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 +@pytest.mark.parametrize("token,status", [ + (VALID_USER_HEADER_TOKEN, 200), + (VALID_ADMIN_HEADER_TOKEN, 200), + ('', 401) +]) +def test_domain_list_get(client, token, status): + assert client.get(api.url_for(DomainsList), headers=token).status_code == status -def test_auth_domain_put(client): - assert client.put(api.url_for(Domains, domain_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 403 +@pytest.mark.parametrize("token,status", [ + (VALID_USER_HEADER_TOKEN, 405), + (VALID_ADMIN_HEADER_TOKEN, 405), + ('', 405) +]) +def test_domain_list_delete(client, token, status): + assert client.delete(api.url_for(DomainsList), headers=token).status_code == status -def test_auth_domain_delete(client): - assert client.delete(api.url_for(Domains, domain_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 405 - - -def test_auth_domain_patch(client): - assert client.patch(api.url_for(Domains, domain_id=1), data={}, headers=VALID_USER_HEADER_TOKEN).status_code == 405 - - -VALID_ADMIN_HEADER_TOKEN = { - 'Authorization': 'Basic ' + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MzUyNTAyMTgsInN1YiI6MiwiZXhwIjoxNTIxNTYzODE4fQ.6mbq4-Ro6K5MmuNiTJBB153RDhlM5LGJBjI7GBKkfqA'} - - -def test_admin_domain_get(client): - assert client.get(api.url_for(Domains, domain_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 - - -def test_admin_domain_post(client): - assert client.post(api.url_for(Domains, domain_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 - - -def test_admin_domain_put(client): - assert client.put(api.url_for(Domains, domain_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 400 - - -def test_admin_domain_delete(client): - assert client.delete(api.url_for(Domains, domain_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 - - -def test_admin_domain_patch(client): - assert client.patch(api.url_for(Domains, domain_id=1), data={}, headers=VALID_ADMIN_HEADER_TOKEN).status_code == 405 - - -def test_domains_get(client): - assert client.get(api.url_for(DomainsList)).status_code == 401 - - -def test_domains_post(client): - assert client.post(api.url_for(DomainsList), data={}).status_code == 401 - - -def test_domains_put(client): - assert client.put(api.url_for(DomainsList), data={}).status_code == 405 - - -def test_domains_delete(client): - assert client.delete(api.url_for(DomainsList)).status_code == 405 - - -def test_domains_patch(client): - assert client.patch(api.url_for(DomainsList), data={}).status_code == 405 - - -def test_auth_domains_get(client): - assert client.get(api.url_for(DomainsList), headers=VALID_USER_HEADER_TOKEN).status_code == 200 - - -def test_admin_domains_get(client): - resp = client.get(api.url_for(DomainsList), headers=VALID_ADMIN_HEADER_TOKEN) - assert resp.status_code == 200 - assert resp.json == {'items': [], 'total': 0} - - -def test_certificate_domains_get(client): - assert client.get(api.url_for(CertificateDomains, certificate_id=1)).status_code == 401 - - -def test_certificate_domains_post(client): - assert client.post(api.url_for(CertificateDomains, certificate_id=1), data={}).status_code == 405 - - -def test_certificate_domains_put(client): - assert client.put(api.url_for(CertificateDomains, certificate_id=1), data={}).status_code == 405 - - -def test_certificate_domains_delete(client): - assert client.delete(api.url_for(CertificateDomains, certificate_id=1)).status_code == 405 - - -def test_certificate_domains_patch(client): - assert client.patch(api.url_for(CertificateDomains, certificate_id=1), data={}).status_code == 405 - - -def test_auth_certificate_domains_get(client): - assert client.get(api.url_for(CertificateDomains, certificate_id=1), headers=VALID_USER_HEADER_TOKEN).status_code == 200 - - -def test_admin_certificate_domains_get(client): - assert client.get(api.url_for(CertificateDomains, certificate_id=1), headers=VALID_ADMIN_HEADER_TOKEN).status_code == 200 +@pytest.mark.parametrize("token,status", [ + (VALID_USER_HEADER_TOKEN, 405), + (VALID_ADMIN_HEADER_TOKEN, 405), + ('', 405) +]) +def test_domain_list_patch(client, token, status): + assert client.patch(api.url_for(DomainsList), data={}, headers=token).status_code == status