Remove db layer hard-code permission checks for floating_ip_dns
This patches removes db layer hard-code permission checks for floating_ip_dns. Partially implements bp nova-api-policy-final-part SecurityImpact UpgradeImpact: Due to the db layer permission checks are removed, we need to add default policy rule into policy file. In this patch, "os_compute_api:os-floating-ip-dns:domain:update" and "os_compute_api:os-floating-ip-dns:domain:delete" were updated with a default rule. Admin will be notfied to update their policy configure file to keep the behavior as before. Change-Id: Ibddf3529a219cb9a0c1d4cfdb89327b53454c436
This commit is contained in:
parent
686621e2ad
commit
4d6a50ab3e
@ -272,6 +272,8 @@
|
|||||||
"os_compute_api:os-flavor-manage": "rule:admin_api",
|
"os_compute_api:os-flavor-manage": "rule:admin_api",
|
||||||
"os_compute_api:os-floating-ip-dns": "",
|
"os_compute_api:os-floating-ip-dns": "",
|
||||||
"os_compute_api:os-floating-ip-dns:discoverable": "",
|
"os_compute_api:os-floating-ip-dns:discoverable": "",
|
||||||
|
"os_compute_api:os-floating-ip-dns:domain:update": "rule:admin_api",
|
||||||
|
"os_compute_api:os-floating-ip-dns:domain:delete": "rule:admin_api",
|
||||||
"os_compute_api:os-floating-ip-pools": "",
|
"os_compute_api:os-floating-ip-pools": "",
|
||||||
"os_compute_api:os-floating-ip-pools:discoverable": "",
|
"os_compute_api:os-floating-ip-pools:discoverable": "",
|
||||||
"os_compute_api:os-floating-ips": "",
|
"os_compute_api:os-floating-ips": "",
|
||||||
|
@ -19,6 +19,7 @@ import webob
|
|||||||
|
|
||||||
from nova.api.openstack import extensions
|
from nova.api.openstack import extensions
|
||||||
from nova.api.openstack import wsgi
|
from nova.api.openstack import wsgi
|
||||||
|
from nova import context as nova_context
|
||||||
from nova import exception
|
from nova import exception
|
||||||
from nova.i18n import _
|
from nova.i18n import _
|
||||||
from nova import network
|
from nova import network
|
||||||
@ -105,6 +106,9 @@ class FloatingIPDNSDomainController(object):
|
|||||||
"""Add or modify domain entry."""
|
"""Add or modify domain entry."""
|
||||||
context = req.environ['nova.context']
|
context = req.environ['nova.context']
|
||||||
authorize(context)
|
authorize(context)
|
||||||
|
# NOTE(shaohe-feng): back-compatible with db layer hard-code
|
||||||
|
# admin permission checks.
|
||||||
|
nova_context.require_admin_context(context)
|
||||||
fqdomain = _unquote_domain(id)
|
fqdomain = _unquote_domain(id)
|
||||||
try:
|
try:
|
||||||
entry = body['domain_entry']
|
entry = body['domain_entry']
|
||||||
@ -138,6 +142,9 @@ class FloatingIPDNSDomainController(object):
|
|||||||
"""Delete the domain identified by id."""
|
"""Delete the domain identified by id."""
|
||||||
context = req.environ['nova.context']
|
context = req.environ['nova.context']
|
||||||
authorize(context)
|
authorize(context)
|
||||||
|
# NOTE(shaohe-feng): back-compatible with db layer hard-code
|
||||||
|
# admin permission checks.
|
||||||
|
nova_context.require_admin_context(context)
|
||||||
domain = _unquote_domain(id)
|
domain = _unquote_domain(id)
|
||||||
|
|
||||||
# Delete the whole domain
|
# Delete the whole domain
|
||||||
|
@ -111,7 +111,7 @@ class FloatingIPDNSDomainController(wsgi.Controller):
|
|||||||
def update(self, req, id, body):
|
def update(self, req, id, body):
|
||||||
"""Add or modify domain entry."""
|
"""Add or modify domain entry."""
|
||||||
context = req.environ['nova.context']
|
context = req.environ['nova.context']
|
||||||
authorize(context)
|
authorize(context, action="domain:update")
|
||||||
fqdomain = _unquote_domain(id)
|
fqdomain = _unquote_domain(id)
|
||||||
entry = body['domain_entry']
|
entry = body['domain_entry']
|
||||||
scope = entry['scope']
|
scope = entry['scope']
|
||||||
@ -146,7 +146,7 @@ class FloatingIPDNSDomainController(wsgi.Controller):
|
|||||||
def delete(self, req, id):
|
def delete(self, req, id):
|
||||||
"""Delete the domain identified by id."""
|
"""Delete the domain identified by id."""
|
||||||
context = req.environ['nova.context']
|
context = req.environ['nova.context']
|
||||||
authorize(context)
|
authorize(context, action="domain:delete")
|
||||||
domain = _unquote_domain(id)
|
domain = _unquote_domain(id)
|
||||||
|
|
||||||
# Delete the whole domain
|
# Delete the whole domain
|
||||||
|
@ -1072,7 +1072,6 @@ def _dnsdomain_get_or_create(context, session, fqdomain):
|
|||||||
return domain_ref
|
return domain_ref
|
||||||
|
|
||||||
|
|
||||||
@require_admin_context
|
|
||||||
def dnsdomain_register_for_zone(context, fqdomain, zone):
|
def dnsdomain_register_for_zone(context, fqdomain, zone):
|
||||||
session = get_session()
|
session = get_session()
|
||||||
with session.begin():
|
with session.begin():
|
||||||
@ -1082,7 +1081,6 @@ def dnsdomain_register_for_zone(context, fqdomain, zone):
|
|||||||
session.add(domain_ref)
|
session.add(domain_ref)
|
||||||
|
|
||||||
|
|
||||||
@require_admin_context
|
|
||||||
def dnsdomain_register_for_project(context, fqdomain, project):
|
def dnsdomain_register_for_project(context, fqdomain, project):
|
||||||
session = get_session()
|
session = get_session()
|
||||||
with session.begin():
|
with session.begin():
|
||||||
@ -1092,7 +1090,6 @@ def dnsdomain_register_for_project(context, fqdomain, project):
|
|||||||
session.add(domain_ref)
|
session.add(domain_ref)
|
||||||
|
|
||||||
|
|
||||||
@require_admin_context
|
|
||||||
def dnsdomain_unregister(context, fqdomain):
|
def dnsdomain_unregister(context, fqdomain):
|
||||||
model_query(context, models.DNSDomain).\
|
model_query(context, models.DNSDomain).\
|
||||||
filter_by(domain=fqdomain).\
|
filter_by(domain=fqdomain).\
|
||||||
|
@ -143,6 +143,7 @@ class FloatingIpDNSTestV21(test.TestCase):
|
|||||||
self.domain_controller = temp
|
self.domain_controller = temp
|
||||||
self.entry_controller = self.floating_ip_dns.\
|
self.entry_controller = self.floating_ip_dns.\
|
||||||
FloatingIPDNSEntryController()
|
FloatingIPDNSEntryController()
|
||||||
|
self.admin_req = fakes.HTTPRequest.blank('', use_admin_context=True)
|
||||||
self.req = fakes.HTTPRequest.blank('')
|
self.req = fakes.HTTPRequest.blank('')
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
@ -211,25 +212,28 @@ class FloatingIpDNSTestV21(test.TestCase):
|
|||||||
self.assertEqual(entry['dns_entry']['ip'], test_ipv4_address)
|
self.assertEqual(entry['dns_entry']['ip'], test_ipv4_address)
|
||||||
|
|
||||||
def test_create_domain(self):
|
def test_create_domain(self):
|
||||||
|
self._test_create_domain(self.req)
|
||||||
|
|
||||||
|
def _test_create_domain(self, req):
|
||||||
body = {'domain_entry':
|
body = {'domain_entry':
|
||||||
{'scope': 'private',
|
{'scope': 'private',
|
||||||
'project': 'testproject'}}
|
'project': 'testproject'}}
|
||||||
self.assertRaises(self._bad_request(),
|
self.assertRaises(self._bad_request(),
|
||||||
self.domain_controller.update,
|
self.domain_controller.update, req,
|
||||||
self.req, _quote_domain(domain), body=body)
|
_quote_domain(domain), body=body)
|
||||||
|
|
||||||
body = {'domain_entry':
|
body = {'domain_entry':
|
||||||
{'scope': 'public',
|
{'scope': 'public',
|
||||||
'availability_zone': 'zone1'}}
|
'availability_zone': 'zone1'}}
|
||||||
self.assertRaises(self._bad_request(),
|
self.assertRaises(self._bad_request(),
|
||||||
self.domain_controller.update,
|
self.domain_controller.update, req,
|
||||||
self.req, _quote_domain(domain), body=body)
|
_quote_domain(domain), body=body)
|
||||||
|
|
||||||
body = {'domain_entry':
|
body = {'domain_entry':
|
||||||
{'scope': 'public',
|
{'scope': 'public',
|
||||||
'project': 'testproject'}}
|
'project': 'testproject'}}
|
||||||
entry = self.domain_controller.update(self.req, _quote_domain(domain),
|
entry = self.domain_controller.update(req,
|
||||||
body=body)
|
_quote_domain(domain), body=body)
|
||||||
self.assertEqual(entry['domain_entry']['domain'], domain)
|
self.assertEqual(entry['domain_entry']['domain'], domain)
|
||||||
self.assertEqual(entry['domain_entry']['scope'], 'public')
|
self.assertEqual(entry['domain_entry']['scope'], 'public')
|
||||||
self.assertEqual(entry['domain_entry']['project'], 'testproject')
|
self.assertEqual(entry['domain_entry']['project'], 'testproject')
|
||||||
@ -237,8 +241,8 @@ class FloatingIpDNSTestV21(test.TestCase):
|
|||||||
body = {'domain_entry':
|
body = {'domain_entry':
|
||||||
{'scope': 'private',
|
{'scope': 'private',
|
||||||
'availability_zone': 'zone1'}}
|
'availability_zone': 'zone1'}}
|
||||||
entry = self.domain_controller.update(self.req, _quote_domain(domain),
|
entry = self.domain_controller.update(req,
|
||||||
body=body)
|
_quote_domain(domain), body=body)
|
||||||
self.assertEqual(entry['domain_entry']['domain'], domain)
|
self.assertEqual(entry['domain_entry']['domain'], domain)
|
||||||
self.assertEqual(entry['domain_entry']['scope'], 'private')
|
self.assertEqual(entry['domain_entry']['scope'], 'private')
|
||||||
self.assertEqual(entry['domain_entry']['availability_zone'], 'zone1')
|
self.assertEqual(entry['domain_entry']['availability_zone'], 'zone1')
|
||||||
@ -270,6 +274,9 @@ class FloatingIpDNSTestV21(test.TestCase):
|
|||||||
name)
|
name)
|
||||||
|
|
||||||
def test_delete_domain(self):
|
def test_delete_domain(self):
|
||||||
|
self._test_delete_domain(self.req)
|
||||||
|
|
||||||
|
def _test_delete_domain(self, req):
|
||||||
calls = []
|
calls = []
|
||||||
|
|
||||||
def network_delete_dns_domain(fakeself, context, fqdomain):
|
def network_delete_dns_domain(fakeself, context, fqdomain):
|
||||||
@ -278,20 +285,25 @@ class FloatingIpDNSTestV21(test.TestCase):
|
|||||||
self.stubs.Set(network.api.API, "delete_dns_domain",
|
self.stubs.Set(network.api.API, "delete_dns_domain",
|
||||||
network_delete_dns_domain)
|
network_delete_dns_domain)
|
||||||
|
|
||||||
res = self.domain_controller.delete(self.req, _quote_domain(domain))
|
res = self.domain_controller.delete(req,
|
||||||
|
_quote_domain(domain))
|
||||||
|
|
||||||
self._check_status(202, res, self.domain_controller.delete)
|
self._check_status(202, res, self.domain_controller.delete)
|
||||||
self.assertEqual([domain], calls)
|
self.assertEqual([domain], calls)
|
||||||
|
|
||||||
def test_delete_domain_notfound(self):
|
def test_delete_domain_notfound(self):
|
||||||
|
self._test_delete_domain_notfound(self.req)
|
||||||
|
|
||||||
|
def _test_delete_domain_notfound(self, req):
|
||||||
def delete_dns_domain_notfound(fakeself, context, fqdomain):
|
def delete_dns_domain_notfound(fakeself, context, fqdomain):
|
||||||
raise exception.NotFound
|
raise exception.NotFound
|
||||||
|
|
||||||
self.stubs.Set(network.api.API, "delete_dns_domain",
|
self.stubs.Set(network.api.API, "delete_dns_domain",
|
||||||
delete_dns_domain_notfound)
|
delete_dns_domain_notfound)
|
||||||
|
|
||||||
self.assertRaises(webob.exc.HTTPNotFound,
|
self.assertRaises(
|
||||||
self.domain_controller.delete, self.req, _quote_domain(domain))
|
webob.exc.HTTPNotFound, self.domain_controller.delete,
|
||||||
|
req, _quote_domain(domain))
|
||||||
|
|
||||||
def test_modify(self):
|
def test_modify(self):
|
||||||
body = {'dns_entry':
|
body = {'dns_entry':
|
||||||
@ -329,7 +341,7 @@ class FloatingIpDNSTestV21(test.TestCase):
|
|||||||
with mock.patch.object(network.api.API, 'delete_dns_domain',
|
with mock.patch.object(network.api.API, 'delete_dns_domain',
|
||||||
side_effect=NotImplementedError()):
|
side_effect=NotImplementedError()):
|
||||||
self.assertRaises(webob.exc.HTTPNotImplemented,
|
self.assertRaises(webob.exc.HTTPNotImplemented,
|
||||||
self.domain_controller.delete, self.req,
|
self.domain_controller.delete, self.admin_req,
|
||||||
_quote_domain(domain))
|
_quote_domain(domain))
|
||||||
|
|
||||||
def test_not_implemented_create_domain(self):
|
def test_not_implemented_create_domain(self):
|
||||||
@ -339,8 +351,8 @@ class FloatingIpDNSTestV21(test.TestCase):
|
|||||||
with mock.patch.object(network.api.API, 'create_private_dns_domain',
|
with mock.patch.object(network.api.API, 'create_private_dns_domain',
|
||||||
side_effect=NotImplementedError()):
|
side_effect=NotImplementedError()):
|
||||||
self.assertRaises(webob.exc.HTTPNotImplemented,
|
self.assertRaises(webob.exc.HTTPNotImplemented,
|
||||||
self.domain_controller.update,
|
self.domain_controller.update, self.admin_req,
|
||||||
self.req, _quote_domain(domain), body=body)
|
_quote_domain(domain), body=body)
|
||||||
|
|
||||||
def test_not_implemented_dns_domains_list(self):
|
def test_not_implemented_dns_domains_list(self):
|
||||||
with mock.patch.object(network.api.API, 'get_dns_domains',
|
with mock.patch.object(network.api.API, 'get_dns_domains',
|
||||||
@ -358,6 +370,28 @@ class FloatingIpDNSTestV2(FloatingIpDNSTestV21):
|
|||||||
def _bad_request(self):
|
def _bad_request(self):
|
||||||
return webob.exc.HTTPUnprocessableEntity
|
return webob.exc.HTTPUnprocessableEntity
|
||||||
|
|
||||||
|
def test_update_dns_domain_with_non_admin(self):
|
||||||
|
body = {'domain_entry':
|
||||||
|
{'scope': 'private',
|
||||||
|
'project': 'testproject'}}
|
||||||
|
self.assertRaises(exception.AdminRequired,
|
||||||
|
self.domain_controller.update,
|
||||||
|
self.req, _quote_domain(domain), body=body)
|
||||||
|
|
||||||
|
def test_delete_dns_domain_with_non_admin(self):
|
||||||
|
self.assertRaises(exception.AdminRequired,
|
||||||
|
self.domain_controller.delete,
|
||||||
|
self.req, _quote_domain(domain))
|
||||||
|
|
||||||
|
def test_create_domain(self):
|
||||||
|
self._test_create_domain(self.admin_req)
|
||||||
|
|
||||||
|
def test_delete_domain(self):
|
||||||
|
self._test_delete_domain(self.admin_req)
|
||||||
|
|
||||||
|
def test_delete_domain_notfound(self):
|
||||||
|
self._test_delete_domain_notfound(self.admin_req)
|
||||||
|
|
||||||
|
|
||||||
class FloatingIPDNSDomainPolicyEnforcementV21(test.NoDBTestCase):
|
class FloatingIPDNSDomainPolicyEnforcementV21(test.NoDBTestCase):
|
||||||
|
|
||||||
@ -369,14 +403,18 @@ class FloatingIPDNSDomainPolicyEnforcementV21(test.NoDBTestCase):
|
|||||||
self.req = fakes.HTTPRequest.blank('')
|
self.req = fakes.HTTPRequest.blank('')
|
||||||
|
|
||||||
def test_get_floating_ip_dns_policy_failed(self):
|
def test_get_floating_ip_dns_policy_failed(self):
|
||||||
|
rule_name = "os_compute_api:os-floating-ip-dns"
|
||||||
|
self.policy.set_rules({rule_name: "project:non_fake"})
|
||||||
exc = self.assertRaises(
|
exc = self.assertRaises(
|
||||||
exception.PolicyNotAuthorized,
|
exception.PolicyNotAuthorized,
|
||||||
self.controller.index, self.req)
|
self.controller.index, self.req)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
"Policy doesn't allow %s to be performed." % self.rule_name,
|
"Policy doesn't allow %s to be performed." % rule_name,
|
||||||
exc.format_message())
|
exc.format_message())
|
||||||
|
|
||||||
def test_update_floating_ip_dns_policy_failed(self):
|
def test_update_floating_ip_dns_policy_failed(self):
|
||||||
|
rule_name = "os_compute_api:os-floating-ip-dns:domain:update"
|
||||||
|
self.policy.set_rules({rule_name: "project:non_fake"})
|
||||||
body = {'domain_entry':
|
body = {'domain_entry':
|
||||||
{'scope': 'public',
|
{'scope': 'public',
|
||||||
'project': 'testproject'}}
|
'project': 'testproject'}}
|
||||||
@ -384,15 +422,17 @@ class FloatingIPDNSDomainPolicyEnforcementV21(test.NoDBTestCase):
|
|||||||
exception.PolicyNotAuthorized,
|
exception.PolicyNotAuthorized,
|
||||||
self.controller.update, self.req, _quote_domain(domain), body=body)
|
self.controller.update, self.req, _quote_domain(domain), body=body)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
"Policy doesn't allow %s to be performed." % self.rule_name,
|
"Policy doesn't allow %s to be performed." % rule_name,
|
||||||
exc.format_message())
|
exc.format_message())
|
||||||
|
|
||||||
def test_delete_floating_ip_dns_policy_failed(self):
|
def test_delete_floating_ip_dns_policy_failed(self):
|
||||||
|
rule_name = "os_compute_api:os-floating-ip-dns:domain:delete"
|
||||||
|
self.policy.set_rules({rule_name: "project:non_fake"})
|
||||||
exc = self.assertRaises(
|
exc = self.assertRaises(
|
||||||
exception.PolicyNotAuthorized,
|
exception.PolicyNotAuthorized,
|
||||||
self.controller.delete, self.req, _quote_domain(domain))
|
self.controller.delete, self.req, _quote_domain(domain))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
"Policy doesn't allow %s to be performed." % self.rule_name,
|
"Policy doesn't allow %s to be performed." % rule_name,
|
||||||
exc.format_message())
|
exc.format_message())
|
||||||
|
|
||||||
|
|
||||||
|
@ -237,6 +237,8 @@ policy_data = """
|
|||||||
"os_compute_api:os-flavors:discoverable": "",
|
"os_compute_api:os-flavors:discoverable": "",
|
||||||
"compute_extension:floating_ip_dns": "",
|
"compute_extension:floating_ip_dns": "",
|
||||||
"os_compute_api:os-floating-ip-dns": "",
|
"os_compute_api:os-floating-ip-dns": "",
|
||||||
|
"os_compute_api:os-floating-ip-dns:domain:update": "",
|
||||||
|
"os_compute_api:os-floating-ip-dns:domain:delete": "",
|
||||||
"compute_extension:floating_ip_pools": "",
|
"compute_extension:floating_ip_pools": "",
|
||||||
"os_compute_api:os-floating-ip-pools": "",
|
"os_compute_api:os-floating-ip-pools": "",
|
||||||
"compute_extension:floating_ips": "",
|
"compute_extension:floating_ips": "",
|
||||||
|
@ -3188,21 +3188,14 @@ class FloatingIPTestCase(test.TestCase):
|
|||||||
name1, zone)
|
name1, zone)
|
||||||
|
|
||||||
def test_floating_dns_domains_public(self):
|
def test_floating_dns_domains_public(self):
|
||||||
zone1 = "testzone"
|
|
||||||
domain1 = "example.org"
|
domain1 = "example.org"
|
||||||
domain2 = "example.com"
|
domain2 = "example.com"
|
||||||
address1 = '10.10.10.10'
|
address1 = '10.10.10.10'
|
||||||
entryname = 'testentry'
|
entryname = 'testentry'
|
||||||
|
|
||||||
context_admin = context.RequestContext('testuser', 'testproject',
|
self.network.create_public_dns_domain(self.context, domain1,
|
||||||
is_admin=True)
|
|
||||||
|
|
||||||
self.assertRaises(exception.AdminRequired,
|
|
||||||
self.network.create_public_dns_domain, self.context,
|
|
||||||
domain1, zone1)
|
|
||||||
self.network.create_public_dns_domain(context_admin, domain1,
|
|
||||||
'testproject')
|
'testproject')
|
||||||
self.network.create_public_dns_domain(context_admin, domain2,
|
self.network.create_public_dns_domain(self.context, domain2,
|
||||||
'fakeproject')
|
'fakeproject')
|
||||||
|
|
||||||
domains = self.network.get_dns_domains(self.context)
|
domains = self.network.get_dns_domains(self.context)
|
||||||
@ -3219,11 +3212,8 @@ class FloatingIPTestCase(test.TestCase):
|
|||||||
self.assertEqual(len(entries), 1)
|
self.assertEqual(len(entries), 1)
|
||||||
self.assertEqual(entries[0], address1)
|
self.assertEqual(entries[0], address1)
|
||||||
|
|
||||||
self.assertRaises(exception.AdminRequired,
|
self.network.delete_dns_domain(self.context, domain1)
|
||||||
self.network.delete_dns_domain, self.context,
|
self.network.delete_dns_domain(self.context, domain2)
|
||||||
domain1)
|
|
||||||
self.network.delete_dns_domain(context_admin, domain1)
|
|
||||||
self.network.delete_dns_domain(context_admin, domain2)
|
|
||||||
|
|
||||||
# Verify that deleting the domain deleted the associated entry
|
# Verify that deleting the domain deleted the associated entry
|
||||||
entries = self.network.get_dns_entries_by_name(self.context,
|
entries = self.network.get_dns_entries_by_name(self.context,
|
||||||
@ -3436,23 +3426,13 @@ class InstanceDNSTestCase(test.TestCase):
|
|||||||
zone1 = 'testzone'
|
zone1 = 'testzone'
|
||||||
domain1 = 'example.org'
|
domain1 = 'example.org'
|
||||||
|
|
||||||
context_admin = context.RequestContext('testuser', 'testproject',
|
self.network.create_private_dns_domain(self.context, domain1, zone1)
|
||||||
is_admin=True)
|
|
||||||
|
|
||||||
self.assertRaises(exception.AdminRequired,
|
|
||||||
self.network.create_private_dns_domain, self.context,
|
|
||||||
domain1, zone1)
|
|
||||||
|
|
||||||
self.network.create_private_dns_domain(context_admin, domain1, zone1)
|
|
||||||
domains = self.network.get_dns_domains(self.context)
|
domains = self.network.get_dns_domains(self.context)
|
||||||
self.assertEqual(len(domains), 1)
|
self.assertEqual(len(domains), 1)
|
||||||
self.assertEqual(domains[0]['domain'], domain1)
|
self.assertEqual(domains[0]['domain'], domain1)
|
||||||
self.assertEqual(domains[0]['availability_zone'], zone1)
|
self.assertEqual(domains[0]['availability_zone'], zone1)
|
||||||
|
|
||||||
self.assertRaises(exception.AdminRequired,
|
self.network.delete_dns_domain(self.context, domain1)
|
||||||
self.network.delete_dns_domain, self.context,
|
|
||||||
domain1)
|
|
||||||
self.network.delete_dns_domain(context_admin, domain1)
|
|
||||||
|
|
||||||
|
|
||||||
domain1 = "example.org"
|
domain1 = "example.org"
|
||||||
|
Loading…
Reference in New Issue
Block a user