From 9cb5f547dc6f3242edf393928dbc14b7cbfbbdd4 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 19 Jan 2012 15:30:55 -0800 Subject: Remove admin_only ext attr in favor of authz Working on blueprint separate-nova-adminapi. This removes the admin_only extension attribute and the allow_admin_api flag. The approach we're going for now is to load all extensions, but to set an admin-only rule in our policy file for those extensions that should be limited to just admin users. Now that all of our admin api code has been moved to extensions, in order to prevent admin api code from being loaded, simply remove it from the extension list. Change-Id: Ic574e06af44922ba764013b769077fc5099fd1a2 --- nova/api/mapper.py | 3 -- nova/api/openstack/compute/__init__.py | 3 -- nova/api/openstack/compute/contrib/accounts.py | 17 ++----- .../api/openstack/compute/contrib/admin_actions.py | 12 ++++- nova/api/openstack/compute/contrib/cloudpipe.py | 6 ++- .../openstack/compute/contrib/console_output.py | 4 +- nova/api/openstack/compute/contrib/consoles.py | 2 + .../openstack/compute/contrib/createserverext.py | 7 ++- .../openstack/compute/contrib/deferred_delete.py | 5 +- nova/api/openstack/compute/contrib/disk_config.py | 57 +++++++++++++--------- .../openstack/compute/contrib/extended_status.py | 48 +++++++++--------- .../openstack/compute/contrib/flavorextraspecs.py | 12 ++++- .../openstack/compute/contrib/floating_ip_dns.py | 8 +++ .../openstack/compute/contrib/floating_ip_pools.py | 2 + nova/api/openstack/compute/contrib/floating_ips.py | 7 +++ nova/api/openstack/compute/contrib/hosts.py | 5 +- nova/api/openstack/compute/contrib/keypairs.py | 6 +++ nova/api/openstack/compute/contrib/multinic.py | 7 ++- nova/api/openstack/compute/contrib/networks.py | 6 ++- nova/api/openstack/compute/contrib/quotas.py | 6 +++ nova/api/openstack/compute/contrib/rescue.py | 3 ++ .../openstack/compute/contrib/security_groups.py | 9 ++++ .../compute/contrib/server_action_list.py | 3 +- .../compute/contrib/server_diagnostics.py | 3 +- .../compute/contrib/simple_tenant_usage.py | 4 +- nova/api/openstack/compute/contrib/users.py | 16 +++--- .../compute/contrib/virtual_interfaces.py | 2 + .../compute/contrib/virtual_storage_arrays.py | 15 +++++- nova/api/openstack/compute/contrib/volumes.py | 15 +++++- nova/api/openstack/compute/contrib/volumetypes.py | 16 +++++- nova/api/openstack/compute/contrib/zones.py | 15 ++++-- nova/api/openstack/compute/servers.py | 2 +- nova/api/openstack/extensions.py | 33 ++++++++----- 33 files changed, 246 insertions(+), 113 deletions(-) (limited to 'nova/api') diff --git a/nova/api/mapper.py b/nova/api/mapper.py index cd26e06ee..aaa59f97f 100644 --- a/nova/api/mapper.py +++ b/nova/api/mapper.py @@ -32,9 +32,6 @@ from nova import wsgi as base_wsgi LOG = logging.getLogger('nova.api.openstack.compute') FLAGS = flags.FLAGS -flags.DEFINE_bool('allow_admin_api', - False, - 'When True, this API service will accept admin operations.') flags.DEFINE_bool('allow_instance_snapshots', True, 'When True, this API service will permit instance snapshot operations.') diff --git a/nova/api/openstack/compute/__init__.py b/nova/api/openstack/compute/__init__.py index 9e0509e71..f074ac941 100644 --- a/nova/api/openstack/compute/__init__.py +++ b/nova/api/openstack/compute/__init__.py @@ -42,9 +42,6 @@ from nova import wsgi as base_wsgi LOG = logging.getLogger('nova.api.openstack.compute') FLAGS = flags.FLAGS -flags.DEFINE_bool('allow_admin_api', - False, - 'When True, this API service will accept admin operations.') flags.DEFINE_bool('allow_instance_snapshots', True, 'When True, this API service will permit instance snapshot operations.') diff --git a/nova/api/openstack/compute/contrib/accounts.py b/nova/api/openstack/compute/contrib/accounts.py index 9253c037d..939912942 100644 --- a/nova/api/openstack/compute/contrib/accounts.py +++ b/nova/api/openstack/compute/contrib/accounts.py @@ -26,6 +26,7 @@ from nova import log as logging FLAGS = flags.FLAGS LOG = logging.getLogger('nova.api.openstack.compute.contrib.accounts') +authorize = extensions.extension_authorizer('compute', 'accounts') class AccountTemplate(xmlutil.TemplateBuilder): @@ -51,23 +52,18 @@ class Controller(object): def __init__(self): self.manager = manager.AuthManager() - def _check_admin(self, context): - """We cannot depend on the db layer to check for admin access - for the auth manager, so we do it here""" - if not context.is_admin: - raise exception.AdminRequired() - def index(self, req): raise webob.exc.HTTPNotImplemented() @wsgi.serializers(xml=AccountTemplate) def show(self, req, id): """Return data about the given account id""" + authorize(req.environ['nova.context']) account = self.manager.get_project(id) return dict(account=_translate_keys(account)) def delete(self, req, id): - self._check_admin(req.environ['nova.context']) + authorize(req.environ['nova.context']) self.manager.delete_project(id) return {} @@ -79,7 +75,7 @@ class Controller(object): @wsgi.serializers(xml=AccountTemplate) def update(self, req, id, body): """This is really create or update.""" - self._check_admin(req.environ['nova.context']) + authorize(req.environ['nova.context']) description = body['account'].get('description') manager = body['account'].get('manager') try: @@ -97,11 +93,8 @@ class Accounts(extensions.ExtensionDescriptor): alias = "os-accounts" namespace = "http://docs.openstack.org/compute/ext/accounts/api/v1.1" updated = "2011-12-23T00:00:00+00:00" - admin_only = True def get_resources(self): #TODO(bcwaldon): This should be prefixed with 'os-' - res = extensions.ResourceExtension('accounts', - Controller()) - + res = extensions.ResourceExtension('accounts', Controller()) return [res] diff --git a/nova/api/openstack/compute/contrib/admin_actions.py b/nova/api/openstack/compute/contrib/admin_actions.py index b44f16a1e..cbd54117d 100644 --- a/nova/api/openstack/compute/contrib/admin_actions.py +++ b/nova/api/openstack/compute/contrib/admin_actions.py @@ -30,6 +30,7 @@ from nova.scheduler import api as scheduler_api FLAGS = flags.FLAGS LOG = logging.getLogger("nova.api.openstack.compute.contrib.admin_actions") +authorize = extensions.extension_authorizer('compute', 'admin_actions') class AdminActionsController(wsgi.Controller): @@ -45,6 +46,7 @@ class AdminActionsController(wsgi.Controller): def _pause(self, req, id, body): """Permit Admins to pause the server""" ctxt = req.environ['nova.context'] + authorize(ctxt) try: server = self.compute_api.get(ctxt, id) self.compute_api.pause(ctxt, server) @@ -63,6 +65,7 @@ class AdminActionsController(wsgi.Controller): def _unpause(self, req, id, body): """Permit Admins to unpause the server""" ctxt = req.environ['nova.context'] + authorize(ctxt) try: server = self.compute_api.get(ctxt, id) self.compute_api.unpause(ctxt, server) @@ -81,6 +84,7 @@ class AdminActionsController(wsgi.Controller): def _suspend(self, req, id, body): """Permit admins to suspend the server""" context = req.environ['nova.context'] + authorize(context) try: server = self.compute_api.get(context, id) self.compute_api.suspend(context, server) @@ -99,6 +103,7 @@ class AdminActionsController(wsgi.Controller): def _resume(self, req, id, body): """Permit admins to resume the server from suspend""" context = req.environ['nova.context'] + authorize(context) try: server = self.compute_api.get(context, id) self.compute_api.resume(context, server) @@ -117,6 +122,7 @@ class AdminActionsController(wsgi.Controller): def _migrate(self, req, id, body): """Permit admins to migrate a server to a new host""" context = req.environ['nova.context'] + authorize(context) try: instance = self.compute_api.get(context, id) self.compute_api.resize(req.environ['nova.context'], instance) @@ -134,6 +140,7 @@ class AdminActionsController(wsgi.Controller): def _reset_network(self, req, id, body): """Permit admins to reset networking on an server""" context = req.environ['nova.context'] + authorize(context) try: instance = self.compute_api.get(context, id) self.compute_api.reset_network(context, instance) @@ -149,6 +156,7 @@ class AdminActionsController(wsgi.Controller): def _inject_network_info(self, req, id, body): """Permit admins to inject network info into a server""" context = req.environ['nova.context'] + authorize(context) try: instance = self.compute_api.get(context, id) self.compute_api.inject_network_info(context, instance) @@ -166,6 +174,7 @@ class AdminActionsController(wsgi.Controller): def _lock(self, req, id, body): """Permit admins to lock a server""" context = req.environ['nova.context'] + authorize(context) try: instance = self.compute_api.get(context, id) self.compute_api.lock(context, instance) @@ -183,6 +192,7 @@ class AdminActionsController(wsgi.Controller): def _unlock(self, req, id, body): """Permit admins to lock a server""" context = req.environ['nova.context'] + authorize(context) try: instance = self.compute_api.get(context, id) self.compute_api.unlock(context, instance) @@ -207,6 +217,7 @@ class AdminActionsController(wsgi.Controller): """ context = req.environ["nova.context"] + authorize(context) try: entity = body["createBackup"] @@ -273,7 +284,6 @@ class Admin_actions(extensions.ExtensionDescriptor): alias = "os-admin-actions" namespace = "http://docs.openstack.org/compute/ext/admin-actions/api/v1.1" updated = "2011-09-20T00:00:00+00:00" - admin_only = True def get_controller_extensions(self): controller = AdminActionsController() diff --git a/nova/api/openstack/compute/contrib/cloudpipe.py b/nova/api/openstack/compute/contrib/cloudpipe.py index 17bfa810b..1cf47a2a9 100644 --- a/nova/api/openstack/compute/contrib/cloudpipe.py +++ b/nova/api/openstack/compute/contrib/cloudpipe.py @@ -32,6 +32,7 @@ from nova import utils FLAGS = flags.FLAGS LOG = logging.getLogger("nova.api.openstack.compute.contrib.cloudpipe") +authorize = extensions.extension_authorizer('compute', 'cloudpipe') class CloudpipeTemplate(xmlutil.TemplateBuilder): @@ -120,6 +121,7 @@ class CloudpipeController(object): """ ctxt = req.environ['nova.context'] + authorize(ctxt) params = body.get('cloudpipe', {}) project_id = params.get('project_id', ctxt.project_id) instance = self._get_cloudpipe_for_project(ctxt, project_id) @@ -137,8 +139,9 @@ class CloudpipeController(object): @wsgi.serializers(xml=CloudpipesTemplate) def index(self, req): - """Show admins the list of running cloudpipe instances.""" + """List running cloudpipe instances.""" context = req.environ['nova.context'] + authorize(context) vpns = [] # TODO(todd): could use compute_api.get_all with admin context? for project in self.auth_manager.get_projects(): @@ -162,7 +165,6 @@ class Cloudpipe(extensions.ExtensionDescriptor): alias = "os-cloudpipe" namespace = "http://docs.openstack.org/compute/ext/cloudpipe/api/v1.1" updated = "2011-12-16T00:00:00+00:00" - admin_only = True def get_resources(self): resources = [] diff --git a/nova/api/openstack/compute/contrib/console_output.py b/nova/api/openstack/compute/contrib/console_output.py index ac578676e..04f975d5b 100644 --- a/nova/api/openstack/compute/contrib/console_output.py +++ b/nova/api/openstack/compute/contrib/console_output.py @@ -26,6 +26,7 @@ from nova.api.openstack import wsgi LOG = logging.getLogger('nova.api.openstack.compute.contrib.console_output') +authorize = extensions.extension_authorizer('compute', 'console_output') class ConsoleOutputController(wsgi.Controller): @@ -37,6 +38,7 @@ class ConsoleOutputController(wsgi.Controller): def get_console_output(self, req, id, body): """Get text console output.""" context = req.environ['nova.context'] + authorize(context) try: instance = self.compute_api.routing_get(context, id) @@ -54,8 +56,6 @@ class ConsoleOutputController(wsgi.Controller): length) except exception.ApiError, e: raise webob.exc.HTTPBadRequest(explanation=e.message) - except exception.NotAuthorized, e: - raise webob.exc.HTTPUnauthorized() return {'output': output} diff --git a/nova/api/openstack/compute/contrib/consoles.py b/nova/api/openstack/compute/contrib/consoles.py index 46e3559ff..c3509e7a8 100644 --- a/nova/api/openstack/compute/contrib/consoles.py +++ b/nova/api/openstack/compute/contrib/consoles.py @@ -24,6 +24,7 @@ from nova.api.openstack import wsgi LOG = logging.getLogger('nova.api.openstack.compute.contrib.console') +authorize = extensions.extension_authorizer('compute', 'consoles') class ConsolesController(wsgi.Controller): @@ -35,6 +36,7 @@ class ConsolesController(wsgi.Controller): def get_vnc_console(self, req, id, body): """Get text console output.""" context = req.environ['nova.context'] + authorize(context) console_type = body['os-getVNCConsole'].get('type') diff --git a/nova/api/openstack/compute/contrib/createserverext.py b/nova/api/openstack/compute/contrib/createserverext.py index 05b0c7d9c..de4ba8dd0 100644 --- a/nova/api/openstack/compute/contrib/createserverext.py +++ b/nova/api/openstack/compute/contrib/createserverext.py @@ -19,13 +19,18 @@ from nova.api.openstack.compute import servers from nova.api.openstack.compute import views +authorize = extensions.soft_extension_authorizer('compute', 'createserverext') + + class ViewBuilder(views.servers.ViewBuilder): """Adds security group output when viewing server details.""" def show(self, request, instance): """Detailed view of a single instance.""" server = super(ViewBuilder, self).show(request, instance) - server["server"]["security_groups"] = self._get_groups(instance) + context = request.environ['nova.context'] + if authorize(context): + server["server"]["security_groups"] = self._get_groups(instance) return server def _get_groups(self, instance): diff --git a/nova/api/openstack/compute/contrib/deferred_delete.py b/nova/api/openstack/compute/contrib/deferred_delete.py index c0164eead..10f776558 100644 --- a/nova/api/openstack/compute/contrib/deferred_delete.py +++ b/nova/api/openstack/compute/contrib/deferred_delete.py @@ -26,6 +26,7 @@ from nova import log as logging LOG = logging.getLogger("nova.api.openstack.compute.contrib.deferred-delete") +authorize = extensions.extension_authorizer('compute', 'deferred_delete') class DeferredDeleteController(wsgi.Controller): @@ -36,8 +37,8 @@ class DeferredDeleteController(wsgi.Controller): @wsgi.action('restore') def _restore(self, req, id, body): """Restore a previously deleted instance.""" - context = req.environ["nova.context"] + authorize(context) instance = self.compute_api.get(context, id) try: self.compute_api.restore(context, instance) @@ -49,8 +50,8 @@ class DeferredDeleteController(wsgi.Controller): @wsgi.action('forceDelete') def _force_delete(self, req, id, body): """Force delete of instance before deferred cleanup.""" - context = req.environ["nova.context"] + authorize(context) instance = self.compute_api.get(context, id) try: self.compute_api.force_delete(context, instance) diff --git a/nova/api/openstack/compute/contrib/disk_config.py b/nova/api/openstack/compute/contrib/disk_config.py index cf78b847e..46c09ffa8 100644 --- a/nova/api/openstack/compute/contrib/disk_config.py +++ b/nova/api/openstack/compute/contrib/disk_config.py @@ -28,6 +28,7 @@ ALIAS = 'OS-DCF' XMLNS_DCF = "http://docs.openstack.org/compute/ext/disk_config/api/v1.1" API_DISK_CONFIG = "%s:diskConfig" % ALIAS INTERNAL_DISK_CONFIG = "auto_disk_config" +authorize = extensions.soft_extension_authorizer('compute', 'disk_config') def disk_config_to_api(value): @@ -70,16 +71,16 @@ class ImageDiskConfigController(wsgi.Controller): @wsgi.extends def show(self, req, resp_obj, id): - if 'image' in resp_obj.obj: - context = req.environ['nova.context'] + context = req.environ['nova.context'] + if 'image' in resp_obj.obj and authorize(context): resp_obj.attach(xml=ImageDiskConfigTemplate()) image = resp_obj.obj['image'] self._add_disk_config(context, [image]) @wsgi.extends def detail(self, req, resp_obj): - if 'images' in resp_obj.obj: - context = req.environ['nova.context'] + context = req.environ['nova.context'] + if 'images' in resp_obj.obj and authorize(context): resp_obj.attach(xml=ImagesDiskConfigTemplate()) images = resp_obj.obj['images'] self._add_disk_config(context, images) @@ -102,13 +103,14 @@ class ServersDiskConfigTemplate(xmlutil.TemplateBuilder): class ServerDiskConfigController(wsgi.Controller): def _add_disk_config(self, context, servers): - # Filter out any servers that already have the key set (most likely - # from a remote zone) + # Filter out any servers that already have the key set + # (most likely from a remote zone) servers = [s for s in servers if API_DISK_CONFIG not in s] # Get DB information for servers uuids = [server['id'] for server in servers] - db_servers = db.instance_get_all_by_filters(context, {'uuid': uuids}) + db_servers = db.instance_get_all_by_filters(context, + {'uuid': uuids}) db_servers_by_uuid = dict((s['uuid'], s) for s in db_servers) for server in servers: @@ -117,21 +119,22 @@ class ServerDiskConfigController(wsgi.Controller): value = db_server[INTERNAL_DISK_CONFIG] server[API_DISK_CONFIG] = disk_config_to_api(value) - def _show(self, req, resp_obj): + def _show(self, context, resp_obj): if 'server' in resp_obj.obj: - context = req.environ['nova.context'] resp_obj.attach(xml=ServerDiskConfigTemplate()) server = resp_obj.obj['server'] self._add_disk_config(context, [server]) @wsgi.extends def show(self, req, resp_obj, id): - self._show(req, resp_obj) + context = req.environ['nova.context'] + if authorize(context): + self._show(context, resp_obj) @wsgi.extends def detail(self, req, resp_obj): - if 'servers' in resp_obj.obj: - context = req.environ['nova.context'] + context = req.environ['nova.context'] + if 'servers' in resp_obj.obj and authorize(context): resp_obj.attach(xml=ServersDiskConfigTemplate()) servers = resp_obj.obj['servers'] self._add_disk_config(context, servers) @@ -144,26 +147,34 @@ class ServerDiskConfigController(wsgi.Controller): @wsgi.extends def create(self, req, body): - self._set_disk_config(body['server']) - resp_obj = (yield) - self._show(req, resp_obj) + context = req.environ['nova.context'] + if authorize(context): + self._set_disk_config(body['server']) + resp_obj = (yield) + self._show(context, resp_obj) @wsgi.extends def update(self, req, id, body): - self._set_disk_config(body['server']) - resp_obj = (yield) - self._show(req, resp_obj) + context = req.environ['nova.context'] + if authorize(context): + self._set_disk_config(body['server']) + resp_obj = (yield) + self._show(context, resp_obj) @wsgi.extends(action='rebuild') def _action_rebuild(self, req, id, body): - self._set_disk_config(body['rebuild']) - resp_obj = (yield) - self._show(req, resp_obj) + context = req.environ['nova.context'] + if authorize(context): + self._set_disk_config(body['rebuild']) + resp_obj = (yield) + self._show(context, resp_obj) @wsgi.extends(action='resize') def _action_resize(self, req, id, body): - self._set_disk_config(body['resize']) - yield + context = req.environ['nova.context'] + if authorize(context): + self._set_disk_config(body['resize']) + yield class Disk_config(extensions.ExtensionDescriptor): diff --git a/nova/api/openstack/compute/contrib/extended_status.py b/nova/api/openstack/compute/contrib/extended_status.py index 7479d6f00..4ab7ca756 100644 --- a/nova/api/openstack/compute/contrib/extended_status.py +++ b/nova/api/openstack/compute/contrib/extended_status.py @@ -27,6 +27,7 @@ from nova import log as logging FLAGS = flags.FLAGS LOG = logging.getLogger("nova.api.openstack.compute.contrib.extendedstatus") +authorize = extensions.soft_extension_authorizer('compute', 'extended_status') class ExtendedStatusController(wsgi.Controller): @@ -48,34 +49,34 @@ class ExtendedStatusController(wsgi.Controller): @wsgi.extends def show(self, req, resp_obj, id): context = req.environ['nova.context'] + if authorize(context): + # Attach our slave template to the response object + resp_obj.attach(xml=ExtendedStatusTemplate()) - # Attach our slave template to the response object - resp_obj.attach(xml=ExtendedStatusTemplate()) - - try: - self._get_and_extend_one(context, id, resp_obj.obj['server']) - except exception.NotFound: - explanation = _("Server not found.") - raise exc.HTTPNotFound(explanation=explanation) + try: + self._get_and_extend_one(context, id, resp_obj.obj['server']) + except exception.NotFound: + explanation = _("Server not found.") + raise exc.HTTPNotFound(explanation=explanation) @wsgi.extends def detail(self, req, resp_obj): context = req.environ['nova.context'] - - # Attach our slave template to the response object - resp_obj.attach(xml=ExtendedStatusesTemplate()) - - for server in list(resp_obj.obj['servers']): - try: - self._get_and_extend_one(context, server['id'], server) - except exception.NotFound: - # NOTE(dtroyer): A NotFound exception at this point - # happens because a delete was in progress and the - # server that was present in the original call to - # compute.api.get_all() is no longer present. - # Delete it from the response and move on. - resp_obj.obj['servers'].remove(server) - continue + if authorize(context): + # Attach our slave template to the response object + resp_obj.attach(xml=ExtendedStatusesTemplate()) + + for server in list(resp_obj.obj['servers']): + try: + self._get_and_extend_one(context, server['id'], server) + except exception.NotFound: + # NOTE(dtroyer): A NotFound exception at this point + # happens because a delete was in progress and the + # server that was present in the original call to + # compute.api.get_all() is no longer present. + # Delete it from the response and move on. + resp_obj.obj['servers'].remove(server) + continue class Extended_status(extensions.ExtensionDescriptor): @@ -86,7 +87,6 @@ class Extended_status(extensions.ExtensionDescriptor): namespace = "http://docs.openstack.org/compute/ext/" \ "extended_status/api/v1.1" updated = "2011-11-03T00:00:00+00:00" - admin_only = True def get_controller_extensions(self): controller = ExtendedStatusController() diff --git a/nova/api/openstack/compute/contrib/flavorextraspecs.py b/nova/api/openstack/compute/contrib/flavorextraspecs.py index eafea5d1f..faf023659 100644 --- a/nova/api/openstack/compute/contrib/flavorextraspecs.py +++ b/nova/api/openstack/compute/contrib/flavorextraspecs.py @@ -26,6 +26,9 @@ from nova import db from nova import exception +authorize = extensions.extension_authorizer('compute', 'flavorextraspecs') + + class ExtraSpecsTemplate(xmlutil.TemplateBuilder): def construct(self): return xmlutil.MasterTemplate(xmlutil.make_flat_dict('extra_specs'), 1) @@ -50,12 +53,14 @@ class FlavorExtraSpecsController(object): def index(self, req, flavor_id): """ Returns the list of extra specs for a givenflavor """ context = req.environ['nova.context'] + authorize(context) return self._get_extra_specs(context, flavor_id) @wsgi.serializers(xml=ExtraSpecsTemplate) def create(self, req, flavor_id, body): - self._check_body(body) context = req.environ['nova.context'] + authorize(context) + self._check_body(body) specs = body.get('extra_specs') try: db.instance_type_extra_specs_update_or_create(context, @@ -67,8 +72,9 @@ class FlavorExtraSpecsController(object): @wsgi.serializers(xml=ExtraSpecsTemplate) def update(self, req, flavor_id, id, body): - self._check_body(body) context = req.environ['nova.context'] + authorize(context) + self._check_body(body) if not id in body: expl = _('Request body and URI mismatch') raise exc.HTTPBadRequest(explanation=expl) @@ -88,6 +94,7 @@ class FlavorExtraSpecsController(object): def show(self, req, flavor_id, id): """ Return a single extra spec item """ context = req.environ['nova.context'] + authorize(context) specs = self._get_extra_specs(context, flavor_id) if id in specs['extra_specs']: return {id: specs['extra_specs'][id]} @@ -97,6 +104,7 @@ class FlavorExtraSpecsController(object): def delete(self, req, flavor_id, id): """ Deletes an existing extra spec """ context = req.environ['nova.context'] + authorize(context) db.instance_type_extra_specs_delete(context, flavor_id, id) def _handle_quota_error(self, error): diff --git a/nova/api/openstack/compute/contrib/floating_ip_dns.py b/nova/api/openstack/compute/contrib/floating_ip_dns.py index a0ac9241f..dbdd0c3a9 100644 --- a/nova/api/openstack/compute/contrib/floating_ip_dns.py +++ b/nova/api/openstack/compute/contrib/floating_ip_dns.py @@ -27,6 +27,7 @@ from nova import network LOG = logging.getLogger('nova.api.openstack.compute.contrib.floating_ip_dns') +authorize = extensions.extension_authorizer('compute', 'floating_ip_dns') def make_dns_entry(elem): @@ -138,6 +139,7 @@ class FloatingIPDNSDomainController(object): def index(self, req): """Return a list of available DNS domains.""" context = req.environ['nova.context'] + authorize(context) domains = self.network_api.get_dns_domains(context) domainlist = [_create_domain_entry(domain['domain'], domain.get('scope'), @@ -151,6 +153,7 @@ class FloatingIPDNSDomainController(object): def update(self, req, id, body): """Add or modify domain entry""" context = req.environ['nova.context'] + authorize(context) fqdomain = _unquote_domain(id) try: entry = body['domain_entry'] @@ -185,6 +188,7 @@ class FloatingIPDNSDomainController(object): def delete(self, req, id): """Delete the domain identified by id. """ context = req.environ['nova.context'] + authorize(context) params = req.str_GET domain = _unquote_domain(id) @@ -210,6 +214,7 @@ class FloatingIPDNSEntryController(object): def show(self, req, domain_id, id): """Return the DNS entry that corresponds to domain_id and id.""" context = req.environ['nova.context'] + authorize(context) domain = _unquote_domain(domain_id) name = id @@ -222,6 +227,7 @@ class FloatingIPDNSEntryController(object): def index(self, req, domain_id): """Return a list of dns entries for the specified domain and ip.""" context = req.environ['nova.context'] + authorize(context) params = req.GET floating_ip = params.get('ip') domain = _unquote_domain(domain_id) @@ -241,6 +247,7 @@ class FloatingIPDNSEntryController(object): def update(self, req, domain_id, id, body): """Add or modify dns entry""" context = req.environ['nova.context'] + authorize(context) domain = _unquote_domain(domain_id) name = id try: @@ -268,6 +275,7 @@ class FloatingIPDNSEntryController(object): def delete(self, req, domain_id, id): """Delete the entry identified by req and id. """ context = req.environ['nova.context'] + authorize(context) domain = _unquote_domain(domain_id) name = id diff --git a/nova/api/openstack/compute/contrib/floating_ip_pools.py b/nova/api/openstack/compute/contrib/floating_ip_pools.py index 0bf2362af..574ef26b0 100644 --- a/nova/api/openstack/compute/contrib/floating_ip_pools.py +++ b/nova/api/openstack/compute/contrib/floating_ip_pools.py @@ -22,6 +22,7 @@ from nova import network LOG = logging.getLogger('nova.api.openstack.compute.contrib.floating_ip_pools') +authorize = extensions.extension_authorizer('compute', 'floating_ip_pools') def _translate_floating_ip_view(pool): @@ -69,6 +70,7 @@ class FloatingIPPoolsController(object): def index(self, req): """Return a list of pools.""" context = req.environ['nova.context'] + authorize(context) pools = self.network_api.get_floating_ip_pools(context) return _translate_floating_ip_pools_view(pools) diff --git a/nova/api/openstack/compute/contrib/floating_ips.py b/nova/api/openstack/compute/contrib/floating_ips.py index 3cfbc0604..afbdcf83a 100644 --- a/nova/api/openstack/compute/contrib/floating_ips.py +++ b/nova/api/openstack/compute/contrib/floating_ips.py @@ -30,6 +30,7 @@ from nova import rpc LOG = logging.getLogger('nova.api.openstack.compute.contrib.floating_ips') +authorize = extensions.extension_authorizer('compute', 'floating_ips') def make_float_ip(elem): @@ -116,6 +117,7 @@ class FloatingIPController(object): def show(self, req, id): """Return data about the given floating ip.""" context = req.environ['nova.context'] + authorize(context) try: floating_ip = self.network_api.get_floating_ip(context, id) @@ -130,6 +132,7 @@ class FloatingIPController(object): def index(self, req): """Return a list of floating ips allocated to a project.""" context = req.environ['nova.context'] + authorize(context) floating_ips = self.network_api.get_floating_ips_by_project(context) @@ -141,6 +144,7 @@ class FloatingIPController(object): @wsgi.serializers(xml=FloatingIPTemplate) def create(self, req, body=None): context = req.environ['nova.context'] + authorize(context) pool = None if body and 'pool' in body: @@ -163,6 +167,7 @@ class FloatingIPController(object): def delete(self, req, id): context = req.environ['nova.context'] + authorize(context) floating_ip = self.network_api.get_floating_ip(context, id) if floating_ip.get('fixed_ip_id'): @@ -188,6 +193,7 @@ class FloatingIPActionController(wsgi.Controller): def _add_floating_ip(self, req, id, body): """Associate floating_ip to an instance.""" context = req.environ['nova.context'] + authorize(context) try: address = body['addFloatingIp']['address'] @@ -213,6 +219,7 @@ class FloatingIPActionController(wsgi.Controller): def _remove_floating_ip(self, req, id, body): """Dissociate floating_ip from an instance.""" context = req.environ['nova.context'] + authorize(context) try: address = body['removeFloatingIp']['address'] diff --git a/nova/api/openstack/compute/contrib/hosts.py b/nova/api/openstack/compute/contrib/hosts.py index 66dd64def..6ee89859b 100644 --- a/nova/api/openstack/compute/contrib/hosts.py +++ b/nova/api/openstack/compute/contrib/hosts.py @@ -31,6 +31,7 @@ from nova.scheduler import api as scheduler_api LOG = logging.getLogger("nova.api.openstack.compute.contrib.hosts") FLAGS = flags.FLAGS +authorize = extensions.extension_authorizer('compute', 'hosts') class HostIndexTemplate(xmlutil.TemplateBuilder): @@ -112,12 +113,14 @@ class HostController(object): @wsgi.serializers(xml=HostIndexTemplate) def index(self, req): + authorize(req.environ['nova.context']) return {'hosts': _list_hosts(req)} @wsgi.serializers(xml=HostUpdateTemplate) @wsgi.deserializers(xml=HostDeserializer) @check_host def update(self, req, id, body): + authorize(req.environ['nova.context']) for raw_key, raw_val in body.iteritems(): key = raw_key.lower().strip() val = raw_val.lower().strip() @@ -149,6 +152,7 @@ class HostController(object): def _host_power_action(self, req, host, action): """Reboots, shuts down or powers up the host.""" context = req.environ['nova.context'] + authorize(context) try: result = self.compute_api.host_power_action(context, host=host, action=action) @@ -176,7 +180,6 @@ class Hosts(extensions.ExtensionDescriptor): alias = "os-hosts" namespace = "http://docs.openstack.org/compute/ext/hosts/api/v1.1" updated = "2011-06-29T00:00:00+00:00" - admin_only = True def get_resources(self): resources = [extensions.ResourceExtension('os-hosts', diff --git a/nova/api/openstack/compute/contrib/keypairs.py b/nova/api/openstack/compute/contrib/keypairs.py index 504539d55..7cd919673 100644 --- a/nova/api/openstack/compute/contrib/keypairs.py +++ b/nova/api/openstack/compute/contrib/keypairs.py @@ -31,6 +31,9 @@ from nova import db from nova import exception +authorize = extensions.extension_authorizer('compute', 'keypairs') + + class KeypairTemplate(xmlutil.TemplateBuilder): def construct(self): return xmlutil.MasterTemplate(xmlutil.make_flat_dict('keypair'), 1) @@ -77,6 +80,7 @@ class KeypairController(object): """ context = req.environ['nova.context'] + authorize(context) params = body['keypair'] name = params['name'] @@ -109,6 +113,7 @@ class KeypairController(object): Delete a keypair with a given name """ context = req.environ['nova.context'] + authorize(context) db.key_pair_destroy(context, context.user_id, id) return webob.Response(status_int=202) @@ -118,6 +123,7 @@ class KeypairController(object): List of keypairs for a user """ context = req.environ['nova.context'] + authorize(context) key_pairs = db.key_pair_get_all_by_user(context, context.user_id) rval = [] for key_pair in key_pairs: diff --git a/nova/api/openstack/compute/contrib/multinic.py b/nova/api/openstack/compute/contrib/multinic.py index eea4532df..a3b4a8dc8 100644 --- a/nova/api/openstack/compute/contrib/multinic.py +++ b/nova/api/openstack/compute/contrib/multinic.py @@ -26,6 +26,7 @@ from nova import log as logging LOG = logging.getLogger("nova.api.openstack.compute.contrib.multinic") +authorize = extensions.extension_authorizer('compute', 'multinic') class MultinicController(wsgi.Controller): @@ -43,13 +44,14 @@ class MultinicController(wsgi.Controller): @wsgi.action('addFixedIp') def _add_fixed_ip(self, req, id, body): """Adds an IP on a given network to an instance.""" + context = req.environ['nova.context'] + authorize(context) # Validate the input entity if 'networkId' not in body['addFixedIp']: msg = _("Missing 'networkId' argument for addFixedIp") raise exc.HTTPUnprocessableEntity(explanation=msg) - context = req.environ['nova.context'] instance = self._get_instance(context, id) network_id = body['addFixedIp']['networkId'] self.compute_api.add_fixed_ip(context, instance, network_id) @@ -58,13 +60,14 @@ class MultinicController(wsgi.Controller): @wsgi.action('removeFixedIp') def _remove_fixed_ip(self, req, id, body): """Removes an IP from an instance.""" + context = req.environ['nova.context'] + authorize(context) # Validate the input entity if 'address' not in body['removeFixedIp']: msg = _("Missing 'address' argument for removeFixedIp") raise exc.HTTPUnprocessableEntity(explanation=msg) - context = req.environ['nova.context'] instance = self._get_instance(context, id) address = body['removeFixedIp']['address'] diff --git a/nova/api/openstack/compute/contrib/networks.py b/nova/api/openstack/compute/contrib/networks.py index f2381a19d..765a1d2fe 100644 --- a/nova/api/openstack/compute/contrib/networks.py +++ b/nova/api/openstack/compute/contrib/networks.py @@ -28,6 +28,7 @@ import nova.network.api FLAGS = flags.FLAGS LOG = logging.getLogger('nova.api.openstack.compute.contrib.networks') +authorize = extensions.extension_authorizer('compute', 'networks') def network_dict(network): @@ -65,6 +66,7 @@ class NetworkController(object): def _disassociate(self, request, network_id, body): context = request.environ['nova.context'] + authorize(context) LOG.debug(_("Disassociating network with id %s" % network_id)) try: self.network_api.disassociate(context, network_id) @@ -74,12 +76,14 @@ class NetworkController(object): def index(self, req): context = req.environ['nova.context'] + authorize(context) networks = self.network_api.get_all(context) result = [network_dict(net_ref) for net_ref in networks] return {'networks': result} def show(self, req, id): context = req.environ['nova.context'] + authorize(context) LOG.debug(_("Showing network with id %s") % id) try: network = self.network_api.get(context, id) @@ -89,6 +93,7 @@ class NetworkController(object): def delete(self, req, id): context = req.environ['nova.context'] + authorize(context) LOG.info(_("Deleting network with id %s") % id) try: self.network_api.delete(context, id) @@ -107,7 +112,6 @@ class Networks(extensions.ExtensionDescriptor): alias = "os-networks" namespace = "http://docs.openstack.org/compute/ext/networks/api/v1.1" updated = "2011-12-23 00:00:00" - admin_only = True def get_resources(self): member_actions = {'action': 'POST'} diff --git a/nova/api/openstack/compute/contrib/quotas.py b/nova/api/openstack/compute/contrib/quotas.py index 5e4a20568..ce466af3f 100644 --- a/nova/api/openstack/compute/contrib/quotas.py +++ b/nova/api/openstack/compute/contrib/quotas.py @@ -25,6 +25,9 @@ from nova import exception from nova import quota +authorize = extensions.extension_authorizer('compute', 'quotas') + + quota_resources = ['metadata_items', 'injected_file_content_bytes', 'volumes', 'gigabytes', 'ram', 'floating_ips', 'instances', 'injected_files', 'cores'] @@ -57,6 +60,7 @@ class QuotaSetsController(object): @wsgi.serializers(xml=QuotaTemplate) def show(self, req, id): context = req.environ['nova.context'] + authorize(context) try: db.sqlalchemy.api.authorize_project_context(context, id) return self._format_quota_set(id, @@ -67,6 +71,7 @@ class QuotaSetsController(object): @wsgi.serializers(xml=QuotaTemplate) def update(self, req, id, body): context = req.environ['nova.context'] + authorize(context) project_id = id for key in body['quota_set'].keys(): if key in quota_resources: @@ -80,6 +85,7 @@ class QuotaSetsController(object): return {'quota_set': quota.get_project_quotas(context, project_id)} def defaults(self, req, id): + authorize(req.environ['nova.context']) return self._format_quota_set(id, quota._get_default_quotas()) diff --git a/nova/api/openstack/compute/contrib/rescue.py b/nova/api/openstack/compute/contrib/rescue.py index b0d5c6f97..e5fbb8388 100644 --- a/nova/api/openstack/compute/contrib/rescue.py +++ b/nova/api/openstack/compute/contrib/rescue.py @@ -28,6 +28,7 @@ from nova import utils FLAGS = flags.FLAGS LOG = logging.getLogger("nova.api.openstack.compute.contrib.rescue") +authorize = exts.extension_authorizer('compute', 'rescue') class RescueController(wsgi.Controller): @@ -47,6 +48,7 @@ class RescueController(wsgi.Controller): def _rescue(self, req, id, body): """Rescue an instance.""" context = req.environ["nova.context"] + authorize(context) if body['rescue'] and 'adminPass' in body['rescue']: password = body['rescue']['adminPass'] @@ -62,6 +64,7 @@ class RescueController(wsgi.Controller): def _unrescue(self, req, id, body): """Unrescue an instance.""" context = req.environ["nova.context"] + authorize(context) instance = self._get_instance(context, id) self.compute_api.unrescue(context, instance) return webob.Response(status_int=202) diff --git a/nova/api/openstack/compute/contrib/security_groups.py b/nova/api/openstack/compute/contrib/security_groups.py index 23b741145..45d72bf61 100644 --- a/nova/api/openstack/compute/contrib/security_groups.py +++ b/nova/api/openstack/compute/contrib/security_groups.py @@ -35,6 +35,7 @@ from nova import utils LOG = logging.getLogger("nova.api.openstack.compute.contrib.security_groups") FLAGS = flags.FLAGS +authorize = extensions.extension_authorizer('compute', 'security_groups') def make_rule(elem): @@ -223,6 +224,7 @@ class SecurityGroupController(object): def show(self, req, id): """Return data about the given security group.""" context = req.environ['nova.context'] + authorize(context) security_group = self._get_security_group(context, id) return {'security_group': self._format_security_group(context, security_group)} @@ -230,6 +232,7 @@ class SecurityGroupController(object): def delete(self, req, id): """Delete a security group.""" context = req.environ['nova.context'] + authorize(context) security_group = self._get_security_group(context, id) LOG.audit(_("Delete security group %s"), id, context=context) db.security_group_destroy(context, security_group.id) @@ -240,6 +243,7 @@ class SecurityGroupController(object): def index(self, req): """Returns a list of security groups""" context = req.environ['nova.context'] + authorize(context) self.compute_api.ensure_default_security_group(context) groups = db.security_group_get_by_project(context, @@ -257,6 +261,7 @@ class SecurityGroupController(object): def create(self, req, body): """Creates a new security group.""" context = req.environ['nova.context'] + authorize(context) if not body: raise exc.HTTPUnprocessableEntity() @@ -313,6 +318,7 @@ class SecurityGroupRulesController(SecurityGroupController): @wsgi.deserializers(xml=SecurityGroupRulesXMLDeserializer) def create(self, req, body): context = req.environ['nova.context'] + authorize(context) if not body: raise exc.HTTPUnprocessableEntity() @@ -471,6 +477,7 @@ class SecurityGroupRulesController(SecurityGroupController): def delete(self, req, id): context = req.environ['nova.context'] + authorize(context) self.compute_api.ensure_default_security_group(context) try: @@ -505,6 +512,7 @@ class SecurityGroupActionController(wsgi.Controller): @wsgi.action('addSecurityGroup') def _addSecurityGroup(self, req, id, body): context = req.environ['nova.context'] + authorize(context) try: body = body['addSecurityGroup'] @@ -535,6 +543,7 @@ class SecurityGroupActionController(wsgi.Controller): @wsgi.action('removeSecurityGroup') def _removeSecurityGroup(self, req, id, body): context = req.environ['nova.context'] + authorize(context) try: body = body['removeSecurityGroup'] diff --git a/nova/api/openstack/compute/contrib/server_action_list.py b/nova/api/openstack/compute/contrib/server_action_list.py index 436758572..24d3595d4 100644 --- a/nova/api/openstack/compute/contrib/server_action_list.py +++ b/nova/api/openstack/compute/contrib/server_action_list.py @@ -23,6 +23,7 @@ from nova import exception sa_nsmap = {None: wsgi.XMLNS_V11} +authorize = extensions.extension_authorizer('compute', 'server_action_list') class ServerActionsTemplate(xmlutil.TemplateBuilder): @@ -39,6 +40,7 @@ class ServerActionListController(object): @wsgi.serializers(xml=ServerActionsTemplate) def index(self, req, server_id): context = req.environ["nova.context"] + authorize(context) compute_api = compute.API() try: @@ -66,7 +68,6 @@ class Server_action_list(extensions.ExtensionDescriptor): namespace = "http://docs.openstack.org/compute/ext/" \ "server-actions-list/api/v1.1" updated = "2011-12-21T00:00:00+00:00" - admin_only = True def get_resources(self): parent_def = {'member_name': 'server', 'collection_name': 'servers'} diff --git a/nova/api/openstack/compute/contrib/server_diagnostics.py b/nova/api/openstack/compute/contrib/server_diagnostics.py index 11d1affaf..49afcac01 100644 --- a/nova/api/openstack/compute/contrib/server_diagnostics.py +++ b/nova/api/openstack/compute/contrib/server_diagnostics.py @@ -23,6 +23,7 @@ from nova import exception from nova.scheduler import api as scheduler_api +authorize = extensions.extension_authorizer('compute', 'server_diagnostics') sd_nsmap = {None: wsgi.XMLNS_V11} @@ -41,6 +42,7 @@ class ServerDiagnosticsController(object): @scheduler_api.redirect_handler def index(self, req, server_id): context = req.environ["nova.context"] + authorize(context) compute_api = compute.API() try: instance = compute_api.get(context, id) @@ -58,7 +60,6 @@ class Server_diagnostics(extensions.ExtensionDescriptor): namespace = "http://docs.openstack.org/compute/ext/" \ "server-diagnostics/api/v1.1" updated = "2011-12-21T00:00:00+00:00" - admin_only = True def get_resources(self): parent_def = {'member_name': 'server', 'collection_name': 'servers'} diff --git a/nova/api/openstack/compute/contrib/simple_tenant_usage.py b/nova/api/openstack/compute/contrib/simple_tenant_usage.py index 85ea9a859..8e4201546 100644 --- a/nova/api/openstack/compute/contrib/simple_tenant_usage.py +++ b/nova/api/openstack/compute/contrib/simple_tenant_usage.py @@ -29,6 +29,7 @@ from nova import flags FLAGS = flags.FLAGS +authorize = extensions.extension_authorizer('compute', 'simple_tenant_usage') def make_usage(elem): @@ -211,6 +212,7 @@ class SimpleTenantUsageController(object): def index(self, req): """Retrive tenant_usage for all tenants""" context = req.environ['nova.context'] + authorize(context) if not context.is_admin: return webob.Response(status_int=403) @@ -227,6 +229,7 @@ class SimpleTenantUsageController(object): """Retrive tenant_usage for a specified tenant""" tenant_id = id context = req.environ['nova.context'] + authorize(context) if not context.is_admin: if tenant_id != context.project_id: @@ -253,7 +256,6 @@ class Simple_tenant_usage(extensions.ExtensionDescriptor): namespace = "http://docs.openstack.org/compute/ext/" \ "os-simple-tenant-usage/api/v1.1" updated = "2011-08-19T00:00:00+00:00" - admin_only = True def get_resources(self): resources = [] diff --git a/nova/api/openstack/compute/contrib/users.py b/nova/api/openstack/compute/contrib/users.py index 55dba02e4..b2a97f327 100644 --- a/nova/api/openstack/compute/contrib/users.py +++ b/nova/api/openstack/compute/contrib/users.py @@ -27,6 +27,7 @@ from nova import log as logging FLAGS = flags.FLAGS LOG = logging.getLogger('nova.api.openstack.users') +authorize = extensions.extension_authorizer('compute', 'users') def make_user(elem): @@ -65,15 +66,10 @@ class Controller(object): def __init__(self): self.manager = manager.AuthManager() - def _check_admin(self, context): - """We cannot depend on the db layer to check for admin access - for the auth manager, so we do it here""" - if not context.is_admin: - raise exception.AdminRequired() - @wsgi.serializers(xml=UsersTemplate) def index(self, req): """Return all users in brief""" + authorize(req.environ['nova.context']) users = self.manager.get_users() users = common.limited(users, req) users = [_translate_keys(user) for user in users] @@ -87,6 +83,7 @@ class Controller(object): @wsgi.serializers(xml=UserTemplate) def show(self, req, id): """Return data about the given user id""" + authorize(req.environ['nova.context']) #NOTE(justinsb): The drivers are a little inconsistent in how they # deal with "NotFound" - some throw, some return None. @@ -101,13 +98,13 @@ class Controller(object): return dict(user=_translate_keys(user)) def delete(self, req, id): - self._check_admin(req.environ['nova.context']) + authorize(req.environ['nova.context']) self.manager.delete_user(id) return {} @wsgi.serializers(xml=UserTemplate) def create(self, req, body): - self._check_admin(req.environ['nova.context']) + authorize(req.environ['nova.context']) is_admin = body['user'].get('admin') in ('T', 'True', True) name = body['user'].get('name') access = body['user'].get('access') @@ -117,7 +114,7 @@ class Controller(object): @wsgi.serializers(xml=UserTemplate) def update(self, req, id, body): - self._check_admin(req.environ['nova.context']) + authorize(req.environ['nova.context']) is_admin = body['user'].get('admin') if is_admin is not None: is_admin = is_admin in ('T', 'True', True) @@ -134,7 +131,6 @@ class Users(extensions.ExtensionDescriptor): alias = "os-users" namespace = "http://docs.openstack.org/compute/ext/users/api/v1.1" updated = "2011-08-08T00:00:00+00:00" - admin_only = True def get_resources(self): coll_actions = {'detail': 'GET'} diff --git a/nova/api/openstack/compute/contrib/virtual_interfaces.py b/nova/api/openstack/compute/contrib/virtual_interfaces.py index ea37c4d97..e8da23c00 100644 --- a/nova/api/openstack/compute/contrib/virtual_interfaces.py +++ b/nova/api/openstack/compute/contrib/virtual_interfaces.py @@ -25,6 +25,7 @@ from nova import network LOG = logging.getLogger("nova.api.openstack.compute." "contrib.virtual_interfaces") +authorize = extensions.extension_authorizer('compute', 'virtual_interfaces') vif_nsmap = {None: wsgi.XMLNS_V11} @@ -68,6 +69,7 @@ class ServerVirtualInterfaceController(object): @wsgi.serializers(xml=VirtualInterfaceTemplate) def index(self, req, server_id): """Returns the list of VIFs for a given instance.""" + authorize(req.environ['nova.context']) return self._items(req, server_id, entity_maker=_translate_vif_summary_view) diff --git a/nova/api/openstack/compute/contrib/virtual_storage_arrays.py b/nova/api/openstack/compute/contrib/virtual_storage_arrays.py index 10e8a1001..ed4b12a92 100644 --- a/nova/api/openstack/compute/contrib/virtual_storage_arrays.py +++ b/nova/api/openstack/compute/contrib/virtual_storage_arrays.py @@ -36,9 +36,11 @@ from nova import log as logging from nova import vsa from nova import volume -FLAGS = flags.FLAGS +FLAGS = flags.FLAGS LOG = logging.getLogger("nova.api.openstack.compute.contrib.vsa") +authorize = extensions.extension_authorizer('compute', + 'virtual_storage_arrays') def _vsa_view(context, vsa, details=False, instances=None): @@ -124,6 +126,7 @@ class VsaController(object): def _items(self, req, details): """Return summary or detailed list of VSAs.""" context = req.environ['nova.context'] + authorize(context) vsas = self.vsa_api.get_all(context) limited_list = common.limited(vsas, req) @@ -147,6 +150,7 @@ class VsaController(object): def show(self, req, id): """Return data about the given VSA.""" context = req.environ['nova.context'] + authorize(context) try: vsa = self.vsa_api.get(context, vsa_id=id) @@ -160,6 +164,7 @@ class VsaController(object): def create(self, req, body): """Create a new VSA.""" context = req.environ['nova.context'] + authorize(context) if not body or 'vsa' not in body: LOG.debug(_("No body provided"), context=context) @@ -193,6 +198,7 @@ class VsaController(object): def delete(self, req, id): """Delete a VSA.""" context = req.environ['nova.context'] + authorize(context) LOG.audit(_("Delete VSA with id: %s"), id, context=context) @@ -206,6 +212,7 @@ class VsaController(object): auto or manually associate an IP to VSA """ context = req.environ['nova.context'] + authorize(context) if body is None: ip = 'auto' @@ -234,6 +241,7 @@ class VsaController(object): auto or manually associate an IP to VSA """ context = req.environ['nova.context'] + authorize(context) if body is None: ip = 'auto' @@ -293,6 +301,7 @@ class VsaVolumeDriveController(volumes.VolumeController): def _items(self, req, vsa_id, details): """Return summary or detailed list of volumes for particular VSA.""" context = req.environ['nova.context'] + authorize(context) vols = self.volume_api.get_all(context, search_opts={'metadata': {self.direction: str(vsa_id)}}) @@ -317,6 +326,7 @@ class VsaVolumeDriveController(volumes.VolumeController): """Create a new volume from VSA.""" LOG.audit(_("Create. vsa_id=%(vsa_id)s, body=%(body)s"), locals()) context = req.environ['nova.context'] + authorize(context) if not body: raise exc.HTTPUnprocessableEntity() @@ -345,6 +355,7 @@ class VsaVolumeDriveController(volumes.VolumeController): def update(self, req, vsa_id, id, body): """Update a volume.""" context = req.environ['nova.context'] + authorize(context) try: self._check_volume_ownership(context, vsa_id, id) @@ -380,6 +391,7 @@ class VsaVolumeDriveController(volumes.VolumeController): def delete(self, req, vsa_id, id): """Delete a volume.""" context = req.environ['nova.context'] + authorize(context) LOG.audit(_("Delete. vsa_id=%(vsa_id)s, id=%(id)s"), locals()) @@ -395,6 +407,7 @@ class VsaVolumeDriveController(volumes.VolumeController): def show(self, req, vsa_id, id): """Return data about the given volume.""" context = req.environ['nova.context'] + authorize(context) LOG.audit(_("Show. vsa_id=%(vsa_id)s, id=%(id)s"), locals()) diff --git a/nova/api/openstack/compute/contrib/volumes.py b/nova/api/openstack/compute/contrib/volumes.py index 83e67102b..c3ba33eef 100644 --- a/nova/api/openstack/compute/contrib/volumes.py +++ b/nova/api/openstack/compute/contrib/volumes.py @@ -32,9 +32,8 @@ from nova.volume import volume_types LOG = logging.getLogger("nova.api.openstack.compute.contrib.volumes") - - FLAGS = flags.FLAGS +authorize = extensions.extension_authorizer('compute', 'volumes') def _translate_volume_detail_view(context, vol): @@ -130,6 +129,7 @@ class VolumeController(object): def show(self, req, id): """Return data about the given volume.""" context = req.environ['nova.context'] + authorize(context) try: vol = self.volume_api.get(context, id) @@ -141,6 +141,7 @@ class VolumeController(object): def delete(self, req, id): """Delete a volume.""" context = req.environ['nova.context'] + authorize(context) LOG.audit(_("Delete volume with id: %s"), id, context=context) @@ -164,6 +165,7 @@ class VolumeController(object): def _items(self, req, entity_maker): """Returns a list of volumes, transformed through entity_maker.""" context = req.environ['nova.context'] + authorize(context) volumes = self.volume_api.get_all(context) limited_list = common.limited(volumes, req) @@ -174,6 +176,7 @@ class VolumeController(object): def create(self, req, body): """Creates a new volume.""" context = req.environ['nova.context'] + authorize(context) if not body: raise exc.HTTPUnprocessableEntity() @@ -289,6 +292,7 @@ class VolumeAttachmentController(object): def show(self, req, server_id, id): """Return data about the given volume attachment.""" context = req.environ['nova.context'] + authorize(context) volume_id = id try: @@ -309,6 +313,7 @@ class VolumeAttachmentController(object): def create(self, req, server_id, body): """Attach a volume to an instance.""" context = req.environ['nova.context'] + authorize(context) if not body: raise exc.HTTPUnprocessableEntity() @@ -350,6 +355,7 @@ class VolumeAttachmentController(object): def delete(self, req, server_id, id): """Detach a volume from an instance.""" context = req.environ['nova.context'] + authorize(context) volume_id = id LOG.audit(_("Detach volume %s"), volume_id, context=context) @@ -372,6 +378,7 @@ class VolumeAttachmentController(object): def _items(self, req, server_id, entity_maker): """Returns a list of attachments, transformed through entity_maker.""" context = req.environ['nova.context'] + authorize(context) try: instance = self.compute_api.get(context, server_id) @@ -452,6 +459,7 @@ class SnapshotController(object): def show(self, req, id): """Return data about the given snapshot.""" context = req.environ['nova.context'] + authorize(context) try: vol = self.volume_api.get_snapshot(context, id) @@ -463,6 +471,7 @@ class SnapshotController(object): def delete(self, req, id): """Delete a snapshot.""" context = req.environ['nova.context'] + authorize(context) LOG.audit(_("Delete snapshot with id: %s"), id, context=context) @@ -485,6 +494,7 @@ class SnapshotController(object): def _items(self, req, entity_maker): """Returns a list of snapshots, transformed through entity_maker.""" context = req.environ['nova.context'] + authorize(context) snapshots = self.volume_api.get_all_snapshots(context) limited_list = common.limited(snapshots, req) @@ -495,6 +505,7 @@ class SnapshotController(object): def create(self, req, body): """Creates a new snapshot.""" context = req.environ['nova.context'] + authorize(context) if not body: return exc.HTTPUnprocessableEntity() diff --git a/nova/api/openstack/compute/contrib/volumetypes.py b/nova/api/openstack/compute/contrib/volumetypes.py index bf249f3f8..cbc205ea7 100644 --- a/nova/api/openstack/compute/contrib/volumetypes.py +++ b/nova/api/openstack/compute/contrib/volumetypes.py @@ -27,6 +27,9 @@ from nova import exception from nova.volume import volume_types +authorize = extensions.extension_authorizer('compute', 'volumetypes') + + def make_voltype(elem): elem.set('id') elem.set('name') @@ -57,12 +60,14 @@ class VolumeTypesController(object): def index(self, req): """ Returns the list of volume types """ context = req.environ['nova.context'] + authorize(context) return volume_types.get_all_types(context) @wsgi.serializers(xml=VolumeTypeTemplate) def create(self, req, body): """Creates a new volume type.""" context = req.environ['nova.context'] + authorize(context) if not body or body == "": raise exc.HTTPUnprocessableEntity() @@ -91,6 +96,7 @@ class VolumeTypesController(object): def show(self, req, id): """ Return a single volume type item """ context = req.environ['nova.context'] + authorize(context) try: vol_type = volume_types.get_volume_type(context, id) @@ -102,6 +108,7 @@ class VolumeTypesController(object): def delete(self, req, id): """ Deletes an existing volume type """ context = req.environ['nova.context'] + authorize(context) try: vol_type = volume_types.get_volume_type(context, id) @@ -155,12 +162,14 @@ class VolumeTypeExtraSpecsController(object): def index(self, req, vol_type_id): """ Returns the list of extra specs for a given volume type """ context = req.environ['nova.context'] + authorize(context) return self._get_extra_specs(context, vol_type_id) @wsgi.serializers(xml=VolumeTypeExtraSpecsTemplate) def create(self, req, vol_type_id, body): - self._check_body(body) context = req.environ['nova.context'] + authorize(context) + self._check_body(body) specs = body.get('extra_specs') try: db.volume_type_extra_specs_update_or_create(context, @@ -172,8 +181,9 @@ class VolumeTypeExtraSpecsController(object): @wsgi.serializers(xml=VolumeTypeExtraSpecTemplate) def update(self, req, vol_type_id, id, body): - self._check_body(body) context = req.environ['nova.context'] + authorize(context) + self._check_body(body) if not id in body: expl = _('Request body and URI mismatch') raise exc.HTTPBadRequest(explanation=expl) @@ -193,6 +203,7 @@ class VolumeTypeExtraSpecsController(object): def show(self, req, vol_type_id, id): """ Return a single extra spec item """ context = req.environ['nova.context'] + authorize(context) specs = self._get_extra_specs(context, vol_type_id) if id in specs['extra_specs']: return {id: specs['extra_specs'][id]} @@ -202,6 +213,7 @@ class VolumeTypeExtraSpecsController(object): def delete(self, req, vol_type_id, id): """ Deletes an existing extra spec """ context = req.environ['nova.context'] + authorize(context) db.volume_type_extra_specs_delete(context, vol_type_id, id) def _handle_quota_error(self, error): diff --git a/nova/api/openstack/compute/contrib/zones.py b/nova/api/openstack/compute/contrib/zones.py index 539833ee2..fac3d7188 100644 --- a/nova/api/openstack/compute/contrib/zones.py +++ b/nova/api/openstack/compute/contrib/zones.py @@ -34,6 +34,7 @@ import nova.scheduler.api LOG = logging.getLogger("nova.api.openstack.compute.contrib.zones") FLAGS = flags.FLAGS +authorize = extensions.extension_authorizer('compute', 'zones') class CapabilitySelector(object): @@ -117,6 +118,7 @@ class Controller(object): @wsgi.serializers(xml=ZonesTemplate) def index(self, req): """Return all zones in brief""" + authorize(req.environ['nova.context']) # Ask the ZoneManager in the Scheduler for most recent data, # or fall-back to the database ... items = nova.scheduler.api.get_zone_list(req.environ['nova.context']) @@ -133,6 +135,7 @@ class Controller(object): def info(self, req): """Return name and capabilities for this zone.""" context = req.environ['nova.context'] + authorize(context) zone_capabs = nova.scheduler.api.get_zone_capabilities(context) # NOTE(comstud): This should probably return, instead: # {'zone': {'name': FLAGS.zone_name, @@ -143,13 +146,15 @@ class Controller(object): @wsgi.serializers(xml=ZoneTemplate) def show(self, req, id): """Return data about the given zone id""" - zone_id = int(id) context = req.environ['nova.context'] + authorize(context) + zone_id = int(id) zone = nova.scheduler.api.zone_get(context, zone_id) return dict(zone=_scrub_zone(zone)) def delete(self, req, id): """Delete a child zone entry.""" + authorize(req.environ['nova.context']) zone_id = int(id) nova.scheduler.api.zone_delete(req.environ['nova.context'], zone_id) return {} @@ -159,6 +164,7 @@ class Controller(object): def create(self, req, body): """Create a child zone entry.""" context = req.environ['nova.context'] + authorize(context) zone = nova.scheduler.api.zone_create(context, body["zone"]) return dict(zone=_scrub_zone(zone)) @@ -166,6 +172,7 @@ class Controller(object): def update(self, req, id, body): """Update a child zone entry.""" context = req.environ['nova.context'] + authorize(context) zone_id = int(id) zone = nova.scheduler.api.zone_update(context, zone_id, body["zone"]) return dict(zone=_scrub_zone(zone)) @@ -175,9 +182,10 @@ class Controller(object): def select(self, req, body): """Returns a weighted list of costs to create instances of desired capabilities.""" - ctx = req.environ['nova.context'] + context = req.environ['nova.context'] + authorize(context) specs = json.loads(body) - build_plan = nova.scheduler.api.select(ctx, specs=specs) + build_plan = nova.scheduler.api.select(context, specs=specs) cooked = self._scrub_build_plan(build_plan) return {"weights": cooked} @@ -205,7 +213,6 @@ class Zones(extensions.ExtensionDescriptor): alias = "os-zones" namespace = "http://docs.openstack.org/compute/ext/zones/api/v1.1" updated = "2011-09-21T00:00:00+00:00" - admin_only = True def get_resources(self): #NOTE(bcwaldon): This resource should be prefixed with 'os-' diff --git a/nova/api/openstack/compute/servers.py b/nova/api/openstack/compute/servers.py index ee7038c6e..d0e771e22 100644 --- a/nova/api/openstack/compute/servers.py +++ b/nova/api/openstack/compute/servers.py @@ -1170,7 +1170,7 @@ def create_resource(): def remove_invalid_options(context, search_options, allowed_search_options): """Remove search options that are not valid for non-admin API/context""" - if FLAGS.allow_admin_api and context.is_admin: + if context.is_admin: # Allow all options return # Otherwise, strip out all unknown options diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 4b8da21ed..bf415765c 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -16,8 +16,10 @@ # License for the specific language governing permissions and limitations # under the License. +import functools import os import routes + import webob.dec import webob.exc @@ -27,13 +29,12 @@ from nova.api.openstack import xmlutil from nova import exception from nova import flags from nova import log as logging +import nova.policy from nova import utils from nova import wsgi as base_wsgi LOG = logging.getLogger('nova.api.openstack.extensions') - - FLAGS = flags.FLAGS @@ -61,10 +62,6 @@ class ExtensionDescriptor(object): # '2011-01-22T13:25:27-06:00' updated = None - # This attribute causes the extension to load only when - # the admin api is enabled - admin_only = False - def __init__(self, ext_mgr): """Register extension with the extension manager.""" @@ -246,15 +243,10 @@ class ExtensionManager(object): ' '.join(extension.__doc__.strip().split())) LOG.debug(_('Ext namespace: %s'), extension.namespace) LOG.debug(_('Ext updated: %s'), extension.updated) - LOG.debug(_('Ext admin_only: %s'), extension.admin_only) except AttributeError as ex: LOG.exception(_("Exception loading extension: %s"), unicode(ex)) return False - # Don't load admin api extensions if the admin api isn't enabled - if not FLAGS.allow_admin_api and extension.admin_only: - return False - return True def load_extension(self, ext_factory): @@ -384,3 +376,22 @@ def load_standard_extensions(ext_mgr, logger, path, package): # Update the list of directories we'll explore... dirnames[:] = subdirs + + +def extension_authorizer(api_name, extension_name): + def authorize(context): + action = '%s_extension:%s' % (api_name, extension_name) + nova.policy.enforce(context, action, {}) + return authorize + + +def soft_extension_authorizer(api_name, extension_name): + hard_authorize = extension_authorizer(api_name, extension_name) + + def authorize(context): + try: + hard_authorize(context) + return True + except exception.NotAuthorized: + return False + return authorize -- cgit